From 778073915f4f9f67a665de7b4ad4d4548a6a29b7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 31 Oct 2023 16:28:50 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 74 ++++++++-- .../com/tangem/tap/common/SnackbarHandler.kt | 19 ++- .../tap/common/extensions/Navigation.kt | 2 + .../tangem/tap/features/main/MainViewModel.kt | 6 +- .../ModalNotificationBottomSheetFragment.kt | 57 ++++++++ .../ui/components/ModalNotificationScreen.kt | 133 ++++++++++++++++++ .../tangem/core/navigation/NavigationState.kt | 1 + .../com/tangem/core/ui/event/StateEvent.kt | 2 +- 8 files changed, 278 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/main/ui/components/ModalNotificationScreen.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 6faa43d670..b1faa6cfad 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -10,6 +10,7 @@ import android.os.Build import android.os.Bundle import android.view.View import androidx.activity.viewModels +import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode @@ -22,9 +23,13 @@ import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse import by.kirich1409.viewbindingdelegate.viewBinding +import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction +import com.tangem.core.ui.event.StateEvent +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference import com.tangem.data.card.sdk.CardSdkLifecycleObserver import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.card.ScanCardUseCase @@ -55,6 +60,7 @@ import com.tangem.tap.features.intentHandler.handlers.BuyCurrencyIntentHandler import com.tangem.tap.features.intentHandler.handlers.SellCurrencyIntentHandler import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler import com.tangem.tap.features.main.MainViewModel +import com.tangem.tap.features.main.model.Toast import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.features.shop.redux.ShopAction import com.tangem.tap.features.welcome.ui.WelcomeFragment @@ -162,12 +168,23 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private fun observeStateUpdates() { viewModel.state .flowWithLifecycle(lifecycle) - .onEach { - // TODO: Show toast + .onEach { state -> + if (state.toast is StateEvent.Triggered) { + showToast(state.toast.data) + state.toast.onConsume() + } } .launchIn(lifecycleScope) } + private fun showToast(toast: Toast) { + dismissSnackbar() + showSnackbar(toast.message, Snackbar.LENGTH_LONG, toast.action.text) { + toast.action.onClick() + dismissSnackbar() + } + } + private fun installActivityDependencies() { store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) @@ -317,18 +334,22 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } } - override fun showSnackbar(text: Int, buttonTitle: Int?, action: View.OnClickListener?) { - if (snackbar != null) return + override fun showSnackbar( + @StringRes text: Int, + length: Int, + @StringRes buttonTitle: Int?, + action: View.OnClickListener?, + ) { + showSnackbar(getString(text), length, buttonTitle?.let(::getString), action) + } - snackbar = Snackbar.make( - binding.fragmentContainer, - getString(text), - Snackbar.LENGTH_INDEFINITE, - ) - if (buttonTitle != null && action != null) { - snackbar?.setAction(getString(buttonTitle), action) - } - snackbar?.show() + override fun showSnackbar( + text: TextReference, + length: Int, + buttonTitle: TextReference?, + action: View.OnClickListener?, + ) { + showSnackbar(text.resolveReference(resources), length, buttonTitle?.resolveReference(resources), action) } override fun dismissSnackbar() { @@ -364,6 +385,33 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac lockUserWalletsTimer?.restart() } + private fun showSnackbar(text: String, length: Int, buttonTitle: String?, action: View.OnClickListener?) { + if (snackbar != null) return + + snackbar = Snackbar.make(binding.fragmentContainer, text, length).apply { + val textColor = getColor(R.color.text_primary_2) + + setBackgroundTint(getColor(R.color.button_primary)) + setActionTextColor(textColor) + setTextColor(textColor) + + if (buttonTitle != null && action != null) { + setAction(buttonTitle, action) + } + + addCallback( + object : BaseTransientBottomBar.BaseCallback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + snackbar = null + removeCallback(this) + } + }, + ) + } + + snackbar?.show() + } + private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) { val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 val isNotScannedBefore = store.state.globalState.scanResponse == null diff --git a/app/src/main/java/com/tangem/tap/common/SnackbarHandler.kt b/app/src/main/java/com/tangem/tap/common/SnackbarHandler.kt index c10b8e6f37..53228bf674 100644 --- a/app/src/main/java/com/tangem/tap/common/SnackbarHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/SnackbarHandler.kt @@ -1,8 +1,25 @@ package com.tangem.tap.common import android.view.View +import androidx.annotation.StringRes +import com.google.android.material.snackbar.Snackbar +import com.tangem.core.ui.extensions.TextReference interface SnackbarHandler { - fun showSnackbar(text: Int, buttonTitle: Int? = null, action: View.OnClickListener? = null) + + fun showSnackbar( + @StringRes text: Int, + length: Int = Snackbar.LENGTH_INDEFINITE, + @StringRes buttonTitle: Int? = null, + action: View.OnClickListener? = null, + ) + + fun showSnackbar( + text: TextReference, + length: Int = Snackbar.LENGTH_INDEFINITE, + buttonTitle: TextReference? = null, + action: View.OnClickListener? = null, + ) + fun dismissSnackbar() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 126ecb0d60..51ac9818c4 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -18,6 +18,7 @@ import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment import com.tangem.tap.features.home.HomeFragment +import com.tangem.tap.features.main.ui.ModalNotificationBottomSheetFragment import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment @@ -194,5 +195,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.SaveWallet -> SaveWalletBottomSheetFragment() AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment() AppScreen.AppCurrencySelector -> AppCurrencySelectorFragment() + AppScreen.ModalNotification -> ModalNotificationBottomSheetFragment() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index cf46a567f9..e919fa41ec 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -2,6 +2,9 @@ package com.tangem.tap.features.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.tangem.core.navigation.AppScreen +import com.tangem.core.navigation.NavigationAction +import com.tangem.core.navigation.ReduxNavController import com.tangem.domain.balancehiding.BalanceHidingSettings import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase @@ -16,6 +19,7 @@ import javax.inject.Inject internal class MainViewModel @Inject constructor( private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase, private val listenToFlipsUseCase: ListenToFlipsUseCase, + private val reduxNavController: ReduxNavController, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel(), MainIntents { @@ -48,7 +52,7 @@ internal class MainViewModel @Inject constructor( .onEach { if (state.value.modalNotification?.isShow != true) { stateHolder.updateWithHiddenBalancesNotification() - // TODO: reduxNavController.navigate(NavigationAction.NavigateTo(AppScreen.ModalNotification)) + reduxNavController.navigate(NavigationAction.NavigateTo(AppScreen.ModalNotification)) } } .launchIn(viewModelScope) diff --git a/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt new file mode 100644 index 0000000000..2a40e92ef8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt @@ -0,0 +1,57 @@ +package com.tangem.tap.features.main.ui + +import android.content.DialogInterface +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.ui.screen.ComposeBottomSheetFragment +import com.tangem.core.ui.theme.AppThemeModeHolder +import com.tangem.tap.features.main.MainViewModel +import com.tangem.tap.features.main.model.ModalNotification +import com.tangem.tap.features.main.ui.components.ModalNotificationContent +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@AndroidEntryPoint +internal class ModalNotificationBottomSheetFragment : ComposeBottomSheetFragment() { + + @Inject + override lateinit var appThemeModeHolder: AppThemeModeHolder + + private val viewModel: MainViewModel by activityViewModels() + + @Composable + override fun ScreenContent(modifier: Modifier) { + val state by viewModel.state.collectAsStateWithLifecycle() + val notification = state.modalNotification + if (notification == null) { + dismiss() + return + } + + BackHandler(onBack = notification.onDismissRequest) + + when (val content = notification.content) { + is ModalNotification -> ModalNotificationContent( + modifier = modifier, + notification = content, + ) + else -> Unit + } + + LaunchedEffect(notification) { + if (!notification.isShow) { + dismiss() + } + } + } + + override fun onDismiss(dialog: DialogInterface) { + viewModel.state.value.modalNotification?.onDismissRequest?.invoke() + super.onDismiss(dialog) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/main/ui/components/ModalNotificationScreen.kt b/app/src/main/java/com/tangem/tap/features/main/ui/components/ModalNotificationScreen.kt new file mode 100644 index 0000000000..3ebb87e8dd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/main/ui/components/ModalNotificationScreen.kt @@ -0,0 +1,133 @@ +package com.tangem.tap.features.main.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.atoms.Hand +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.main.model.ActionConfig +import com.tangem.tap.features.main.model.ModalNotification +import com.tangem.wallet.R + +@Composable +internal fun ModalNotificationContent(notification: ModalNotification, modifier: Modifier = Modifier) { + Column( + modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing16), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing40), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Hand() + Icon( + modifier = Modifier.size(TangemTheme.dimens.size48), + painter = painterResource(notification.iconResId), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + Column( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = notification.title.resolveReference(), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + Text( + text = notification.message.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + } + Column( + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = notification.primaryAction.text.resolveReference(), + onClick = notification.primaryAction.onClick, + ) + when (val secondaryAction = notification.secondaryAction) { + null -> Unit + else -> SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = secondaryAction.text.resolveReference(), + onClick = secondaryAction.onClick, + ) + } + } + } +} + +// region Preview +@Preview(widthDp = 360, heightDp = 404) +@Composable +private fun ModalNotificationContentPreview_Light( + @PreviewParameter(GlobalNotificationProvider::class) param: ModalNotification, +) { + TangemTheme(isDark = false) { + ModalNotificationContent( + param, + modifier = Modifier.background( + color = TangemTheme.colors.background.plain, + shape = TangemTheme.shapes.bottomSheet, + ), + ) + } +} + +@Preview(widthDp = 360, heightDp = 404) +@Composable +private fun ModalNotificationContentPreview_Dark( + @PreviewParameter(GlobalNotificationProvider::class) param: ModalNotification, +) { + TangemTheme(isDark = true) { + ModalNotificationContent( + param, + modifier = Modifier.background( + color = TangemTheme.colors.background.plain, + shape = TangemTheme.shapes.bottomSheet, + ), + ) + } +} + +private class GlobalNotificationProvider : CollectionPreviewParameterProvider( + collection = listOf( + ModalNotification( + iconResId = R.drawable.ic_eye_off_outline_24, + title = resourceReference(R.string.balance_hidden_title), + message = resourceReference(R.string.balance_hidden_description), + primaryAction = ActionConfig( + text = resourceReference(R.string.balance_hidden_got_it_button), + onClick = {}, + ), + secondaryAction = ActionConfig( + text = resourceReference(R.string.balance_hidden_do_not_show_button), + onClick = {}, + ), + ), + ), +) +// endregion Preview \ No newline at end of file diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt index 6685b69953..9f96640d74 100644 --- a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt +++ b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt @@ -36,4 +36,5 @@ enum class AppScreen(val isDialogFragment: Boolean = false) { SaveWallet(isDialogFragment = true), WalletSelector(isDialogFragment = true), AppCurrencySelector, + ModalNotification(isDialogFragment = true), } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/event/StateEvent.kt b/core/ui/src/main/java/com/tangem/core/ui/event/StateEvent.kt index 1860808b1d..fb215b168f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/event/StateEvent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/event/StateEvent.kt @@ -35,7 +35,7 @@ sealed class StateEvent { */ data class Triggered( val data: A, - internal val onConsume: () -> Unit, + val onConsume: () -> Unit, ) : StateEvent() }