From af3f8c6c25068189f7412f85d649d793529de8a5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Aug 2022 18:03:14 +0400 Subject: [PATCH 1/5] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 14 -- .../tap/common/extensions/Navigation.kt | 3 +- .../features/details/redux/DetailsAction.kt | 5 +- .../details/redux/DetailsMiddleware.kt | 15 +- .../features/details/redux/DetailsReducer.kt | 7 +- .../features/details/redux/DetailsState.kt | 15 +- .../ui/appsettings/AppSettingsFragment.kt | 70 +++++- .../ui/appsettings/AppSettingsScreen.kt | 229 +++++++++++++++++- .../ui/appsettings/AppSettingsScreenState.kt | 10 + .../ui/appsettings/AppSettingsViewModel.kt | 27 +++ .../details/ui/common/TangemSwitch.kt | 87 +++++++ app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 16 files changed, 436 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index eeb216951d..6329729b5d 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -6,8 +6,6 @@ import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.ui.SimpleAlertDialog import com.tangem.tap.common.ui.SimpleCancelableAlertDialog -import com.tangem.tap.features.details.redux.DetailsDialog -import com.tangem.tap.features.details.redux.PrivacySetting import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog import com.tangem.tap.features.details.ui.walletconnect.dialogs.ApproveWcSessionDialog import com.tangem.tap.features.details.ui.walletconnect.dialogs.BnbTransactionDialog @@ -150,18 +148,6 @@ class DialogManager : StoreSubscriber { ) is WalletDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog(context) - is DetailsDialog.ConfirmDisablingSaving -> { - val messageRes = when (state.dialog.setting) { - PrivacySetting.SAVE_CARDS -> R.string.app_settings_off_saved_wallet_alert_message - PrivacySetting.SAVE_ACCESS_CODE -> R.string.app_settings_off_saved_access_code_alert_message - } - SimpleCancelableAlertDialog.create( - titleRes = R.string.common_warning, - messageRes = messageRes, - context = context, - primaryButtonAction = state.dialog.onOk - ) - } else -> null } dialog?.show() 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 f5feed18d7..a9504e668a 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 @@ -7,6 +7,7 @@ import androidx.fragment.app.FragmentManager import com.tangem.common.extensions.VoidCallback import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.FragmentShareTransition +import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment import com.tangem.tap.features.details.ui.details.DetailsFragment import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment @@ -95,7 +96,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.Details -> DetailsFragment() AppScreen.DetailsSecurity -> SecurityModeFragment() AppScreen.CardSettings -> CardSettingsFragment() - AppScreen.AppSettings -> CardSettingsFragment() + AppScreen.AppSettings -> AppSettingsFragment() AppScreen.ResetToFactory -> ResetCardFragment() AppScreen.Disclaimer -> DisclaimerFragment() AppScreen.AddTokens -> AddTokensFragment() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 3ad36caa55..9cb5cfe537 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -44,10 +44,7 @@ sealed class DetailsAction : Action { } sealed class AppSettings : DetailsAction() { - data class SwitchPrivacySetting(val allow: Boolean, val setting: PrivacySetting) : - AppSettings() - - data class ConfirmSwitchingSetting(val allow: Boolean, val setting: PrivacySetting) : + data class SwitchPrivacySetting(val enable: Boolean, val setting: PrivacySetting) : AppSettings() } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 8cae2e026b..537668094f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -5,7 +5,6 @@ import com.tangem.common.core.TangemSdkError import com.tangem.domain.common.isTangemTwins import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsParam -import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState @@ -188,20 +187,8 @@ class DetailsMiddleware { class ManagePrivacyMiddleware { fun handle(action: DetailsAction.AppSettings) { when (action) { - is DetailsAction.AppSettings.ConfirmSwitchingSetting -> { -// TODO() - } is DetailsAction.AppSettings.SwitchPrivacySetting -> { - if (action.allow) { - store.dispatch( - DetailsAction.AppSettings.ConfirmSwitchingSetting( - action.allow, - action.setting, - ), - ) - } else { - store.dispatchDialogShow(DetailsDialog.ConfirmDisablingSaving(action.setting)) - } +// TODO() } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 99024b0c20..a85b15b857 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -149,13 +149,12 @@ private fun handlePrivacyAction( action: DetailsAction.AppSettings, state: DetailsState, ): DetailsState { return when (action) { - is DetailsAction.AppSettings.ConfirmSwitchingSetting -> { + is DetailsAction.AppSettings.SwitchPrivacySetting -> { when (action.setting) { - PrivacySetting.SAVE_CARDS -> state.copy(saveCards = action.allow) - PrivacySetting.SAVE_ACCESS_CODE -> state.copy(savePasswords = action.allow) + PrivacySetting.SaveWallets -> state.copy(saveWallets = action.enable) + PrivacySetting.SaveAccessCode -> state.copy(saveAccessCodes = action.enable) } } - is DetailsAction.AppSettings.SwitchPrivacySetting -> state } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index cf0b3852e5..8e02856097 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -6,7 +6,6 @@ import com.tangem.common.card.Card import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.entities.Button import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.store import org.rekotlin.StateType @@ -21,8 +20,8 @@ data class DetailsState( val privacyPolicyUrl: String? = null, val createBackupAllowed: Boolean = false, val appCurrency: FiatCurrency = FiatCurrency.Default, - val saveCards: Boolean = true, - val savePasswords: Boolean = true + val saveWallets: Boolean = true, + val saveAccessCodes: Boolean = true, ) : StateType { // if you do not delegate - the application crashes on startup, @@ -58,14 +57,6 @@ data class ManageSecurityState( enum class SecurityOption { LongTap, PassCode, AccessCode } - -sealed interface DetailsDialog : StateDialog { - data class ConfirmDisablingSaving(val setting: PrivacySetting) : DetailsDialog { - val onOk: () -> Unit = - { store.dispatch(DetailsAction.AppSettings.ConfirmSwitchingSetting(false, setting)) } - } -} - enum class PrivacySetting { - SAVE_CARDS, SAVE_ACCESS_CODE + SaveWallets, SaveAccessCode } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt index 048b390954..bea7b87171 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt @@ -1,3 +1,71 @@ package com.tangem.tap.features.details.ui.appsettings -class AppSettingsFragment \ No newline at end of file +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.details.redux.DetailsState +import com.tangem.tap.store +import com.tangem.wallet.R +import org.rekotlin.StoreSubscriber + +class AppSettingsFragment : Fragment(), StoreSubscriber { + private val viewModel = AppSettingsViewModel(store) + private var screenState: MutableState = + mutableStateOf(viewModel.updateState(store.state.detailsState)) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return ComposeView(requireContext()).apply { + setContent { + isTransitionGroup = true + AppCompatTheme { + AppSettingsScreen( + state = screenState.value, + onBackPressed = { + store.dispatch(DetailsAction.ResetCardSettingsData) + store.dispatch(NavigationAction.PopBackTo()) + }, + ) + } + } + } + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.detailsState == newState.detailsState + }.select { it.detailsState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun newState(state: DetailsState) { + if (activity == null || view == null) return + screenState.value = viewModel.updateState(state) + } +} diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index 060c8cc343..71f13c39b4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -1,9 +1,236 @@ package com.tangem.tap.features.details.ui.appsettings +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material.AlertDialog +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Switch +import androidx.compose.material.SwitchDefaults +import androidx.compose.material.Text +import androidx.compose.material.TextButton import androidx.compose.runtime.Composable +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.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.tangem.tap.common.compose.TangemTypography +import com.tangem.tap.features.details.redux.PrivacySetting +import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold +import com.tangem.wallet.R @Composable fun AppSettingsScreen( - + state: AppSettingsScreenState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, ) { + SettingsScreensScaffold( + content = { + AppSettings(state = state, modifier = modifier) + }, + titleRes = R.string.app_settings_title, + onBackClick = onBackPressed, + ) +} + +@Composable +fun AppSettings( + state: AppSettingsScreenState, + modifier: Modifier = Modifier, +) { + var dialogType by remember { mutableStateOf(null) } + val onDialogStateChange: (PrivacySetting?) -> Unit = { dialogType = it } + + dialogType?.let { + SettingsAlertDialog( + element = it, + onDialogStateChange = onDialogStateChange, + onSettingToggled = state.onSettingToggled, + modifier = modifier, + ) + } + + Column( + modifier = modifier + .fillMaxSize(), + ) { + AppSettingsElement( + state = state, setting = PrivacySetting.SaveWallets, + onDialogStateChange = onDialogStateChange, + modifier = modifier, + ) + Spacer(modifier = Modifier.size(32.dp)) + AppSettingsElement( + state = state, setting = PrivacySetting.SaveAccessCode, + onDialogStateChange = onDialogStateChange, + modifier = modifier, + ) + } +} + +@Composable +fun AppSettingsElement( + state: AppSettingsScreenState, + setting: PrivacySetting, + onDialogStateChange: (PrivacySetting?) -> Unit, + modifier: Modifier = Modifier, +) { + val titleRes = when (setting) { + PrivacySetting.SaveWallets -> R.string.app_settings_saved_wallet + PrivacySetting.SaveAccessCode -> R.string.app_settings_saved_access_codes + } + val subtitleRes = when (setting) { + PrivacySetting.SaveWallets -> R.string.app_settings_saved_wallet_footer + PrivacySetting.SaveAccessCode -> R.string.app_settings_saved_access_codes_footer + } + val checked = state.settings[setting] ?: false + + Row( + modifier = modifier + .fillMaxWidth() + .padding(start = 20.dp), + // .clickable { state.onSettingToggled(element, !checked) }, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + verticalArrangement = Arrangement.Center, + modifier = modifier + .weight(0.6f) + .padding(end = 4.dp), + ) { + Text( + text = stringResource(id = titleRes), + style = TangemTypography.subtitle1, + color = colorResource(id = R.color.text_primary_1), + ) + Spacer(modifier = Modifier.size(4.dp)) + Text( + text = stringResource(id = subtitleRes), + style = TangemTypography.body2, + color = colorResource(id = R.color.text_secondary), + ) + } + Switch( + checked = checked, + onCheckedChange = { + onCheckedChange( + element = setting, + enabled = it, + onSettingToggled = state.onSettingToggled, + onDialogStateChange = onDialogStateChange, + ) + }, + colors = SwitchDefaults.colors( + checkedThumbColor = colorResource(id = R.color.control_key), + uncheckedThumbColor = colorResource(id = R.color.control_key), + checkedTrackColor = colorResource(id = R.color.control_checked), + uncheckedTrackColor = colorResource(id = R.color.icon_informative), + ), + modifier = modifier + .padding(start = 20.dp, end = 20.dp), + ) + } +} + +fun onCheckedChange( + element: PrivacySetting, enabled: Boolean, + onSettingToggled: (PrivacySetting, Boolean) -> Unit, + onDialogStateChange: (PrivacySetting?) -> Unit, +) { + //Show warning if user wants to disable the switch + if (!enabled) { + onDialogStateChange(element) + } else { + onSettingToggled(element, enabled) + } +} + +@Composable +fun SettingsAlertDialog( + element: PrivacySetting, + onDialogStateChange: (PrivacySetting?) -> Unit, + onSettingToggled: (PrivacySetting, Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + val text = when (element) { + PrivacySetting.SaveWallets -> R.string.app_settings_off_saved_wallet_alert_message + PrivacySetting.SaveAccessCode -> R.string.app_settings_off_saved_access_code_alert_message + } + AlertDialog( + onDismissRequest = { onDialogStateChange(null) }, + confirmButton = { + TextButton( + onClick = { + onDialogStateChange(null) + }, + ) { + Text( + text = stringResource(id = R.string.common_cancel), + color = colorResource(id = R.color.text_secondary), + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + ) + } + }, + dismissButton = { + TextButton( + onClick = { + onDialogStateChange(null) + onSettingToggled(element, false) + }, + modifier = modifier.padding(bottom = 14.dp), + ) { + Text( + text = stringResource(id = R.string.common_delete), + color = colorResource(id = R.color.text_warning), + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + ) + } + }, + title = { + Text( + text = stringResource(id = R.string.common_attention), + color = colorResource(id = R.color.text_primary_1), + ) + }, + text = { + Text( + text = stringResource(id = text), + color = colorResource(id = R.color.text_secondary), + ) + }, + shape = MaterialTheme.shapes.medium.copy(all = CornerSize(size = 28.dp)), + modifier = modifier.padding(vertical = 34.dp, horizontal = 24.dp), + ) +} + +@Preview +@Composable +fun AppSettingsScreenPreview() { + AppSettingsScreen( + state = AppSettingsScreenState( + settings = mapOf( + PrivacySetting.SaveWallets to true, + PrivacySetting.SaveAccessCode to false, + ), + onSettingToggled = { _, _ -> }, + ), + onBackPressed = { }, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt new file mode 100644 index 0000000000..043423b3d3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.features.details.ui.appsettings + +import com.tangem.tap.features.details.redux.PrivacySetting + +data class AppSettingsScreenState( + val settings: Map, + val onSettingToggled: (PrivacySetting, Boolean) -> Unit, +) + + diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt new file mode 100644 index 0000000000..65dd12eaeb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.features.details.ui.appsettings + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.details.redux.DetailsState +import com.tangem.tap.features.details.redux.PrivacySetting +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.rekotlin.Store + +class AppSettingsViewModel(private val store: Store) { + private val _uiState = MutableStateFlow(updateState(store.state.detailsState)) + val uiState: StateFlow = _uiState + fun updateState(state: DetailsState): AppSettingsScreenState { + return AppSettingsScreenState( + settings = mapOf( + PrivacySetting.SaveWallets to state.saveWallets, + PrivacySetting.SaveAccessCode to state.saveAccessCodes, + ), + onSettingToggled = { privacySetting, enabled -> onSettingsToggled(privacySetting, enabled) }, + ) + } + + private fun onSettingsToggled(setting: PrivacySetting, enable: Boolean) { + store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt new file mode 100644 index 0000000000..02b698421a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt @@ -0,0 +1,87 @@ +package com.tangem.tap.features.details.ui.common + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Fill +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.tangem.wallet.R + +@Composable +fun TangemSwitch( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + width: Dp = 48.dp, + height: Dp = 24.dp, + checkedTrackColor: Color = colorResource(id = R.color.control_checked), + uncheckedTrackColor: Color = colorResource(id = R.color.icon_informative), + selectorColor: Color = colorResource(id = R.color.background_secondary), + gapBetweenThumbAndTrackEdge: Dp = 3.dp, +) { + val thumbRadius = (height / 2) - gapBetweenThumbAndTrackEdge + val animatePosition = animateFloatAsState( + targetValue = if (checked) + with(LocalDensity.current) { (width - thumbRadius - gapBetweenThumbAndTrackEdge).toPx() } + else + with(LocalDensity.current) { (thumbRadius + gapBetweenThumbAndTrackEdge).toPx() }, + ) + + Canvas( + modifier = modifier + .size(width = width, height = height) + .pointerInput(Unit) { + detectTapGestures( + onTap = { + onCheckedChange(checked) + }, + ) + }, + ) { + drawRoundRect( + color = if (checked) checkedTrackColor else uncheckedTrackColor, + cornerRadius = CornerRadius(x = 35.dp.toPx(), y = 35.dp.toPx()), + style = Fill, + ) + + drawCircle( + color = selectorColor, + radius = thumbRadius.toPx(), + center = Offset( + x = animatePosition.value, + y = size.height / 2, + ), + ) + } + + Spacer(modifier = Modifier.height(18.dp)) +} + +@Preview +@Composable +fun TangemSwitchPreview() { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + TangemSwitch(true, {}) + } +} \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 99c83c09c5..cf8a46c64c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -6,6 +6,7 @@ Erledigt Abbrechen Akzeptieren + Entfernen Der Kamerazugriff wurde verweigert Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 17a6a3cce0..d39650f3b7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -7,6 +7,7 @@ Exécuté Annuler J\'accepte + Supprimer L\'accès à la caméra est refusé Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4d699600d3..7eba948ed7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -6,6 +6,7 @@ Riprova Fatto Annulla + Rimuovere Accesso alla videocamera negato Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy Impostazioni diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9b6b5d5725..db78a57ae5 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -7,6 +7,7 @@ Готово Отмена Принять + Удалить %s Доступ к камере запрещен Вы не предоставили доступ к своей камере, измените настройки конфиденциальности diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 60d0c22f1d..2c1ebffd92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,6 +8,7 @@ Done Cancel Accept + Delete %s Camera access denied From 06481327fdd59e7bd3b8627239cc5f5f0c1af4d1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 7 Aug 2022 22:32:40 +0400 Subject: [PATCH 2/5] Updated on 2026-08-14 --- .../ui/appsettings/AppSettingsScreen.kt | 13 +- .../details/ui/common/TangemSwitch.kt | 157 +++++++++++------- .../details/ui/details/DetailsViewModel.kt | 1 + 3 files changed, 97 insertions(+), 74 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index 71f13c39b4..9b1dd4af03 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -11,8 +11,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CornerSize import androidx.compose.material.AlertDialog import androidx.compose.material.MaterialTheme -import androidx.compose.material.Switch -import androidx.compose.material.SwitchDefaults import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable @@ -31,6 +29,7 @@ import androidx.compose.ui.unit.sp import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.details.redux.PrivacySetting import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold +import com.tangem.tap.features.details.ui.common.TangemSwitch import com.tangem.wallet.R @Composable @@ -125,7 +124,7 @@ fun AppSettingsElement( color = colorResource(id = R.color.text_secondary), ) } - Switch( + TangemSwitch( checked = checked, onCheckedChange = { onCheckedChange( @@ -135,14 +134,8 @@ fun AppSettingsElement( onDialogStateChange = onDialogStateChange, ) }, - colors = SwitchDefaults.colors( - checkedThumbColor = colorResource(id = R.color.control_key), - uncheckedThumbColor = colorResource(id = R.color.control_key), - checkedTrackColor = colorResource(id = R.color.control_checked), - uncheckedTrackColor = colorResource(id = R.color.icon_informative), - ), modifier = modifier - .padding(start = 20.dp, end = 20.dp), + .padding(20.dp), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt index 02b698421a..3132bb6fcc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt @@ -1,87 +1,116 @@ package com.tangem.tap.features.details.ui.common -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.animation.animateColor +import androidx.compose.animation.core.FastOutLinearInEasing +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.animateDp +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.indication +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.CornerRadius -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.drawscope.Fill -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.colorResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.wallet.R @Composable fun TangemSwitch( - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, - width: Dp = 48.dp, - height: Dp = 24.dp, - checkedTrackColor: Color = colorResource(id = R.color.control_checked), - uncheckedTrackColor: Color = colorResource(id = R.color.icon_informative), - selectorColor: Color = colorResource(id = R.color.background_secondary), - gapBetweenThumbAndTrackEdge: Dp = 3.dp, + enabledColor: Color = colorResource(id = R.color.control_checked), + disabledColor: Color = colorResource(id = R.color.icon_informative), + size: Dp = 48.dp, + checked: Boolean = false, + onCheckedChange: (Boolean) -> Unit, ) { - val thumbRadius = (height / 2) - gapBetweenThumbAndTrackEdge - val animatePosition = animateFloatAsState( - targetValue = if (checked) - with(LocalDensity.current) { (width - thumbRadius - gapBetweenThumbAndTrackEdge).toPx() } - else - with(LocalDensity.current) { (thumbRadius + gapBetweenThumbAndTrackEdge).toPx() }, - ) - - Canvas( - modifier = modifier - .size(width = width, height = height) - .pointerInput(Unit) { - detectTapGestures( - onTap = { - onCheckedChange(checked) - }, - ) - }, + val transition = updateTransition(checked, label = "SwitchState") + val color by transition.animateColor( + transitionSpec = { + tween(200, easing = FastOutLinearInEasing) + }, + label = "", ) { - drawRoundRect( - color = if (checked) checkedTrackColor else uncheckedTrackColor, - cornerRadius = CornerRadius(x = 35.dp.toPx(), y = 35.dp.toPx()), - style = Fill, - ) - - drawCircle( - color = selectorColor, - radius = thumbRadius.toPx(), - center = Offset( - x = animatePosition.value, - y = size.height / 2, - ), - ) + when (it) { + true -> enabledColor + false -> disabledColor + } + } + val interactionSource = remember { MutableInteractionSource() } + val clickable = Modifier.clickable( + interactionSource = interactionSource, + indication = null, + ) { + if (!checked) { + onCheckedChange(true) + } else { + onCheckedChange(false) + } } - Spacer(modifier = Modifier.height(18.dp)) -} - -@Preview -@Composable -fun TangemSwitchPreview() { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, + Box( + modifier = Modifier + .then(clickable) + .indication( + interactionSource = MutableInteractionSource(), + indication = rememberRipple( + bounded = true, + radius = 100.dp, + color = Color.Transparent, + ), + ), ) { - TangemSwitch(true, {}) + BoxWithConstraints( + modifier = modifier + .width(size) + .height(size / 2) + .indication(MutableInteractionSource(), null) + .background(color = color, shape = RoundedCornerShape(100)), + contentAlignment = Alignment.CenterStart, + ) { + val roundCardSize = this.maxWidth / 2 + val xOffset by transition.animateDp( + transitionSpec = { + tween(150, easing = LinearOutSlowInEasing) + }, + label = "xOffset", + ) { state -> + when (state) { + false -> 0.dp + true -> this.maxWidth - roundCardSize + } + } + + Card( + modifier = Modifier + .size(this.maxWidth / 2) + .offset(x = xOffset, y = 0.dp) + .padding(3.dp), + shape = RoundedCornerShape(100), + backgroundColor = Color.White, + border = BorderStroke( + if (!checked) 0.5.dp else 0.dp, + color = Color.LightGray, + ), + ) { + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 9e6dc602e2..ecb6a30a0b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -28,6 +28,7 @@ class DetailsViewModel(private val store: Store) { SettingsElement.PrivacyPolicy -> { if (state.privacyPolicyUrl != null) it else null } + SettingsElement.AppSettings -> null // TODO: until we implement settings from this screen else -> it } } From 104e365934dbb0ea1d88dea24a84b6bee5b3ac89 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Aug 2022 16:35:39 +0400 Subject: [PATCH 3/5] Updated on 2026-08-14 --- .../details/ui/appsettings/AppSettingsScreen.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index 9b1dd4af03..34018707cf 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -48,7 +48,7 @@ fun AppSettingsScreen( } @Composable -fun AppSettings( +private fun AppSettings( state: AppSettingsScreenState, modifier: Modifier = Modifier, ) { @@ -69,13 +69,15 @@ fun AppSettings( .fillMaxSize(), ) { AppSettingsElement( - state = state, setting = PrivacySetting.SaveWallets, + state = state, + setting = PrivacySetting.SaveWallets, onDialogStateChange = onDialogStateChange, modifier = modifier, ) Spacer(modifier = Modifier.size(32.dp)) AppSettingsElement( - state = state, setting = PrivacySetting.SaveAccessCode, + state = state, + setting = PrivacySetting.SaveAccessCode, onDialogStateChange = onDialogStateChange, modifier = modifier, ) @@ -83,7 +85,7 @@ fun AppSettings( } @Composable -fun AppSettingsElement( +private fun AppSettingsElement( state: AppSettingsScreenState, setting: PrivacySetting, onDialogStateChange: (PrivacySetting?) -> Unit, @@ -103,7 +105,6 @@ fun AppSettingsElement( modifier = modifier .fillMaxWidth() .padding(start = 20.dp), - // .clickable { state.onSettingToggled(element, !checked) }, verticalAlignment = Alignment.CenterVertically, ) { Column( @@ -140,7 +141,7 @@ fun AppSettingsElement( } } -fun onCheckedChange( +private fun onCheckedChange( element: PrivacySetting, enabled: Boolean, onSettingToggled: (PrivacySetting, Boolean) -> Unit, onDialogStateChange: (PrivacySetting?) -> Unit, @@ -154,7 +155,7 @@ fun onCheckedChange( } @Composable -fun SettingsAlertDialog( +private fun SettingsAlertDialog( element: PrivacySetting, onDialogStateChange: (PrivacySetting?) -> Unit, onSettingToggled: (PrivacySetting, Boolean) -> Unit, From 2a812a836ccd53eafea2d674242aa45a7c19c7d1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Aug 2022 16:54:14 +0400 Subject: [PATCH 4/5] Updated on 2026-08-14 --- .../details/ui/common/TangemSwitch.kt | 50 ++++++------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt index 3132bb6fcc..9eda9be886 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt @@ -6,7 +6,6 @@ import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.indication @@ -19,7 +18,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -44,30 +42,22 @@ fun TangemSwitch( val transition = updateTransition(checked, label = "SwitchState") val color by transition.animateColor( transitionSpec = { - tween(200, easing = FastOutLinearInEasing) + tween(durationMillis = 200, easing = FastOutLinearInEasing) }, label = "", - ) { - when (it) { - true -> enabledColor - false -> disabledColor - } + ) { enabled -> + if (enabled) enabledColor else disabledColor } val interactionSource = remember { MutableInteractionSource() } - val clickable = Modifier.clickable( - interactionSource = interactionSource, - indication = null, - ) { - if (!checked) { - onCheckedChange(true) - } else { - onCheckedChange(false) - } - } Box( - modifier = Modifier - .then(clickable) + modifier = modifier + .clickable( + interactionSource = interactionSource, + indication = null, + ) { + onCheckedChange(!checked) + } .indication( interactionSource = MutableInteractionSource(), indication = rememberRipple( @@ -88,27 +78,19 @@ fun TangemSwitch( val roundCardSize = this.maxWidth / 2 val xOffset by transition.animateDp( transitionSpec = { - tween(150, easing = LinearOutSlowInEasing) + tween(durationMillis = 150, easing = LinearOutSlowInEasing) }, label = "xOffset", - ) { state -> - when (state) { - false -> 0.dp - true -> this.maxWidth - roundCardSize - } + ) { enabled -> + if (enabled) this.maxWidth - roundCardSize else 0.dp } - Card( + Box( modifier = Modifier .size(this.maxWidth / 2) .offset(x = xOffset, y = 0.dp) - .padding(3.dp), - shape = RoundedCornerShape(100), - backgroundColor = Color.White, - border = BorderStroke( - if (!checked) 0.5.dp else 0.dp, - color = Color.LightGray, - ), + .padding(3.dp) + .background(color = Color.White, shape = RoundedCornerShape(100)), ) { } } From d9e8be86c7b9ec442351a729390ae58edcf800bb Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Aug 2022 17:05:47 +0400 Subject: [PATCH 5/5] Updated on 2026-08-14 --- .../tap/features/details/ui/common/TangemSwitch.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt index 9eda9be886..bf5268bf6f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt @@ -59,10 +59,9 @@ fun TangemSwitch( onCheckedChange(!checked) } .indication( - interactionSource = MutableInteractionSource(), + interactionSource = interactionSource, indication = rememberRipple( - bounded = true, - radius = 100.dp, + bounded = false, color = Color.Transparent, ), ), @@ -71,7 +70,7 @@ fun TangemSwitch( modifier = modifier .width(size) .height(size / 2) - .indication(MutableInteractionSource(), null) + .indication(interactionSource, null) .background(color = color, shape = RoundedCornerShape(100)), contentAlignment = Alignment.CenterStart, ) { @@ -91,8 +90,7 @@ fun TangemSwitch( .offset(x = xOffset, y = 0.dp) .padding(3.dp) .background(color = Color.White, shape = RoundedCornerShape(100)), - ) { - } + ) } } } \ No newline at end of file