Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -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/
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| <img src="docs/images/models-list.png" width="180" alt="Models list, newest first, with search and provider filter"> | <img src="docs/images/model-detail.png" width="180" alt="Model detail with prices, context window and copyable id"> | <img src="docs/images/roadmap.png" width="180" alt="The Fedo feedback board in the Roadmap destination"> | <img src="docs/images/settings.png" width="180" alt="Settings showing SDK status and the demo account"> |

<details>
<summary>Light theme</summary>

| Models | Model detail | Roadmap | Settings |
| --- | --- | --- | --- |
| Models | Model detail | Roadmap | Settings |
|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
| <img src="docs/images/models-list-light.png" width="180" alt="Models list in light theme"> | <img src="docs/images/model-detail-light.png" width="180" alt="Model detail in light theme"> | <img src="docs/images/roadmap-light.png" width="180" alt="The Fedo feedback board in light theme"> | <img src="docs/images/settings-light.png" width="180" alt="Settings in light theme"> |

</details>
Expand Down Expand Up @@ -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(…)`<br>`Fedo.setUserDisplayName(…)`<br>`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/).

Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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
)

}
Original file line number Diff line number Diff line change
@@ -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({})
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 ->
Expand All @@ -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()
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
Loading
Loading