From 22863bfcd65751a20b64fbc00c18fe6fc25f6010 Mon Sep 17 00:00:00 2001 From: Achmad Setyabudi Susilo Date: Sun, 28 Jun 2026 18:46:17 +0700 Subject: [PATCH] fix(#7): emit snackbar as resource id, resolve with stringResource in UI Drop the Context injection from the ScreenModel. Emit a sealed ImportSnackbarMessage carrying either a @StringRes id or a dynamic text (for the error.message case where the bank importer surfaces a useful 'BRI import not yet implemented' string). The screen resolves resource ids via stringResource() in Composable scope and shows the snackbar; the original error.message info is preserved via the Dynamic variant instead of being dropped. --- .../ImportBankStatementScreen.kt | 15 ++++++++++++++- .../ImportBankStatementScreenModel.kt | 19 ++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/dev/achmad/ledgerr/ui/screens/import_bank_statement/ImportBankStatementScreen.kt b/app/src/main/java/dev/achmad/ledgerr/ui/screens/import_bank_statement/ImportBankStatementScreen.kt index 984d356..e1e3eda 100644 --- a/app/src/main/java/dev/achmad/ledgerr/ui/screens/import_bank_statement/ImportBankStatementScreen.kt +++ b/app/src/main/java/dev/achmad/ledgerr/ui/screens/import_bank_statement/ImportBankStatementScreen.kt @@ -86,9 +86,22 @@ object ImportBankStatementScreen : Screen { pendingImporter = null } + val importFailedText = stringResource(R.string.import_failed) + val importNoTransactionsText = stringResource(R.string.import_no_transactions) + val importNoItemsSelectedText = stringResource(R.string.import_no_items_selected) + LaunchedEffect(Unit) { screenModel.snackbar.collect { message -> - snackbarHostState.showSnackbar(message) + val text = when (message) { + is ImportSnackbarMessage.Resource -> when (message.id) { + R.string.import_failed -> importFailedText + R.string.import_no_transactions -> importNoTransactionsText + R.string.import_no_items_selected -> importNoItemsSelectedText + else -> null + } + is ImportSnackbarMessage.Dynamic -> message.text + } + text?.let { snackbarHostState.showSnackbar(it) } } } diff --git a/app/src/main/java/dev/achmad/ledgerr/ui/screens/import_bank_statement/ImportBankStatementScreenModel.kt b/app/src/main/java/dev/achmad/ledgerr/ui/screens/import_bank_statement/ImportBankStatementScreenModel.kt index feff121..d39d8e9 100644 --- a/app/src/main/java/dev/achmad/ledgerr/ui/screens/import_bank_statement/ImportBankStatementScreenModel.kt +++ b/app/src/main/java/dev/achmad/ledgerr/ui/screens/import_bank_statement/ImportBankStatementScreenModel.kt @@ -1,7 +1,7 @@ package dev.achmad.ledgerr.ui.screens.import_bank_statement -import android.content.Context import android.net.Uri +import androidx.annotation.StringRes import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.navigator.Navigator @@ -36,18 +36,22 @@ sealed interface ImportState { ) : ImportState } +sealed interface ImportSnackbarMessage { + data class Resource(@StringRes val id: Int) : ImportSnackbarMessage + data class Dynamic(val text: String) : ImportSnackbarMessage +} + class ImportBankStatementScreenModel( private val importers: List = inject(), private val getCategories: GetCategories = inject(), private val insertExpenses: InsertExpenses = inject(), - private val context: Context = inject(), ) : ScreenModel { private val _state = MutableStateFlow(ImportState.BankPicker(importers)) val state: StateFlow = _state.asStateFlow() - private val _snackbar = MutableSharedFlow(extraBufferCapacity = 1) - val snackbar: SharedFlow = _snackbar.asSharedFlow() + private val _snackbar = MutableSharedFlow(extraBufferCapacity = 1) + val snackbar: SharedFlow = _snackbar.asSharedFlow() val categories: StateFlow> = getCategories.subscribeAll() .flowOn(Dispatchers.IO) @@ -69,7 +73,7 @@ class ImportBankStatementScreenModel( .onSuccess { rows -> if (rows.isEmpty()) { _state.value = ImportState.BankPicker(importers) - _snackbar.tryEmit(context.getString(R.string.import_no_transactions)) + _snackbar.tryEmit(ImportSnackbarMessage.Resource(R.string.import_no_transactions)) } else { _state.value = ImportState.Confirmation(importer.bankName, rows) } @@ -77,7 +81,8 @@ class ImportBankStatementScreenModel( .onFailure { error -> _state.value = ImportState.BankPicker(importers) val message = error.message?.takeIf { it.isNotBlank() } - ?: context.getString(R.string.import_failed) + ?.let { ImportSnackbarMessage.Dynamic(it) } + ?: ImportSnackbarMessage.Resource(R.string.import_failed) _snackbar.tryEmit(message) } } @@ -115,7 +120,7 @@ class ImportBankStatementScreenModel( val confirmation = _state.value as? ImportState.Confirmation ?: return@launch val selected = confirmation.rows.filter { it.isSelected } if (selected.isEmpty()) { - _snackbar.tryEmit(context.getString(R.string.import_no_items_selected)) + _snackbar.tryEmit(ImportSnackbarMessage.Resource(R.string.import_no_items_selected)) return@launch } withContext(Dispatchers.IO) {