From 8df85e3a3e4b945a6b68b259c553444ab14bb0af Mon Sep 17 00:00:00 2001 From: GSmithBoltz <251984476+GSmithBoltz@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:59:26 +0100 Subject: [PATCH] Add favorite models and pricing to model selector Co-Authored-By: Claude Fable 5 --- data/build.gradle.kts | 1 + .../java/com/beradeep/aiyo/data/Mappers.kt | 22 ++++-- .../aiyo/data/local/kv/entity/ModelEntity.kt | 4 +- .../data/remote/OpenRouterModelsResponse.kt | 21 +++++ .../data/repository/ModelRepositoryImpl.kt | 42 ++++++++-- .../com/beradeep/aiyo/domain/model/Model.kt | 4 +- .../aiyo/domain/repository/ModelRepository.kt | 5 ++ gradle/libs.versions.toml | 1 + .../aiyo/ui/screens/chat/ChatScreen.kt | 4 + .../aiyo/ui/screens/chat/ChatUiEvent.kt | 2 + .../aiyo/ui/screens/chat/ChatUiState.kt | 2 + .../aiyo/ui/screens/chat/ChatViewModel.kt | 11 +++ .../screens/components/ModelSelectionSheet.kt | 76 +++++++++++++++++-- .../ui/screens/settings/SettingsScreen.kt | 4 + .../ui/screens/settings/SettingsUiEvent.kt | 2 + .../ui/screens/settings/SettingsUiState.kt | 2 + .../ui/screens/settings/SettingsViewModel.kt | 11 +++ 17 files changed, 191 insertions(+), 23 deletions(-) create mode 100644 data/src/main/java/com/beradeep/aiyo/data/remote/OpenRouterModelsResponse.kt diff --git a/data/build.gradle.kts b/data/build.gradle.kts index f41a0e0..0a70bd1 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -51,6 +51,7 @@ dependencies { // Api Client implementation(platform(libs.openai.client.bom)) implementation(libs.openai.client) + implementation(libs.ktor.client.core) runtimeOnly(libs.ktor.client.okhttp) // Room diff --git a/data/src/main/java/com/beradeep/aiyo/data/Mappers.kt b/data/src/main/java/com/beradeep/aiyo/data/Mappers.kt index ee6e6cd..a5c712d 100644 --- a/data/src/main/java/com/beradeep/aiyo/data/Mappers.kt +++ b/data/src/main/java/com/beradeep/aiyo/data/Mappers.kt @@ -5,13 +5,13 @@ import com.aallam.openai.api.chat.ChatRole import com.beradeep.aiyo.data.local.db.entity.ConversationEntity import com.beradeep.aiyo.data.local.db.entity.MessageEntity import com.beradeep.aiyo.data.local.kv.entity.ModelEntity +import com.beradeep.aiyo.data.remote.OpenRouterModelDto import com.beradeep.aiyo.domain.model.Conversation import com.beradeep.aiyo.domain.model.Message import com.beradeep.aiyo.domain.model.Model import com.beradeep.aiyo.domain.model.Role import java.util.Date import java.util.UUID -import com.aallam.openai.api.model.Model as OpenAIModel fun ChatMessage.toChatMessage(): Message = Message( role = this.role.toRole(), @@ -32,12 +32,16 @@ fun Role.toChatRole(): ChatRole = when (this) { Role.System -> ChatRole.System } -fun OpenAIModel.toModel(): Model = Model( - id = this.id.id, - createdAt = this.created?.let { Date(it) }, - ownedBy = this.ownedBy +fun OpenRouterModelDto.toModel(): Model = Model( + id = id, + createdAt = created?.let { Date(it * 1000) }, + inputPricePerMillion = pricing?.prompt?.toPricePerMillion(), + outputPricePerMillion = pricing?.completion?.toPricePerMillion() ) +private fun String.toPricePerMillion(): Double? = + toDoubleOrNull()?.takeIf { it >= 0 }?.times(1_000_000) + fun ConversationEntity.toDomain(): Conversation = Conversation( id = id, title = title, @@ -73,11 +77,15 @@ fun Message.toEntity(conversationId: UUID): MessageEntity = MessageEntity( fun ModelEntity.toDomain(): Model = Model( id = id, createdAt = createdAt, - ownedBy = ownedBy + ownedBy = ownedBy, + inputPricePerMillion = inputPricePerMillion, + outputPricePerMillion = outputPricePerMillion ) fun Model.toEntity(): ModelEntity = ModelEntity( id = id, createdAt = createdAt, - ownedBy = ownedBy + ownedBy = ownedBy, + inputPricePerMillion = inputPricePerMillion, + outputPricePerMillion = outputPricePerMillion ) diff --git a/data/src/main/java/com/beradeep/aiyo/data/local/kv/entity/ModelEntity.kt b/data/src/main/java/com/beradeep/aiyo/data/local/kv/entity/ModelEntity.kt index f191a56..8236a70 100644 --- a/data/src/main/java/com/beradeep/aiyo/data/local/kv/entity/ModelEntity.kt +++ b/data/src/main/java/com/beradeep/aiyo/data/local/kv/entity/ModelEntity.kt @@ -8,5 +8,7 @@ data class ModelEntity( val id: String, val ownedBy: String? = null, @Serializable(with = DateSerializer::class) - val createdAt: java.util.Date? = null + val createdAt: java.util.Date? = null, + val inputPricePerMillion: Double? = null, + val outputPricePerMillion: Double? = null ) diff --git a/data/src/main/java/com/beradeep/aiyo/data/remote/OpenRouterModelsResponse.kt b/data/src/main/java/com/beradeep/aiyo/data/remote/OpenRouterModelsResponse.kt new file mode 100644 index 0000000..a5b1895 --- /dev/null +++ b/data/src/main/java/com/beradeep/aiyo/data/remote/OpenRouterModelsResponse.kt @@ -0,0 +1,21 @@ +package com.beradeep.aiyo.data.remote + +import kotlinx.serialization.Serializable + +@Serializable +data class OpenRouterModelsResponse( + val data: List +) + +@Serializable +data class OpenRouterModelDto( + val id: String, + val created: Long? = null, + val pricing: OpenRouterPricingDto? = null +) + +@Serializable +data class OpenRouterPricingDto( + val prompt: String? = null, + val completion: String? = null +) diff --git a/data/src/main/java/com/beradeep/aiyo/data/repository/ModelRepositoryImpl.kt b/data/src/main/java/com/beradeep/aiyo/data/repository/ModelRepositoryImpl.kt index 7cf83f8..03c4259 100644 --- a/data/src/main/java/com/beradeep/aiyo/data/repository/ModelRepositoryImpl.kt +++ b/data/src/main/java/com/beradeep/aiyo/data/repository/ModelRepositoryImpl.kt @@ -4,23 +4,32 @@ import android.content.Context import com.beradeep.aiyo.data.local.kv.KVStore import com.beradeep.aiyo.data.local.kv.entity.ModelEntity import com.beradeep.aiyo.data.remote.DataApiClient +import com.beradeep.aiyo.data.remote.OpenRouterModelDto +import com.beradeep.aiyo.data.remote.OpenRouterModelsResponse import com.beradeep.aiyo.data.toDomain import com.beradeep.aiyo.data.toEntity import com.beradeep.aiyo.data.toModel import com.beradeep.aiyo.domain.model.Model import com.beradeep.aiyo.domain.repository.ModelRepository +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.serialization.json.Json class ModelRepositoryImpl(context: Context, val apiClient: DataApiClient) : ModelRepository { private val kvStore by lazy { KVStore.getInstance(context) } private val json = Json { ignoreUnknownKeys = true } - private val openAi get() = apiClient.openAI + private val httpClient by lazy { HttpClient() } + private val favoriteModelIdsFlow by lazy { MutableStateFlow(loadFavoriteModelIds()) } - override suspend fun getModels(apiKey: String?): Result> = openAi?.let { client -> - safeCall { - client.models().map(com.aallam.openai.api.model.Model::toModel) - } - } ?: Result.failure(IllegalStateException("OpenAI client not initialized")) + override suspend fun getModels(apiKey: String?): Result> = safeCall { + val body = httpClient.get(MODELS_URL).bodyAsText() + json.decodeFromString(body) + .data + .map(OpenRouterModelDto::toModel) + } override fun getDefaultModel(): Model { val modelJson = kvStore.getString(KEY_DEFAULT_MODEL) @@ -38,9 +47,30 @@ class ModelRepositoryImpl(context: Context, val apiClient: DataApiClient) : Mode kvStore.putString(KEY_DEFAULT_MODEL, modelJson) } + override fun getFavoriteModelIdsFlow(): Flow> = favoriteModelIdsFlow + + override fun toggleFavoriteModel(model: Model) { + val updated = favoriteModelIdsFlow.value.toMutableSet().apply { + if (!add(model.id)) remove(model.id) + } + kvStore.putString(KEY_FAVORITE_MODELS, json.encodeToString(updated.toSet())) + favoriteModelIdsFlow.value = updated + } + + private fun loadFavoriteModelIds(): Set = + kvStore.getString(KEY_FAVORITE_MODELS)?.let { + try { + json.decodeFromString>(it) + } catch (_: Throwable) { + emptySet() + } + } ?: emptySet() + private suspend fun safeCall(call: suspend () -> T): Result = runCatching { call() } companion object { private const val KEY_DEFAULT_MODEL = "default_model" + private const val KEY_FAVORITE_MODELS = "favorite_models" + private const val MODELS_URL = DataApiClient.BASE_URL + "models" } } diff --git a/domain/src/main/java/com/beradeep/aiyo/domain/model/Model.kt b/domain/src/main/java/com/beradeep/aiyo/domain/model/Model.kt index af6ec9a..f540d91 100644 --- a/domain/src/main/java/com/beradeep/aiyo/domain/model/Model.kt +++ b/domain/src/main/java/com/beradeep/aiyo/domain/model/Model.kt @@ -5,7 +5,9 @@ import java.util.Date data class Model( val id: String, val ownedBy: String? = null, - val createdAt: Date? = null + val createdAt: Date? = null, + val inputPricePerMillion: Double? = null, + val outputPricePerMillion: Double? = null ) { override fun equals(other: Any?): Boolean { return this.id == (other as Model).id diff --git a/domain/src/main/java/com/beradeep/aiyo/domain/repository/ModelRepository.kt b/domain/src/main/java/com/beradeep/aiyo/domain/repository/ModelRepository.kt index 374d6db..6268d88 100644 --- a/domain/src/main/java/com/beradeep/aiyo/domain/repository/ModelRepository.kt +++ b/domain/src/main/java/com/beradeep/aiyo/domain/repository/ModelRepository.kt @@ -1,6 +1,7 @@ package com.beradeep.aiyo.domain.repository import com.beradeep.aiyo.domain.model.Model +import kotlinx.coroutines.flow.Flow interface ModelRepository { @@ -9,4 +10,8 @@ interface ModelRepository { fun getDefaultModel(): Model fun setDefaultModel(model: Model) + + fun getFavoriteModelIdsFlow(): Flow> + + fun toggleFavoriteModel(model: Model) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8993f7c..ccf9b4b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,6 +43,7 @@ androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-te androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" } androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } +ktor-client-core = { module = "io.ktor:ktor-client-core" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp" } lumo-composables = { group = "com.nomanr", name = "composables", version.ref = "lumo-composables" } mikepenz-multiplatform-markdown-renderer-m3 = { module = "com.mikepenz:multiplatform-markdown-renderer-m3", version.ref = "multiplatformMarkdownRendererAndroid" } diff --git a/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatScreen.kt b/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatScreen.kt index 4684f0a..46ccb89 100644 --- a/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatScreen.kt +++ b/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatScreen.kt @@ -398,10 +398,14 @@ private fun ChatScreen( isVisible = showModelSheet, models = uiState.models, selectedModel = uiState.selectedModel, + favoriteModelIds = uiState.favoriteModelIds, onModelSelected = { model -> onUiEvent(ChatUiEvent.OnModelSelected(model)) showModelSheet = false }, + onToggleFavoriteModel = { model -> + onUiEvent(ChatUiEvent.OnToggleFavoriteModel(model)) + }, isFetchingModels = uiState.isFetchingModels, fetchModels = { onUiEvent(ChatUiEvent.OnFetchModels) }, onDismiss = { showModelSheet = false } diff --git a/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatUiEvent.kt b/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatUiEvent.kt index d76ce45..f50296b 100644 --- a/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatUiEvent.kt +++ b/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatUiEvent.kt @@ -17,6 +17,8 @@ sealed interface ChatUiEvent { data class OnModelSelected(val model: Model) : ChatUiEvent + data class OnToggleFavoriteModel(val model: Model) : ChatUiEvent + data object OnFetchModels : ChatUiEvent data object OnNewChat : ChatUiEvent diff --git a/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatUiState.kt b/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatUiState.kt index c27f966..92f5dbe 100644 --- a/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatUiState.kt +++ b/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatUiState.kt @@ -11,6 +11,7 @@ import com.mikepenz.markdown.model.State data class ChatUiState( val models: List, val selectedModel: Model, + val favoriteModelIds: Set, val selectedConversation: Conversation?, val streamingResponse: String?, val isLoadingResponse: Boolean, @@ -34,6 +35,7 @@ data class ChatUiState( ChatUiState( models = listOf(defaultModel), selectedModel = defaultModel, + favoriteModelIds = emptySet(), selectedConversation = null, streamingResponse = null, isLoadingResponse = false, diff --git a/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatViewModel.kt b/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatViewModel.kt index 47a7b2b..7642284 100644 --- a/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatViewModel.kt +++ b/ui/src/main/java/com/beradeep/aiyo/ui/screens/chat/ChatViewModel.kt @@ -65,6 +65,7 @@ open class ChatViewModel( }.onStart { loadApiKey() observeFontSizes() + observeFavoriteModels() loadDefaultModel() fetchModels() }.stateIn( @@ -86,6 +87,8 @@ open class ChatViewModel( is ChatUiEvent.OnCancelPreloadMarkdownJobs -> cancelExistingPreloadJobs() is ChatUiEvent.OnFetchModels -> viewModelScope.launch { fetchModels() } is ChatUiEvent.OnModelSelected -> viewModelScope.launch { selectModel(chatUiEvent.model) } + is ChatUiEvent.OnToggleFavoriteModel -> + modelRepository.toggleFavoriteModel(chatUiEvent.model) is ChatUiEvent.OnConversationSelected -> viewModelScope.launch { selectConversation(chatUiEvent.conversation) } @@ -154,6 +157,14 @@ open class ChatViewModel( } } + private fun observeFavoriteModels() { + viewModelScope.launch { + modelRepository.getFavoriteModelIdsFlow().collect { favoriteModelIds -> + _uiState.update { it.copy(favoriteModelIds = favoriteModelIds) } + } + } + } + private fun loadDefaultModel() { viewModelScope.launch { defaultModel = modelRepository.getDefaultModel() diff --git a/ui/src/main/java/com/beradeep/aiyo/ui/screens/components/ModelSelectionSheet.kt b/ui/src/main/java/com/beradeep/aiyo/ui/screens/components/ModelSelectionSheet.kt index 16444ab..39b9c1f 100644 --- a/ui/src/main/java/com/beradeep/aiyo/ui/screens/components/ModelSelectionSheet.kt +++ b/ui/src/main/java/com/beradeep/aiyo/ui/screens/components/ModelSelectionSheet.kt @@ -15,6 +15,8 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.outlined.StarOutline import androidx.compose.material.icons.rounded.Search import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -23,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.StrokeCap @@ -31,11 +34,14 @@ import com.beradeep.aiyo.domain.model.Model import com.beradeep.aiyo.ui.AiyoTheme import com.beradeep.aiyo.ui.LocalTypography import com.beradeep.aiyo.ui.basics.components.Icon +import com.beradeep.aiyo.ui.basics.components.IconButton +import com.beradeep.aiyo.ui.basics.components.IconButtonVariant import com.beradeep.aiyo.ui.basics.components.ModalBottomSheet import com.beradeep.aiyo.ui.basics.components.Surface import com.beradeep.aiyo.ui.basics.components.Text import com.beradeep.aiyo.ui.basics.components.progressindicators.LinearProgressIndicator import com.beradeep.aiyo.ui.basics.components.textfield.TextField +import java.util.Locale @Composable fun ModelSelectionSheet( @@ -45,7 +51,9 @@ fun ModelSelectionSheet( fetchModels: () -> Unit, models: List, selectedModel: Model, + favoriteModelIds: Set, onModelSelected: (Model) -> Unit, + onToggleFavoriteModel: (Model) -> Unit, onDismiss: () -> Unit ) { ModalBottomSheet( @@ -54,11 +62,13 @@ fun ModelSelectionSheet( sheetGesturesEnabled = false ) { var searchText by remember { mutableStateOf("") } - val filteredModels by remember { + val filteredModels by remember(models, favoriteModelIds) { derivedStateOf { - models.filter { - it.id.contains(searchText, ignoreCase = true) - } + models + .filter { + it.id.contains(searchText, ignoreCase = true) + } + .sortedByDescending { it.id in favoriteModelIds } } } Column( @@ -94,6 +104,7 @@ fun ModelSelectionSheet( key = { it.id } ) { model -> val isSelected = model.id == selectedModel.id + val isFavorite = model.id in favoriteModelIds Surface( color = if (isSelected) AiyoTheme.colors.surface else AiyoTheme.colors.background ) { @@ -118,17 +129,66 @@ fun ModelSelectionSheet( text = model.ownedBy ?: model.id.substringBefore('/'), style = LocalTypography.current.h4 ) - Text( + Row( modifier = Modifier.padding(top = 2.dp), - text = model.id.substringAfter('/'), - style = LocalTypography.current.body2 - ) + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.weight(1f, fill = false), + text = model.id.substringAfter('/'), + style = LocalTypography.current.body2 + ) + model.pricingLabel()?.let { pricing -> + Text( + modifier = Modifier.padding(start = 8.dp), + text = pricing, + style = LocalTypography.current.label3, + color = AiyoTheme.colors.tertiary + ) + } + } Spacer(modifier = Modifier.height(8.dp)) } + IconButton( + modifier = Modifier.align(Alignment.CenterVertically), + variant = IconButtonVariant.PrimaryGhost, + onClick = { onToggleFavoriteModel(model) } + ) { + Icon( + imageVector = if (isFavorite) { + Icons.Filled.Star + } else { + Icons.Outlined.StarOutline + }, + contentDescription = if (isFavorite) "Unfavorite" else "Favorite", + tint = if (isFavorite) { + AiyoTheme.colors.primary + } else { + AiyoTheme.colors.tertiary + } + ) + } } } } } } } +} + +private fun Model.pricingLabel(): String? { + val input = inputPricePerMillion ?: return null + val output = outputPricePerMillion ?: return null + if (input == 0.0 && output == 0.0) return "Free" + return "${formatPricePerMillion(input)} in | ${formatPricePerMillion(output)} out" +} + +private fun formatPricePerMillion(price: Double): String { + val formatted = when { + price == 0.0 -> "0" + price < 0.01 -> String.format(Locale.US, "%.4f", price) + price < 100 -> String.format(Locale.US, "%.2f", price) + else -> String.format(Locale.US, "%.0f", price) + } + return "$$formatted/M" } \ No newline at end of file diff --git a/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsScreen.kt b/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsScreen.kt index 7ae6bc2..5507b3e 100644 --- a/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsScreen.kt +++ b/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsScreen.kt @@ -197,9 +197,13 @@ fun SettingsScreen( }, models = uiState.models, selectedModel = uiState.selectedModel, + favoriteModelIds = uiState.favoriteModelIds, onModelSelected = { model -> viewModel.onUiEvent(SettingsUiEvent.OnModelSelected(model)) }, + onToggleFavoriteModel = { model -> + viewModel.onUiEvent(SettingsUiEvent.OnToggleFavoriteModel(model)) + }, onDismiss = { viewModel.onUiEvent(SettingsUiEvent.OnDismissModelSelectionSheet) } diff --git a/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsUiEvent.kt b/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsUiEvent.kt index 00f0251..2ad85f6 100644 --- a/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsUiEvent.kt +++ b/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsUiEvent.kt @@ -5,6 +5,8 @@ import com.beradeep.aiyo.domain.model.ThemeType sealed class SettingsUiEvent { data class OnSetApiKey(val apiKey: String) : SettingsUiEvent() data class OnModelSelected(val model: com.beradeep.aiyo.domain.model.Model) : SettingsUiEvent() + data class OnToggleFavoriteModel(val model: com.beradeep.aiyo.domain.model.Model) : + SettingsUiEvent() object OnFetchModels : SettingsUiEvent() object OnShowModelSelectionSheet : SettingsUiEvent() object OnDismissModelSelectionSheet : SettingsUiEvent() diff --git a/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsUiState.kt b/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsUiState.kt index 0b9aa90..8ce73ec 100644 --- a/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsUiState.kt +++ b/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsUiState.kt @@ -8,6 +8,7 @@ data class SettingsUiState( val apiKey: String?, val models: List, val selectedModel: Model, + val favoriteModelIds: Set, val isFetchingModels: Boolean, val showModelSelectionSheet: Boolean, val themeType: ThemeType, @@ -20,6 +21,7 @@ data class SettingsUiState( apiKey = null, models = listOf(defaultModel), selectedModel = defaultModel, + favoriteModelIds = emptySet(), isFetchingModels = false, showModelSelectionSheet = false, themeType = ThemeType.System, diff --git a/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsViewModel.kt b/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsViewModel.kt index 2a20e44..526adf7 100644 --- a/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsViewModel.kt +++ b/ui/src/main/java/com/beradeep/aiyo/ui/screens/settings/SettingsViewModel.kt @@ -37,6 +37,8 @@ open class SettingsViewModel( when (settingsUiEvent) { is SettingsUiEvent.OnSetApiKey -> setApiKey(settingsUiEvent.apiKey) is SettingsUiEvent.OnModelSelected -> selectModel(settingsUiEvent.model) + is SettingsUiEvent.OnToggleFavoriteModel -> + modelRepository.toggleFavoriteModel(settingsUiEvent.model) SettingsUiEvent.OnFetchModels -> fetchModels() SettingsUiEvent.OnShowModelSelectionSheet -> showModelSelectionSheet() SettingsUiEvent.OnDismissModelSelectionSheet -> dismissModelSelectionSheet() @@ -51,9 +53,18 @@ open class SettingsViewModel( loadFontSizes() loadApiKey() loadDefaultModel() + observeFavoriteModels() fetchModels() } + private fun observeFavoriteModels() { + viewModelScope.launch { + modelRepository.getFavoriteModelIdsFlow().collect { favoriteModelIds -> + _uiState.update { it.copy(favoriteModelIds = favoriteModelIds) } + } + } + } + private fun loadThemeType() { viewModelScope.launch { _uiState.update { it.copy(themeType = settingsRepository.getThemeType()) }