From aad04d226dd5d7dd500398d96794592fa7aa4d7a Mon Sep 17 00:00:00 2001 From: "mohammad.hossain" Date: Fri, 5 Jul 2024 12:39:02 +0200 Subject: [PATCH 1/4] added snackbar support --- .../zeapp/zeui/ZeNameEditorDialog.kt | 43 +++++++++++++++ .../zeapp/zeui/snackbar/SnackBarData.kt | 41 ++++++++++++++ .../zeapp/zeui/zehome/SelectedEditor.kt | 4 ++ .../berlindroid/zeapp/zeui/zehome/ZePages.kt | 4 +- .../berlindroid/zeapp/zeui/zehome/ZeScreen.kt | 53 +++++++++++++++++++ .../zeapp/zevm/ZeBadgeViewModel.kt | 23 +++++++- 6 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/snackbar/SnackBarData.kt diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt index 842ca435..808d10ca 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt @@ -15,8 +15,10 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -30,9 +32,12 @@ import de.berlindroid.zeapp.R import de.berlindroid.zeapp.zebits.composableToBitmap import de.berlindroid.zeapp.zebits.isBinary import de.berlindroid.zeapp.zemodels.ZeConfiguration +import de.berlindroid.zeapp.zeui.snackbar.SnackBarData import de.berlindroid.zeapp.zeui.zepages.NamePage import de.berlindroid.zeapp.zeui.zetheme.ZeBlack import de.berlindroid.zeapp.zeui.zetheme.ZeWhite +import de.berlindroid.zeapp.zevm.ZeBadgeUiAction +import kotlinx.coroutines.flow.SharedFlow const val MaxCharacters: Int = 20 @@ -50,6 +55,8 @@ fun NameEditorDialog( dismissed: () -> Unit = {}, accepted: (config: ZeConfiguration.Name) -> Unit, updateMessage: (String) -> Unit, + onShowSnackBar: (SnackBarData) -> Unit, + uiAction: SharedFlow, ) { val activity = LocalContext.current as Activity @@ -149,6 +156,11 @@ fun NameEditorDialog( } }, ) + + ZeNameEditorDialogAction( + onShowSnackBar = onShowSnackBar, + uiAction = uiAction, + ) } @Composable @@ -162,3 +174,34 @@ fun ClearIcon(isEmpty: Boolean, modifier: Modifier = Modifier, onClick: () -> Un ) } } + +@Composable +fun ZeNameEditorDialogAction( + onShowSnackBar: (SnackBarData) -> Unit, + uiAction: SharedFlow, +) { + val resources = LocalContext.current.resources + LaunchedEffect(Unit) { + uiAction.collect { action -> + when (action) { + is ZeBadgeUiAction.ShowLocalisedMessageInSnackBar -> { + onShowSnackBar( + SnackBarData.SnackBarWithMessage( + message = resources.getString(action.messageResId), + snackbarDuration = SnackbarDuration.Long, + ) + ) + } + + is ZeBadgeUiAction.ShowMessageInSnackBar -> { + onShowSnackBar( + SnackBarData.SnackBarWithMessage( + message = action.message, + snackbarDuration = SnackbarDuration.Long, + ) + ) + } + } + } + } +} diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/snackbar/SnackBarData.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/snackbar/SnackBarData.kt new file mode 100644 index 00000000..e54047be --- /dev/null +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/snackbar/SnackBarData.kt @@ -0,0 +1,41 @@ +package de.berlindroid.zeapp.zeui.snackbar + +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult + +sealed class SnackBarData { + + data class SnackBarWithMessage( + val message: String, + val snackbarDuration: SnackbarDuration, + val onDismissed: (() -> Unit)? = null, + ) : SnackBarData() + + data class SnackBarWithAction( + val message: String, + val actionText: String, + val onActionClicked: () -> Unit, + val onDismissed: (() -> Unit)? = null, + val snackbarDuration: SnackbarDuration, + ) : SnackBarData() + +} + + +suspend fun SnackbarHostState.showSnackbarWithMessage(snackBarWithMessage: SnackBarData.SnackBarWithMessage): SnackbarResult { + return showSnackbar( + message = snackBarWithMessage.message, + withDismissAction = true, + duration = snackBarWithMessage.snackbarDuration, + ) +} + +suspend fun SnackbarHostState.showSnackbarWithAction(snackBarWithMessage: SnackBarData.SnackBarWithAction): SnackbarResult { + return showSnackbar( + message = snackBarWithMessage.message, + withDismissAction = true, + actionLabel = snackBarWithMessage.actionText, + duration = snackBarWithMessage.snackbarDuration, + ) +} diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/SelectedEditor.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/SelectedEditor.kt index 67a027c8..4eca9416 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/SelectedEditor.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/SelectedEditor.kt @@ -16,6 +16,7 @@ import de.berlindroid.zeapp.zeui.RandomQuotesEditorDialog import de.berlindroid.zeapp.zeui.WeatherEditorDialog import de.berlindroid.zeapp.zeui.ZeCameraEditor import de.berlindroid.zeapp.zeui.ZeImageDrawEditorDialog +import de.berlindroid.zeapp.zeui.snackbar.SnackBarData import de.berlindroid.zeapp.zevm.ZeBadgeViewModel import timber.log.Timber @@ -23,6 +24,7 @@ import timber.log.Timber internal fun SelectedEditor( editor: ZeEditor, vm: ZeBadgeViewModel, + onShowSnackBar: (SnackBarData) -> Unit, ) { if (editor.slot !in listOf( ZeSlot.Name, @@ -44,6 +46,8 @@ internal fun SelectedEditor( dismissed = { vm.slotConfigured(editor.slot, null) }, accepted = { newConfig -> vm.slotConfigured(editor.slot, newConfig) }, updateMessage = vm::showMessage, + onShowSnackBar = onShowSnackBar, + uiAction = vm.uiAction, ) is ZeConfiguration.Picture -> PictureEditorDialog( diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZePages.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZePages.kt index 04c4ccee..e65d77da 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZePages.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZePages.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.unit.LayoutDirection import de.berlindroid.zeapp.ZeDimen +import de.berlindroid.zeapp.zeui.snackbar.SnackBarData import de.berlindroid.zeapp.zevm.ZeBadgeViewModel @OptIn(ExperimentalFoundationApi::class) @@ -35,6 +36,7 @@ internal fun ZePages( paddingValues: PaddingValues, vm: ZeBadgeViewModel, lazyListState: LazyListState, + onShowSnackBar: (SnackBarData) -> Unit, ) { Surface( modifier = Modifier.fillMaxSize(), @@ -65,7 +67,7 @@ internal fun ZePages( if (editor != null) { Box(Modifier.padding(paddingValues)) { - SelectedEditor(editor, vm) + SelectedEditor(editor, vm, onShowSnackBar) } } diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeScreen.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeScreen.kt index 0d22543a..982f0843 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeScreen.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeScreen.kt @@ -5,10 +5,16 @@ import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.DrawerValue +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.Scaffold +import androidx.compose.material3.Snackbar +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -22,10 +28,14 @@ import de.berlindroid.zeapp.ROUTE_ABOUT import de.berlindroid.zeapp.ROUTE_HOME import de.berlindroid.zeapp.ROUTE_OPENSOURCE import de.berlindroid.zeapp.zeui.ZeNavigationPad +import de.berlindroid.zeapp.zeui.snackbar.SnackBarData +import de.berlindroid.zeapp.zeui.snackbar.showSnackbarWithAction +import de.berlindroid.zeapp.zeui.snackbar.showSnackbarWithMessage import de.berlindroid.zeapp.zeui.zeabout.ZeAbout import de.berlindroid.zeapp.zeui.zeopensource.ZeOpenSource import de.berlindroid.zeapp.zeui.zetheme.ZeBadgeAppTheme import de.berlindroid.zeapp.zevm.ZeBadgeViewModel +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch @Composable @@ -61,6 +71,8 @@ internal fun ZeScreen(vm: ZeBadgeViewModel, modifier: Modifier = Modifier) { val navController = rememberNavController() val currentNavBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = currentNavBackStackEntry?.destination?.route ?: ROUTE_HOME + val snackBarHostState = remember { SnackbarHostState() } + val snackBarData = remember { MutableSharedFlow() } BackHandler(drawerState.isOpen || currentRoute != ROUTE_HOME) { if (drawerState.isOpen) { @@ -126,6 +138,18 @@ internal fun ZeScreen(vm: ZeBadgeViewModel, modifier: Modifier = Modifier) { }, ) }, + snackbarHost = { + SnackbarHost( + hostState = snackBarHostState, + snackbar = { + Snackbar( + snackbarData = it, + containerColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface, + ) + }, + ) + }, ) { paddingValues -> NavHost(navController = navController, startDestination = ROUTE_HOME) { composable(ROUTE_HOME) { @@ -133,6 +157,11 @@ internal fun ZeScreen(vm: ZeBadgeViewModel, modifier: Modifier = Modifier) { paddingValues = paddingValues, lazyListState = lazyListState, vm = vm, + onShowSnackBar = { data -> + scope.launch { + snackBarData.emit(data) + } + } ) } composable(ROUTE_ABOUT) { @@ -146,4 +175,28 @@ internal fun ZeScreen(vm: ZeBadgeViewModel, modifier: Modifier = Modifier) { } }, ) + + LaunchedEffect(Unit) { + snackBarData.collect { data -> + when (data) { + is SnackBarData.SnackBarWithAction -> { + when (snackBarHostState.showSnackbarWithAction(data)) { + SnackbarResult.ActionPerformed -> { + data.onActionClicked.invoke() + } + + SnackbarResult.Dismissed -> { + data.onDismissed?.invoke() + } + } + } + + is SnackBarData.SnackBarWithMessage -> { + if (snackBarHostState.showSnackbarWithMessage(data) == SnackbarResult.Dismissed) { + data.onDismissed?.invoke() + } + } + } + } + } } diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt index be66805d..378e9b7a 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt @@ -1,6 +1,7 @@ package de.berlindroid.zeapp.zevm import android.graphics.Bitmap +import androidx.annotation.StringRes import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -24,8 +25,11 @@ import de.berlindroid.zekompanion.ditherFloydSteinberg import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -52,6 +56,9 @@ class ZeBadgeViewModel @Inject constructor( private val _uiState: MutableStateFlow = MutableStateFlow(getInitialUIState()) val uiState: StateFlow = _uiState.asStateFlow() + private val _uiAction: MutableSharedFlow = MutableSharedFlow() + val uiAction: SharedFlow = _uiAction.asSharedFlow() + // See if disappearing message is ongoing private var hideMessageJob: Job? = null private var messageProgressJob: Job? = null @@ -83,6 +90,14 @@ class ZeBadgeViewModel @Inject constructor( } scheduleMessageDisappearance(duration) + + emitSnackBarAction(message = message) + } + + private fun emitSnackBarAction(message: String) { + viewModelScope.launch { + _uiAction.emit(ZeBadgeUiAction.ShowMessageInSnackBar(message = message)) + } } private fun scheduleMessageDisappearance( @@ -213,7 +228,7 @@ class ZeBadgeViewModel @Inject constructor( slot, slots[slot]!!, ) - newCurrentSlotEditor?.let { currentSlotEditor -> + newCurrentSlotEditor.let { currentSlotEditor -> _uiState.update { it.copy(currentSlotEditor = currentSlotEditor) } @@ -539,4 +554,10 @@ data class ZeBadgeUiState( val currentBadgeConfig: Map?, ) +sealed class ZeBadgeUiAction { + data class ShowLocalisedMessageInSnackBar(@StringRes val messageResId: Int) : ZeBadgeUiAction() + + data class ShowMessageInSnackBar(val message: String) : ZeBadgeUiAction() +} + // ¹ https://www.reddit.com/r/ProgrammerHumor/comments/27yykv/indent_hadouken/ From 76c19dcc210ff62c0502e93ca9e7f6550b0f731b Mon Sep 17 00:00:00 2001 From: "mohammad.hossain" Date: Fri, 5 Jul 2024 13:23:38 +0200 Subject: [PATCH 2/4] added clear error message --- zeapp/android/build.gradle.kts | 1 + .../zeapp/zeui/ZeNameEditorDialog.kt | 64 ++++++------------- .../zeapp/zeui/snackbar/SnackBarData.kt | 41 ------------ .../zeapp/zeui/zehome/SelectedEditor.kt | 22 ++++--- .../berlindroid/zeapp/zeui/zehome/ZePages.kt | 4 +- .../berlindroid/zeapp/zeui/zehome/ZeScreen.kt | 53 --------------- .../zeapp/zevm/ZeBadgeViewModel.kt | 31 +++++---- zeapp/android/src/main/res/values/strings.xml | 2 +- zeapp/gradle/libs.versions.toml | 2 + 9 files changed, 55 insertions(+), 165 deletions(-) delete mode 100644 zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/snackbar/SnackBarData.kt diff --git a/zeapp/android/build.gradle.kts b/zeapp/android/build.gradle.kts index 4000aa12..5dad0469 100644 --- a/zeapp/android/build.gradle.kts +++ b/zeapp/android/build.gradle.kts @@ -191,6 +191,7 @@ dependencies { implementation(libs.timber) implementation(libs.aboutlibraries.compose) implementation(libs.androidx.compose.hilt.navigation) + implementation(libs.androidx.lifecycle.runtime.compose.android) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt index 808d10ca..d4017a16 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt @@ -1,5 +1,4 @@ @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class) - package de.berlindroid.zeapp.zeui import android.app.Activity @@ -15,10 +14,8 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -27,17 +24,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.ban.autosizetextfield.AutoSizeTextField import de.berlindroid.zeapp.R import de.berlindroid.zeapp.zebits.composableToBitmap import de.berlindroid.zeapp.zebits.isBinary import de.berlindroid.zeapp.zemodels.ZeConfiguration -import de.berlindroid.zeapp.zeui.snackbar.SnackBarData import de.berlindroid.zeapp.zeui.zepages.NamePage import de.berlindroid.zeapp.zeui.zetheme.ZeBlack import de.berlindroid.zeapp.zeui.zetheme.ZeWhite -import de.berlindroid.zeapp.zevm.ZeBadgeUiAction -import kotlinx.coroutines.flow.SharedFlow +import de.berlindroid.zeapp.zevm.ZeBadgeErrorUiState +import kotlinx.coroutines.flow.StateFlow const val MaxCharacters: Int = 20 @@ -54,9 +51,8 @@ fun NameEditorDialog( config: ZeConfiguration.Name, dismissed: () -> Unit = {}, accepted: (config: ZeConfiguration.Name) -> Unit, - updateMessage: (String) -> Unit, - onShowSnackBar: (SnackBarData) -> Unit, - uiAction: SharedFlow, + updateMessage: (String, Boolean) -> Unit, + errorUiState: StateFlow, ) { val activity = LocalContext.current as Activity @@ -83,7 +79,7 @@ fun NameEditorDialog( if (image.isBinary()) { accepted(ZeConfiguration.Name(name, contact, image)) } else { - updateMessage(activity.resources.getString(R.string.binary_image_needed)) + updateMessage(activity.resources.getString(R.string.binary_image_needed), true) } }, ) { @@ -153,14 +149,22 @@ fun NameEditorDialog( } }, ) + + when (val viewState = errorUiState.collectAsStateWithLifecycle().value) { + is ZeBadgeErrorUiState.ShowError -> { + Text(text = "Error: ${viewState.message}") + } + + is ZeBadgeErrorUiState.ShowLocalisedError -> { + Text(text = "Error: ${stringResource(id = viewState.messageResId)}") + } + + else -> return@Column + } + } }, ) - - ZeNameEditorDialogAction( - onShowSnackBar = onShowSnackBar, - uiAction = uiAction, - ) } @Composable @@ -175,33 +179,3 @@ fun ClearIcon(isEmpty: Boolean, modifier: Modifier = Modifier, onClick: () -> Un } } -@Composable -fun ZeNameEditorDialogAction( - onShowSnackBar: (SnackBarData) -> Unit, - uiAction: SharedFlow, -) { - val resources = LocalContext.current.resources - LaunchedEffect(Unit) { - uiAction.collect { action -> - when (action) { - is ZeBadgeUiAction.ShowLocalisedMessageInSnackBar -> { - onShowSnackBar( - SnackBarData.SnackBarWithMessage( - message = resources.getString(action.messageResId), - snackbarDuration = SnackbarDuration.Long, - ) - ) - } - - is ZeBadgeUiAction.ShowMessageInSnackBar -> { - onShowSnackBar( - SnackBarData.SnackBarWithMessage( - message = action.message, - snackbarDuration = SnackbarDuration.Long, - ) - ) - } - } - } - } -} diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/snackbar/SnackBarData.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/snackbar/SnackBarData.kt deleted file mode 100644 index e54047be..00000000 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/snackbar/SnackBarData.kt +++ /dev/null @@ -1,41 +0,0 @@ -package de.berlindroid.zeapp.zeui.snackbar - -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult - -sealed class SnackBarData { - - data class SnackBarWithMessage( - val message: String, - val snackbarDuration: SnackbarDuration, - val onDismissed: (() -> Unit)? = null, - ) : SnackBarData() - - data class SnackBarWithAction( - val message: String, - val actionText: String, - val onActionClicked: () -> Unit, - val onDismissed: (() -> Unit)? = null, - val snackbarDuration: SnackbarDuration, - ) : SnackBarData() - -} - - -suspend fun SnackbarHostState.showSnackbarWithMessage(snackBarWithMessage: SnackBarData.SnackBarWithMessage): SnackbarResult { - return showSnackbar( - message = snackBarWithMessage.message, - withDismissAction = true, - duration = snackBarWithMessage.snackbarDuration, - ) -} - -suspend fun SnackbarHostState.showSnackbarWithAction(snackBarWithMessage: SnackBarData.SnackBarWithAction): SnackbarResult { - return showSnackbar( - message = snackBarWithMessage.message, - withDismissAction = true, - actionLabel = snackBarWithMessage.actionText, - duration = snackBarWithMessage.snackbarDuration, - ) -} diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/SelectedEditor.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/SelectedEditor.kt index 4eca9416..f0c0464a 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/SelectedEditor.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/SelectedEditor.kt @@ -16,7 +16,6 @@ import de.berlindroid.zeapp.zeui.RandomQuotesEditorDialog import de.berlindroid.zeapp.zeui.WeatherEditorDialog import de.berlindroid.zeapp.zeui.ZeCameraEditor import de.berlindroid.zeapp.zeui.ZeImageDrawEditorDialog -import de.berlindroid.zeapp.zeui.snackbar.SnackBarData import de.berlindroid.zeapp.zevm.ZeBadgeViewModel import timber.log.Timber @@ -24,7 +23,6 @@ import timber.log.Timber internal fun SelectedEditor( editor: ZeEditor, vm: ZeBadgeViewModel, - onShowSnackBar: (SnackBarData) -> Unit, ) { if (editor.slot !in listOf( ZeSlot.Name, @@ -41,14 +39,18 @@ internal fun SelectedEditor( Timber.e("Slot: This slot '${editor.slot}' is not supposed to be editable.") } else { when (val config = editor.config) { - is ZeConfiguration.Name -> NameEditorDialog( - config = config, - dismissed = { vm.slotConfigured(editor.slot, null) }, - accepted = { newConfig -> vm.slotConfigured(editor.slot, newConfig) }, - updateMessage = vm::showMessage, - onShowSnackBar = onShowSnackBar, - uiAction = vm.uiAction, - ) + is ZeConfiguration.Name -> { + NameEditorDialog( + config = config, + dismissed = { + vm.clearErrorState() + vm.slotConfigured(editor.slot, null) + }, + accepted = { newConfig -> vm.slotConfigured(editor.slot, newConfig) }, + updateMessage = vm::showMessage, + errorUiState = vm.errorUiState, + ) + } is ZeConfiguration.Picture -> PictureEditorDialog( dismissed = { vm.slotConfigured(null, null) }, diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZePages.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZePages.kt index e65d77da..04c4ccee 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZePages.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZePages.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.unit.LayoutDirection import de.berlindroid.zeapp.ZeDimen -import de.berlindroid.zeapp.zeui.snackbar.SnackBarData import de.berlindroid.zeapp.zevm.ZeBadgeViewModel @OptIn(ExperimentalFoundationApi::class) @@ -36,7 +35,6 @@ internal fun ZePages( paddingValues: PaddingValues, vm: ZeBadgeViewModel, lazyListState: LazyListState, - onShowSnackBar: (SnackBarData) -> Unit, ) { Surface( modifier = Modifier.fillMaxSize(), @@ -67,7 +65,7 @@ internal fun ZePages( if (editor != null) { Box(Modifier.padding(paddingValues)) { - SelectedEditor(editor, vm, onShowSnackBar) + SelectedEditor(editor, vm) } } diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeScreen.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeScreen.kt index 982f0843..0d22543a 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeScreen.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeScreen.kt @@ -5,16 +5,10 @@ import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.DrawerValue -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.Scaffold -import androidx.compose.material3.Snackbar -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -28,14 +22,10 @@ import de.berlindroid.zeapp.ROUTE_ABOUT import de.berlindroid.zeapp.ROUTE_HOME import de.berlindroid.zeapp.ROUTE_OPENSOURCE import de.berlindroid.zeapp.zeui.ZeNavigationPad -import de.berlindroid.zeapp.zeui.snackbar.SnackBarData -import de.berlindroid.zeapp.zeui.snackbar.showSnackbarWithAction -import de.berlindroid.zeapp.zeui.snackbar.showSnackbarWithMessage import de.berlindroid.zeapp.zeui.zeabout.ZeAbout import de.berlindroid.zeapp.zeui.zeopensource.ZeOpenSource import de.berlindroid.zeapp.zeui.zetheme.ZeBadgeAppTheme import de.berlindroid.zeapp.zevm.ZeBadgeViewModel -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch @Composable @@ -71,8 +61,6 @@ internal fun ZeScreen(vm: ZeBadgeViewModel, modifier: Modifier = Modifier) { val navController = rememberNavController() val currentNavBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = currentNavBackStackEntry?.destination?.route ?: ROUTE_HOME - val snackBarHostState = remember { SnackbarHostState() } - val snackBarData = remember { MutableSharedFlow() } BackHandler(drawerState.isOpen || currentRoute != ROUTE_HOME) { if (drawerState.isOpen) { @@ -138,18 +126,6 @@ internal fun ZeScreen(vm: ZeBadgeViewModel, modifier: Modifier = Modifier) { }, ) }, - snackbarHost = { - SnackbarHost( - hostState = snackBarHostState, - snackbar = { - Snackbar( - snackbarData = it, - containerColor = MaterialTheme.colorScheme.surface, - contentColor = MaterialTheme.colorScheme.onSurface, - ) - }, - ) - }, ) { paddingValues -> NavHost(navController = navController, startDestination = ROUTE_HOME) { composable(ROUTE_HOME) { @@ -157,11 +133,6 @@ internal fun ZeScreen(vm: ZeBadgeViewModel, modifier: Modifier = Modifier) { paddingValues = paddingValues, lazyListState = lazyListState, vm = vm, - onShowSnackBar = { data -> - scope.launch { - snackBarData.emit(data) - } - } ) } composable(ROUTE_ABOUT) { @@ -175,28 +146,4 @@ internal fun ZeScreen(vm: ZeBadgeViewModel, modifier: Modifier = Modifier) { } }, ) - - LaunchedEffect(Unit) { - snackBarData.collect { data -> - when (data) { - is SnackBarData.SnackBarWithAction -> { - when (snackBarHostState.showSnackbarWithAction(data)) { - SnackbarResult.ActionPerformed -> { - data.onActionClicked.invoke() - } - - SnackbarResult.Dismissed -> { - data.onDismissed?.invoke() - } - } - } - - is SnackBarData.SnackBarWithMessage -> { - if (snackBarHostState.showSnackbarWithMessage(data) == SnackbarResult.Dismissed) { - data.onDismissed?.invoke() - } - } - } - } - } } diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt index 378e9b7a..4d96c2a0 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt @@ -25,11 +25,8 @@ import de.berlindroid.zekompanion.ditherFloydSteinberg import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -56,8 +53,8 @@ class ZeBadgeViewModel @Inject constructor( private val _uiState: MutableStateFlow = MutableStateFlow(getInitialUIState()) val uiState: StateFlow = _uiState.asStateFlow() - private val _uiAction: MutableSharedFlow = MutableSharedFlow() - val uiAction: SharedFlow = _uiAction.asSharedFlow() + private val _errorUiState: MutableStateFlow = MutableStateFlow(ZeBadgeErrorUiState.Initial) + val errorUiState: StateFlow = _errorUiState.asStateFlow() // See if disappearing message is ongoing private var hideMessageJob: Job? = null @@ -83,6 +80,7 @@ class ZeBadgeViewModel @Inject constructor( fun showMessage( message: String, + showAsError: Boolean = false, duration: Long = MESSAGE_DISPLAY_DURATION, ) { _uiState.update { @@ -91,13 +89,13 @@ class ZeBadgeViewModel @Inject constructor( scheduleMessageDisappearance(duration) - emitSnackBarAction(message = message) + if (showAsError) { + emitSnackBarAction(message = message) + } } private fun emitSnackBarAction(message: String) { - viewModelScope.launch { - _uiAction.emit(ZeBadgeUiAction.ShowMessageInSnackBar(message = message)) - } + _errorUiState.value = ZeBadgeErrorUiState.ShowError(message) } private fun scheduleMessageDisappearance( @@ -543,6 +541,10 @@ class ZeBadgeViewModel @Inject constructor( slots = emptyMap(), currentBadgeConfig = null, ) + + fun clearErrorState() { + _errorUiState.value = ZeBadgeErrorUiState.ClearError + } } data class ZeBadgeUiState( @@ -554,10 +556,15 @@ data class ZeBadgeUiState( val currentBadgeConfig: Map?, ) -sealed class ZeBadgeUiAction { - data class ShowLocalisedMessageInSnackBar(@StringRes val messageResId: Int) : ZeBadgeUiAction() +sealed class ZeBadgeErrorUiState { + data object Initial : ZeBadgeErrorUiState() + + data class ShowLocalisedError(@StringRes val messageResId: Int) : ZeBadgeErrorUiState() + + data class ShowError(val message: String) : ZeBadgeErrorUiState() + + data object ClearError : ZeBadgeErrorUiState() - data class ShowMessageInSnackBar(val message: String) : ZeBadgeUiAction() } // ¹ https://www.reddit.com/r/ProgrammerHumor/comments/27yykv/indent_hadouken/ diff --git a/zeapp/android/src/main/res/values/strings.xml b/zeapp/android/src/main/res/values/strings.xml index 9544f45a..c89d5362 100644 --- a/zeapp/android/src/main/res/values/strings.xml +++ b/zeapp/android/src/main/res/values/strings.xml @@ -31,7 +31,7 @@ Phone Your Name Here Contact Me Here - Binary image needed. Press one of the buttons below the image. + Binary image needed. Add your phrase here Random Phrase Unicorn at an android conference in isometric view. diff --git a/zeapp/gradle/libs.versions.toml b/zeapp/gradle/libs.versions.toml index e68731d7..9d3f28a7 100644 --- a/zeapp/gradle/libs.versions.toml +++ b/zeapp/gradle/libs.versions.toml @@ -47,6 +47,7 @@ espresso = "3.6.1" compose-ui-core = "1.6.8" compose-ui-junit = "1.6.8" compose-ui-manifest = "1.6.8" +lifecycleRuntimeComposeAndroid = "2.8.3" [plugins] android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } @@ -120,6 +121,7 @@ androidx-ktx = { group = "androidx.test", name = "core-ktx", version.ref = "andr compose-ui-core = { module = "androidx.compose.ui:ui-test", version.ref = "compose-ui-core" } compose-ui-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose-ui-junit" } compose-ui-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose-ui-manifest" } +androidx-lifecycle-runtime-compose-android = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose-android", version.ref = "lifecycleRuntimeComposeAndroid" } [bundles] screeshottest-android = [ From cbb0dce9aad0b822af3562ae7a7ef80b84704489 Mon Sep 17 00:00:00 2001 From: "mohammad.hossain" Date: Fri, 5 Jul 2024 13:34:35 +0200 Subject: [PATCH 3/4] fix lint issue --- .../berlindroid/zeapp/zeui/ZeNameEditorDialog.kt | 14 +++++++++++--- .../de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt | 1 - 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt index d4017a16..89f9e03a 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt @@ -1,4 +1,5 @@ @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class) + package de.berlindroid.zeapp.zeui import android.app.Activity @@ -21,8 +22,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.ban.autosizetextfield.AutoSizeTextField @@ -152,16 +155,21 @@ fun NameEditorDialog( when (val viewState = errorUiState.collectAsStateWithLifecycle().value) { is ZeBadgeErrorUiState.ShowError -> { - Text(text = "Error: ${viewState.message}") + Text( + text = "Error: ${viewState.message}", + style = TextStyle(color = Color.Red), + ) } is ZeBadgeErrorUiState.ShowLocalisedError -> { - Text(text = "Error: ${stringResource(id = viewState.messageResId)}") + Text( + text = "Error: ${stringResource(id = viewState.messageResId)}", + style = TextStyle(color = Color.Red), + ) } else -> return@Column } - } }, ) diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt index 4d96c2a0..29e3b345 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zevm/ZeBadgeViewModel.kt @@ -564,7 +564,6 @@ sealed class ZeBadgeErrorUiState { data class ShowError(val message: String) : ZeBadgeErrorUiState() data object ClearError : ZeBadgeErrorUiState() - } // ¹ https://www.reddit.com/r/ProgrammerHumor/comments/27yykv/indent_hadouken/ From a0a7f6356946f44807ae9a23ada0489ecc3b2c8b Mon Sep 17 00:00:00 2001 From: "mohammad.hossain" Date: Fri, 5 Jul 2024 13:39:22 +0200 Subject: [PATCH 4/4] refactor error placeholder --- .../zeapp/zeui/ZeNameEditorDialog.kt | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt index 89f9e03a..265efea7 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/ZeNameEditorDialog.kt @@ -153,28 +153,33 @@ fun NameEditorDialog( }, ) - when (val viewState = errorUiState.collectAsStateWithLifecycle().value) { - is ZeBadgeErrorUiState.ShowError -> { - Text( - text = "Error: ${viewState.message}", - style = TextStyle(color = Color.Red), - ) - } - - is ZeBadgeErrorUiState.ShowLocalisedError -> { - Text( - text = "Error: ${stringResource(id = viewState.messageResId)}", - style = TextStyle(color = Color.Red), - ) - } - - else -> return@Column - } + ErrorPlaceHolder(errorUiState) } }, ) } +@Composable +private fun ErrorPlaceHolder(errorUiState: StateFlow) { + when (val viewState = errorUiState.collectAsStateWithLifecycle().value) { + is ZeBadgeErrorUiState.ShowError -> { + Text( + text = "Error: ${viewState.message}", + style = TextStyle(color = Color.Red), + ) + } + + is ZeBadgeErrorUiState.ShowLocalisedError -> { + Text( + text = "Error: ${stringResource(id = viewState.messageResId)}", + style = TextStyle(color = Color.Red), + ) + } + + else -> return + } +} + @Composable fun ClearIcon(isEmpty: Boolean, modifier: Modifier = Modifier, onClick: () -> Unit) { if (!isEmpty) { @@ -186,4 +191,3 @@ fun ClearIcon(isEmpty: Boolean, modifier: Modifier = Modifier, onClick: () -> Un ) } } -