diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 74f1959..2d8c19f 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: Fedo Android SDK issues
- url: https://github.com/kusa-software/fedo-sdk/issues
+ url: https://github.com/getfedo/fedo-sdk/issues
about: Bugs and feature requests for the Fedo Android SDK (com.getfedo:sdk-android) belong in the SDK repository.
- name: Fedo documentation
url: https://docs.getfedo.com/next/guide/getting-started/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 38d20fb..2a3521f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -122,6 +122,6 @@ they only come alive once the code is pushed there.
## Where to report
- Bugs and ideas for this example app: [GitHub issues](https://github.com/getfedo/fedo-android-example/issues) in this repository.
-- Bugs in the Fedo Android SDK itself: [kusa-software/fedo-sdk issues](https://github.com/kusa-software/fedo-sdk/issues).
+- Bugs in the Fedo Android SDK itself: [getfedo/fedo-sdk issues](https://github.com/getfedo/fedo-sdk/issues).
- Security vulnerabilities: privately, as described in [SECURITY.md](SECURITY.md).
- Conduct concerns: see the [Code of Conduct](CODE_OF_CONDUCT.md).
diff --git a/README.md b/README.md
index 94db285..5f3cbe0 100644
--- a/README.md
+++ b/README.md
@@ -20,15 +20,15 @@ Single `:app` module. Compose, Koin, OkHttp, kotlinx.serialization and Navigatio
## Screenshots
-| Models | Model detail | Roadmap | Settings |
-| --- | --- | --- | --- |
+| Models | Model detail | Roadmap | Settings |
+|----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
|
|
|
|
Light theme
-| Models | Model detail | Roadmap | Settings |
-| --- | --- | --- | --- |
+| Models | Model detail | Roadmap | Settings |
+|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
|
|
|
|
|
@@ -62,14 +62,14 @@ No key yet? The app still builds and runs: the models list, search, filter and d
## Where Fedo is used
-| API | File | What it does |
-| --- | --- | --- |
-| `Fedo.initialize(context, apiKey) { }` | [`ModelPulseApplication.kt`](app/src/main/java/com/fedo/modelpulse/ModelPulseApplication.kt) | Initializes the SDK once at startup, with debug logging in debug builds. Skipped entirely when no key is configured. |
-| `FedoFeedbackScreen(onDismiss = …)` | [`RoadmapScreen.kt`](app/src/main/java/com/fedo/modelpulse/ui/roadmap/RoadmapScreen.kt) | The Roadmap destination: the whole feedback board. It owns its internal navigation, so `onDismiss` only fires at the board root and pops the app's own back stack. |
+| API | File | What it does |
+|-------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `Fedo.initialize(context, apiKey) { }` | [`ModelPulseApplication.kt`](app/src/main/java/com/fedo/modelpulse/ModelPulseApplication.kt) | Initializes the SDK once at startup, with debug logging in debug builds. Skipped entirely when no key is configured. |
+| `FedoFeedbackScreen(onDismiss = …)` | [`RoadmapScreen.kt`](app/src/main/java/com/fedo/modelpulse/ui/roadmap/RoadmapScreen.kt) | The Roadmap destination: the whole feedback board. It owns its internal navigation, so `onDismiss` only fires at the board root and pops the app's own back stack. |
| `Fedo.setUserID(…)`
`Fedo.setUserDisplayName(…)`
`Fedo.setUserEmail(…)` | [`SettingsViewModel.kt`](app/src/main/java/com/fedo/modelpulse/ui/settings/SettingsViewModel.kt) | Demo sign-in. Feedback, votes and comments left as a guest move to the signed-in account. The demo id is a generated `demo-` UUID, never the email — a real app passes its own backend user id and keeps PII out of it. |
-| `Fedo.setUserProperty("favorite_provider", …)` | [`ModelsViewModel.kt`](app/src/main/java/com/fedo/modelpulse/ui/models/ModelsViewModel.kt) | Records the provider picked in the filter as a user property, so feedback can be segmented by what the user cares about. Properties are user-level and last-write-wins; clearing the filter writes an empty value. |
-| `FedoCreateFeedbackSheet(onDismiss = …)` | [`ModelsScreen.kt`](app/src/main/java/com/fedo/modelpulse/ui/models/ModelsScreen.kt) | The contextual sheet: "Missing a model? Request it" in the no-results state and "Report a problem" in the load-error state. Both appear only when a key is configured. |
-| `Fedo.logout()` | [`SettingsViewModel.kt`](app/src/main/java/com/fedo/modelpulse/ui/settings/SettingsViewModel.kt) | Demo sign-out: clears the identity and starts a new guest. |
+| `Fedo.setUserProperty("favorite_provider", …)` | [`ModelsViewModel.kt`](app/src/main/java/com/fedo/modelpulse/ui/models/ModelsViewModel.kt) | Records the provider picked in the filter as a user property, so feedback can be segmented by what the user cares about. Properties are user-level and last-write-wins; clearing the filter writes an empty value. |
+| `FedoCreateFeedbackSheet(onDismiss = …)` | [`ModelsScreen.kt`](app/src/main/java/com/fedo/modelpulse/ui/models/ModelsScreen.kt) | The contextual sheet: "Missing a model? Request it" in the no-results state and "Report a problem" in the load-error state. Both appear only when a key is configured. |
+| `Fedo.logout()` | [`SettingsViewModel.kt`](app/src/main/java/com/fedo/modelpulse/ui/settings/SettingsViewModel.kt) | Demo sign-out: clears the identity and starts a new guest. |
How each surface is meant to behave, including without a key, is written down in [specs/fedo-showcase.md](specs/fedo-showcase.md). The full SDK guide is in the [Fedo docs](https://docs.getfedo.com/guide/getting-started/).
@@ -105,7 +105,7 @@ specs/ constitution, architecture, Compose and testing patterns, decis
## Contributing
-Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines, and follow the [Code of Conduct](CODE_OF_CONDUCT.md). Report security vulnerabilities privately as described in [SECURITY.md](SECURITY.md). Bugs in the SDK itself belong in [kusa-software/fedo-sdk](https://github.com/kusa-software/fedo-sdk/issues).
+Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines, and follow the [Code of Conduct](CODE_OF_CONDUCT.md). Report security vulnerabilities privately as described in [SECURITY.md](SECURITY.md). Bugs in the SDK itself belong in [getfedo/fedo-sdk](https://github.com/getfedo/fedo-sdk/issues).
## Credits
diff --git a/app/src/main/java/com/fedo/modelpulse/ui/common/ModelPulseTopBar.kt b/app/src/main/java/com/fedo/modelpulse/ui/common/ModelPulseTopBar.kt
new file mode 100644
index 0000000..a921395
--- /dev/null
+++ b/app/src/main/java/com/fedo/modelpulse/ui/common/ModelPulseTopBar.kt
@@ -0,0 +1,41 @@
+package com.fedo.modelpulse.ui.common
+
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.LargeTopAppBar
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+
+/**
+ * @see TopBarBackButton
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ModelPulseTopBar(
+ title: String,
+ modifier: Modifier = Modifier,
+ navigationIcon: @Composable () -> Unit = {},
+ actions: @Composable RowScope.() -> Unit = {},
+ scrollBehavior: TopAppBarScrollBehavior? = null,
+) {
+ LargeTopAppBar(
+ title = {
+ Text(
+ title,
+ style = MaterialTheme.typography.headlineLarge.copy(
+ fontWeight = FontWeight.Bold
+ )
+ )
+ },
+ actions = actions,
+ navigationIcon = navigationIcon,
+ scrollBehavior = scrollBehavior,
+ modifier = modifier
+ )
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fedo/modelpulse/ui/common/TopBarBackButton.kt b/app/src/main/java/com/fedo/modelpulse/ui/common/TopBarBackButton.kt
new file mode 100644
index 0000000..7701759
--- /dev/null
+++ b/app/src/main/java/com/fedo/modelpulse/ui/common/TopBarBackButton.kt
@@ -0,0 +1,56 @@
+package com.fedo.modelpulse.ui.common
+
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.FilledIconButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme.colorScheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.fedo.modelpulse.R
+import com.fedo.modelpulse.ui.theme.ModelPulseTheme
+
+
+@Composable
+fun TopBarBackButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ FilledIconButton(
+ onClick = onClick, modifier,
+ colors = IconButtonDefaults.filledIconButtonColors(
+ containerColor = colorScheme.surfaceVariant,
+ contentColor = colorScheme.onSurfaceVariant,
+ ),
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_arrow_back),
+ contentDescription = stringResource(R.string.detail_back),
+ )
+ }
+}
+
+@Preview(name = "Light")
+@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
+private annotation class ThemePreviews
+
+@ThemePreviews
+@Composable
+private fun TopBarBackButtonPreview() {
+ ModelPulseTheme {
+ Surface {
+ Box(Modifier.padding(16.dp)) {
+ TopBarBackButton({})
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fedo/modelpulse/ui/detail/ModelDetailScreen.kt b/app/src/main/java/com/fedo/modelpulse/ui/detail/ModelDetailScreen.kt
index 7bb3183..b0d1ad5 100644
--- a/app/src/main/java/com/fedo/modelpulse/ui/detail/ModelDetailScreen.kt
+++ b/app/src/main/java/com/fedo/modelpulse/ui/detail/ModelDetailScreen.kt
@@ -23,11 +23,14 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.res.painterResource
@@ -45,6 +48,8 @@ import com.fedo.modelpulse.data.Price
import com.fedo.modelpulse.data.contextLabel
import com.fedo.modelpulse.data.perMillionLabel
import com.fedo.modelpulse.data.relativeLabel
+import com.fedo.modelpulse.ui.common.ModelPulseTopBar
+import com.fedo.modelpulse.ui.common.TopBarBackButton
import com.fedo.modelpulse.ui.mergePaddingValues
import com.fedo.modelpulse.ui.theme.ModelPulseTheme
import java.math.BigDecimal
@@ -88,26 +93,18 @@ internal fun ModelDetailScreen(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues()
) {
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
+
Scaffold(
modifier = modifier.fillMaxSize(),
topBar = {
- TopAppBar(
- title = {
- Text(
- text = (uiState as? ModelDetailUiState.Success)
- ?.model
- ?.shortName
- .orEmpty(),
- )
- },
- navigationIcon = {
- IconButton(onClick = onBackClick) {
- Icon(
- painter = painterResource(R.drawable.ic_arrow_back),
- contentDescription = stringResource(R.string.detail_back),
- )
- }
- },
+ ModelPulseTopBar(
+ title = (uiState as? ModelDetailUiState.Success)
+ ?.model
+ ?.shortName
+ .orEmpty(),
+ navigationIcon = { TopBarBackButton(onBackClick) },
+ scrollBehavior = scrollBehavior
)
},
) { innerPadding ->
@@ -129,15 +126,18 @@ internal fun ModelDetailScreen(
is ModelDetailUiState.Success -> ModelDetailContent(
model = uiState.model,
onCopyId = onCopyId,
+ scrollBehavior = scrollBehavior,
contentPadding = mergedContentPadding,
)
}
}
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ModelDetailContent(
model: AiModel,
+ scrollBehavior: TopAppBarScrollBehavior,
onCopyId: (String) -> Unit,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues()
@@ -146,6 +146,7 @@ private fun ModelDetailContent(
modifier = modifier
.fillMaxSize()
// A long description scrolls rather than clipping.
+ .nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
.padding(contentPadding)
.padding(horizontal = 16.dp, vertical = 8.dp)
diff --git a/app/src/main/java/com/fedo/modelpulse/ui/models/ModelsScreen.kt b/app/src/main/java/com/fedo/modelpulse/ui/models/ModelsScreen.kt
index 36ba09f..dddf251 100644
--- a/app/src/main/java/com/fedo/modelpulse/ui/models/ModelsScreen.kt
+++ b/app/src/main/java/com/fedo/modelpulse/ui/models/ModelsScreen.kt
@@ -62,6 +62,7 @@ import com.fedo.modelpulse.data.contextLabel
import com.fedo.modelpulse.data.perMillionLabel
import com.fedo.modelpulse.data.providerFilters
import com.fedo.modelpulse.data.relativeLabel
+import com.fedo.modelpulse.ui.common.ModelPulseTopBar
import com.fedo.modelpulse.ui.mergePaddingValues
import com.fedo.modelpulse.ui.theme.ModelPulseTheme
import com.fedo.sdk.ui.FedoCreateFeedbackSheet
@@ -134,8 +135,8 @@ internal fun ModelsScreen(
.nestedScroll(scrollBehavior.nestedScrollConnection),
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
- MediumFlexibleTopAppBar(
- title = { Text(stringResource(R.string.models_title)) },
+ ModelPulseTopBar(
+ title = stringResource(R.string.models_title),
scrollBehavior = scrollBehavior,
)
},
diff --git a/app/src/main/java/com/fedo/modelpulse/ui/roadmap/RoadmapScreen.kt b/app/src/main/java/com/fedo/modelpulse/ui/roadmap/RoadmapScreen.kt
index 910024e..7c64291 100644
--- a/app/src/main/java/com/fedo/modelpulse/ui/roadmap/RoadmapScreen.kt
+++ b/app/src/main/java/com/fedo/modelpulse/ui/roadmap/RoadmapScreen.kt
@@ -19,6 +19,8 @@ import androidx.compose.ui.unit.dp
import com.fedo.modelpulse.R
import com.fedo.modelpulse.ui.theme.ModelPulseTheme
import com.fedo.sdk.ui.FedoFeedbackScreen
+import com.fedo.sdk.ui.FedoFeedbackScreenDefaults
+import com.fedo.sdk.ui.FedoFeedbackScreenSlots
/**
* The Roadmap destination. With a key it is the SDK's board, which owns its
@@ -33,7 +35,13 @@ internal fun RoadmapScreen(
modifier: Modifier = Modifier,
) {
if (isConfigured) {
- FedoFeedbackScreen(modifier = modifier.fillMaxSize(), onDismiss = onBack)
+ FedoFeedbackScreen(
+ modifier = modifier.fillMaxSize(),
+ onDismiss = onBack,
+ slots = FedoFeedbackScreenDefaults.slots().copy(
+ backButtonIcon = null
+ )
+ )
} else {
NotConfiguredState(modifier)
}
diff --git a/app/src/main/java/com/fedo/modelpulse/ui/settings/SettingsScreen.kt b/app/src/main/java/com/fedo/modelpulse/ui/settings/SettingsScreen.kt
index 77f1972..10e1c6c 100644
--- a/app/src/main/java/com/fedo/modelpulse/ui/settings/SettingsScreen.kt
+++ b/app/src/main/java/com/fedo/modelpulse/ui/settings/SettingsScreen.kt
@@ -11,15 +11,18 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.tooling.preview.Preview
@@ -28,6 +31,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.fedo.modelpulse.R
+import com.fedo.modelpulse.ui.common.ModelPulseTopBar
import com.fedo.modelpulse.ui.mergePaddingValues
import com.fedo.modelpulse.ui.theme.ModelPulseTheme
import org.koin.androidx.compose.koinViewModel
@@ -51,6 +55,7 @@ internal fun SettingsRoute(
)
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun SettingsScreen(
uiState: SettingsUiState,
@@ -61,25 +66,28 @@ internal fun SettingsScreen(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues()
) {
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
+
Scaffold(
modifier = modifier,
+ topBar = {
+ ModelPulseTopBar(
+ title = stringResource(R.string.settings_title),
+ scrollBehavior = scrollBehavior
+ )
+ }
) { innerPadding ->
val mergedContentPadding = mergePaddingValues(innerPadding, contentPadding)
Column(
-
modifier = Modifier
.fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
.padding(mergedContentPadding)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
- Text(
- text = stringResource(R.string.settings_title),
- style = MaterialTheme.typography.headlineSmall,
- )
-
SdkStatusCard(isConfigured = uiState.isConfigured)
DemoAccountCard(
diff --git a/specs/compose-pattern.md b/specs/compose-pattern.md
index 8ce1832..ff4e75d 100644
--- a/specs/compose-pattern.md
+++ b/specs/compose-pattern.md
@@ -70,6 +70,33 @@ internal fun ModelsScreen(
- Previews work without Koin
- Navigation stays in one place
+## Top Bar
+```kotlin
+
+@Composable
+internal fun ModelsScreen(
+ // ...
+ onBack: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
+ Scaffold(
+ modifier = modifier.fillMaxSize(),
+ topBar = {
+ ModelPulseTopBar(
+ title = stringResource(R.string.something),
+ navigationIcon = { TopBarBackButton(onBack) },
+ )
+ },
+ ) { innerPadding ->
+ // screen content
+ }
+}
+```
+Always use `ModelPulseTopBar` for top bar and `TopBarBackButton` for top bar navigation icon
+
+##
+
## State Management
### Sealed Interface for UI State
diff --git a/specs/decisions/0001-library-versions.md b/specs/decisions/0001-library-versions.md
index a01a8a5..1041875 100644
--- a/specs/decisions/0001-library-versions.md
+++ b/specs/decisions/0001-library-versions.md
@@ -73,7 +73,7 @@ Rejected:
`MockEngine` are present in `classes7.dex`/`classes8.dex` of a debug build.
The debug APK is ~46 MB.
-This is a packaging bug in the SDK (`kusa-software/fedo-sdk`), not something
+This is a packaging bug in the SDK (`getfedo/fedo-sdk`), not something
this repo can fix properly. Workaround if it blocks a release:
```kotlin