diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index bf673f7f88..f33f020823 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit bf673f7f88ed561cd8827447c4886f80f30b7e23 +Subproject commit f33f020823031f1c532cf5a9d5cf3fbecadcf050 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 a5682e3b5c..eeb216951d 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,14 @@ 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.* +import com.tangem.tap.features.details.ui.walletconnect.dialogs.ApproveWcSessionDialog +import com.tangem.tap.features.details.ui.walletconnect.dialogs.BnbTransactionDialog +import com.tangem.tap.features.details.ui.walletconnect.dialogs.ClipboardOrScanQrDialog +import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialog +import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionDialog import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.ui.dialog.CreateWalletInterruptDialog @@ -17,7 +23,12 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInPro import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog import com.tangem.tap.features.wallet.redux.models.WalletDialog -import com.tangem.tap.features.wallet.ui.dialogs.* +import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendBottomSheetDialog +import com.tangem.tap.features.wallet.ui.dialogs.ChooseTradeActionBottomSheetDialog +import com.tangem.tap.features.wallet.ui.dialogs.RussianCardholdersWarningBottomSheetDialog +import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog +import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog +import com.tangem.tap.features.wallet.ui.dialogs.SimpleOkDialog import com.tangem.tap.features.wallet.ui.wallet.CurrencySelectionDialog import com.tangem.tap.store import com.tangem.wallet.R @@ -139,6 +150,18 @@ 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/compose/TangemTypography.kt b/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt new file mode 100644 index 0000000000..88b088fa6a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt @@ -0,0 +1,71 @@ +package com.tangem.tap.common.compose + +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.sp +import com.tangem.wallet.R + +object TangemTypography { + + val body1 = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto)), + fontSize = 16.0.sp, + letterSpacing = 0.5.sp, + lineHeight = 24.0.sp, + ) + val body2 = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto)), + fontSize = 14.0.sp, + letterSpacing = 0.25.sp, + lineHeight = 20.0.sp, + ) + val button = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto_medium)), + fontSize = 14.0.sp, + letterSpacing = 0.10000000149011612.sp, + lineHeight = 20.0.sp, + ) + val caption = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto)), + fontSize = 12.0.sp, + letterSpacing = 0.4000000059604645.sp, + lineHeight = 16.0.sp, + ) + val headline1 = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto)), + fontSize = 34.0.sp, + letterSpacing = 0.0.sp, + lineHeight = 44.0.sp, + ) + val headline2 = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto_medium)), + fontSize = 24.0.sp, + letterSpacing = 0.18000000715255737.sp, + lineHeight = 32.0.sp, + ) + val headline3 = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto_medium)), + fontSize = 20.0.sp, + letterSpacing = 0.15000000596046448.sp, + lineHeight = 24.0.sp, + ) + val overline = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto_medium)), + fontSize = 10.0.sp, + letterSpacing = 1.5.sp, + lineHeight = 16.0.sp, + ) + val subtitle1 = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto_medium)), + fontSize = 16.0.sp, + letterSpacing = 0.15000000596046448.sp, + lineHeight = 24.0.sp, + ) + val subtitle2 = TextStyle( + fontFamily = FontFamily(Font(R.font.roboto_medium)), + fontSize = 14.0.sp, + letterSpacing = 0.10000000149011612.sp, + lineHeight = 24.0.sp, + ) +} \ 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 f538ff13a3..d4c3c6eca3 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,9 +7,10 @@ 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.DetailsConfirmFragment -import com.tangem.tap.features.details.ui.DetailsFragment -import com.tangem.tap.features.details.ui.DetailsSecurityFragment +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 +import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment import com.tangem.tap.features.details.ui.walletconnect.WalletConnectSessionsFragment import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment @@ -92,8 +93,10 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.Wallet -> WalletFragment() AppScreen.Send -> SendFragment() AppScreen.Details -> DetailsFragment() - AppScreen.DetailsConfirm -> DetailsConfirmFragment() - AppScreen.DetailsSecurity -> DetailsSecurityFragment() + AppScreen.DetailsSecurity -> SecurityModeFragment() + AppScreen.CardSettings -> CardSettingsFragment() + AppScreen.AppSettings -> CardSettingsFragment() + AppScreen.ResetToFactory -> ResetCardFragment() AppScreen.Disclaimer -> DisclaimerFragment() AppScreen.AddTokens -> AddTokensFragment() AppScreen.AddCustomToken -> AddCustomTokenFragment() diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt index 214a034270..0579c7f651 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.redux.navigation +import android.net.Uri import androidx.fragment.app.FragmentActivity import org.rekotlin.Action import java.lang.ref.WeakReference @@ -15,6 +16,8 @@ sealed class NavigationAction : Action { data class OpenUrl(val url: String) : NavigationAction() + data class OpenDocument(val url: Uri) : NavigationAction() + data class Share(val data: String) : NavigationAction() data class ActivityCreated(val activity: WeakReference) : NavigationAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index e8e0168455..8a2f5c5ac1 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.redux.navigation +import android.content.Intent import com.tangem.tap.common.CustomTabsManager import com.tangem.tap.common.extensions.openFragment import com.tangem.tap.common.extensions.popBackTo @@ -32,6 +33,11 @@ val navigationMiddleware: Middleware = { dispatch, state -> CustomTabsManager().openUrl(action.url, it) } } + is NavigationAction.OpenDocument -> { + val intent = Intent(Intent.ACTION_VIEW) + intent.data = action.url + navState?.activity?.get()?.startActivity(intent) + } is NavigationAction.Share -> { navState?.activity?.get()?.let { it.shareText(action.data) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index 1fc6832148..9c6468c049 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -16,7 +16,7 @@ enum class AppScreen { OnboardingNote, OnboardingWallet, OnboardingTwins, OnboardingOther, Wallet, WalletDetails, Send, - Details, DetailsConfirm, DetailsSecurity, + Details, DetailsSecurity, CardSettings, AppSettings, ResetToFactory, AddTokens, AddCustomToken, WalletConnectSessions, QrScan diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 61dbbe7f32..4833079ad0 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -19,6 +19,7 @@ import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.hdWallet.DerivationPath import com.tangem.domain.common.ScanResponse import com.tangem.operations.CommandResponse +import com.tangem.operations.ScanTask import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.pins.CheckUserCodesCommand @@ -144,6 +145,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co initialMessage = Message(context.getString(R.string.initial_message_tap_header))) } + suspend fun scanCard(): CompletionResult { + return runTaskAsyncReturnOnMain( + ScanTask(), + initialMessage = Message(context.getString(R.string.initial_message_tap_header))) + } + suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, ): CompletionResult = diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index 0dc1819018..c627a7de91 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -6,8 +6,8 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result -import com.tangem.domain.common.TapWorkarounds import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.getTwinCardNumber import com.tangem.domain.common.isTangemTwin @@ -15,7 +15,6 @@ import com.tangem.operations.attestation.CardVerifyAndGetInfo import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.features.wallet.redux.Artwork - val Card.remainingSignatures: Int? get() = this.getSingleWallet()?.remainingSignatures @@ -24,7 +23,7 @@ val Card.isWalletDataSupported: Boolean val Card.isMultiwalletAllowed: Boolean get() { - return !isTangemTwin() && !isStart2Coin && !TapWorkarounds.isTangemNote(this) + return !isTangemTwin() && !isStart2Coin && !isTangemNote() && (firmwareVersion >= FirmwareVersion.MultiWalletAvailable || getSingleWallet()?.curve == EllipticCurve.Secp256k1) } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index d57336841e..326b59acf3 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -14,10 +14,14 @@ import com.tangem.common.extensions.guard import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath -import com.tangem.domain.common.* +import com.tangem.domain.common.ProductType +import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isExcluded import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease +import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation +import com.tangem.domain.common.TwinsHelper +import com.tangem.domain.common.isTangemTwins import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.ScanTask @@ -56,7 +60,7 @@ class ScanProductTask( } val commandProcessor = when { - TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor() + card.isTangemNote() -> ScanNoteProcessor() card.isTangemTwins() -> ScanTwinProcessor() else -> ScanWalletProcessor(currenciesRepository, additionalBlockchainsToDerive) } 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 c37a625c54..3ad36caa55 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 @@ -3,12 +3,8 @@ package com.tangem.tap.features.details.redux import com.tangem.blockchain.common.Wallet import com.tangem.common.card.Card import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.TwinCardNumber -import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.termsOfUse.CardTou -import com.tangem.wallet.R import org.rekotlin.Action sealed class DetailsAction : Action { @@ -20,31 +16,23 @@ sealed class DetailsAction : Action { ) : DetailsAction() object ShowDisclaimer : DetailsAction() - data class ReCreateTwinsWallet(val number: TwinCardNumber) : DetailsAction() + object ReCreateTwinsWallet : DetailsAction() sealed class ResetToFactory : DetailsAction() { - object Check : ResetToFactory() - object Proceed : ResetToFactory() { - object NotAllowedByCard : ResetToFactory(), NotificationAction { - override val messageResource = R.string.error_purge_prohibited - } - - object NotEmpty : ResetToFactory(), NotificationAction { - override val messageResource = R.string.details_notification_erase_wallet_not_possible - } - } - - object Confirm : ResetToFactory() - object Cancel : ResetToFactory() + object Proceed : ResetToFactory() + data class Confirm(val confirmed: Boolean) : ResetToFactory() object Failure : ResetToFactory() object Success : ResetToFactory() } object CreateBackup : DetailsAction() + object ScanCard : DetailsAction() + + data class PrepareCardSettingsData(val card: Card) : DetailsAction() + object ResetCardSettingsData : DetailsAction() + sealed class ManageSecurity : DetailsAction() { - data class CheckCurrentSecurityOption(val card: Card) : ManageSecurity() - data class SetCurrentOption(val userCodes: CheckUserCodesResponse) : ManageSecurity() object OpenSecurity : ManageSecurity() data class SelectOption(val option: SecurityOption) : ManageSecurity() object SaveChanges : ManageSecurity() { @@ -52,11 +40,15 @@ sealed class DetailsAction : Action { object Failure : ManageSecurity() } - data class ConfirmSelection(val option: SecurityOption) : ManageSecurity() { - object AlreadySet : ManageSecurity(), NotificationAction { - override val messageResource = R.string.details_notification_security_option_already_active - } - } + object ChangeAccessCode : ManageSecurity() + } + + sealed class AppSettings : DetailsAction() { + data class SwitchPrivacySetting(val allow: Boolean, val setting: PrivacySetting) : + AppSettings() + + data class ConfirmSwitchingSetting(val allow: Boolean, val setting: PrivacySetting) : + AppSettings() } data class ChangeAppCurrency(val fiatCurrency: FiatCurrency) : DetailsAction() 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 efed01f9c3..8cae2e026b 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 @@ -1,11 +1,11 @@ package com.tangem.tap.features.details.redux import com.tangem.common.CompletionResult -import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.TangemSdkError -import com.tangem.operations.pins.CheckUserCodesResponse +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 @@ -28,24 +28,33 @@ import org.rekotlin.Middleware class DetailsMiddleware { private val eraseWalletMiddleware = EraseWalletMiddleware() private val manageSecurityMiddleware = ManageSecurityMiddleware() + private val managePrivacyMiddleware = ManagePrivacyMiddleware() val detailsMiddleware: Middleware = { _, _ -> { next -> { action -> when (action) { is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action) is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) + is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(action) is DetailsAction.ShowDisclaimer -> { - store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer) - store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) + val uri = store.state.detailsState.cardTermsOfUseUrl + if (uri != null) { + store.dispatch(NavigationAction.OpenDocument(uri)) + } else { + store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) + } } is DetailsAction.ReCreateTwinsWallet -> { - val wallet = store.state.walletState.walletManagers.map { it.wallet }.firstOrNull() + val wallet = + store.state.walletState.walletManagers.map { it.wallet }.firstOrNull() if (wallet == null) { store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) } else { if (wallet.hasSendableAmountsOrPendingTransactions()) { - val walletIsNotEmpty = store.state.globalState.resources.strings.walletIsNotEmpty + val walletIsNotEmpty = + store.state.globalState.resources.strings.walletIsNotEmpty store.dispatchNotification(walletIsNotEmpty) } else { store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) @@ -56,7 +65,24 @@ class DetailsMiddleware { is DetailsAction.CreateBackup -> { store.state.detailsState.scanResponse?.let { store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) - store.dispatch(GlobalAction.Onboarding.Start(it, fromHomeScreen = false)) + store.dispatch( + GlobalAction.Onboarding.Start( + it, + fromHomeScreen = false, + ), + ) + } + } + DetailsAction.ScanCard -> { + scope.launch { + when (val result = tangemSdkManager.scanCard()) { + is CompletionResult.Success -> { + val card = result.data + store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) + } + is CompletionResult.Failure -> { + } + } } } } @@ -69,22 +95,11 @@ class DetailsMiddleware { fun handle(action: DetailsAction.ResetToFactory) { when (action) { is DetailsAction.ResetToFactory.Proceed -> { - when (store.state.detailsState.eraseWalletState) { - EraseWalletState.Allowed -> - store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) - EraseWalletState.NotAllowedByCard -> - store.dispatch(DetailsAction.ResetToFactory.Proceed.NotAllowedByCard) - EraseWalletState.NotEmpty -> - store.dispatch(DetailsAction.ResetToFactory.Proceed.NotEmpty) - else -> { /* no-op */ - } + val card = store.state.detailsState.cardSettingsState?.card ?: return + if (card.isTangemTwins()) { + store.dispatch(DetailsAction.ReCreateTwinsWallet) + return } - } - is DetailsAction.ResetToFactory.Cancel -> { - store.dispatch(NavigationAction.PopBackTo()) - } - is DetailsAction.ResetToFactory.Confirm -> { - val card = store.state.detailsState.scanResponse?.card ?: return scope.launch { val result = tangemSdkManager.resetToFactorySettings(card) withContext(Dispatchers.Main) { @@ -98,7 +113,7 @@ class DetailsMiddleware { store.state.globalState.analyticsHandlers?.logCardSdkError( error, Analytics.ActionToLog.PurgeWallet, - card = store.state.detailsState.scanResponse?.card + card = store.state.detailsState.scanResponse?.card, ) } } @@ -115,44 +130,13 @@ class DetailsMiddleware { class ManageSecurityMiddleware { fun handle(action: DetailsAction.ManageSecurity) { when (action) { - is DetailsAction.ManageSecurity.CheckCurrentSecurityOption -> { - if (action.card.firmwareVersion >= FirmwareVersion.IsAccessCodeStatusAvailable) { - // for a card that meets this condition, we can get these statuses from it - val simulatedResponse = CheckUserCodesResponse( - action.card.isAccessCodeSet, action.card.isPasscodeSet ?: false - ) - store.dispatch(DetailsAction.ManageSecurity.SetCurrentOption(simulatedResponse)) - store.dispatch(DetailsAction.ManageSecurity.OpenSecurity) - } else { - scope.launch { - when (val response = tangemSdkManager.checkUserCodes(action.card.cardId)) { - is CompletionResult.Success -> { - store.dispatchOnMain(DetailsAction.ManageSecurity.SetCurrentOption(response.data)) - store.dispatchOnMain(DetailsAction.ManageSecurity.OpenSecurity) - } - is CompletionResult.Failure -> { - } - } - } - } - } is DetailsAction.ManageSecurity.OpenSecurity -> { store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity)) } - is DetailsAction.ManageSecurity.ConfirmSelection -> { - if (action.option != store.state.detailsState.securityScreenState?.currentOption) { - if (action.option == SecurityOption.LongTap) { - store.dispatch(DetailsAction.ManageSecurity.SaveChanges) - } else { - store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) - } - } else { - store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection.AlreadySet) - } - } is DetailsAction.ManageSecurity.SaveChanges -> { val cardId = store.state.detailsState.scanResponse?.card?.cardId - val selectedOption = store.state.detailsState.securityScreenState?.selectedOption + val selectedOption = + store.state.detailsState.cardSettingsState?.manageSecurityState?.selectedOption scope.launch { val result = when (selectedOption) { SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId) @@ -166,9 +150,7 @@ class DetailsMiddleware { selectedOption?.let { store.dispatch(GlobalAction.UpdateSecurityOptions(it)) } - if (selectedOption != SecurityOption.LongTap) { - store.dispatch(NavigationAction.PopBackTo()) - } + store.dispatch(NavigationAction.PopBackTo()) store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success) } is CompletionResult.Failure -> { @@ -178,9 +160,9 @@ class DetailsMiddleware { actionToLog = Analytics.ActionToLog.ChangeSecOptions, parameters = mapOf( AnalyticsParam.NEW_SECURITY_OPTION to - (selectedOption?.name ?: "") + (selectedOption?.name ?: ""), ), - card = store.state.detailsState.scanResponse?.card + card = store.state.detailsState.scanResponse?.card, ) } store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure) @@ -191,9 +173,37 @@ class DetailsMiddleware { } } } + is DetailsAction.ManageSecurity.ChangeAccessCode -> { + val card = store.state.detailsState.cardSettingsState?.card ?: return + scope.launch { + tangemSdkManager.setAccessCode(card.cardId) + } + } else -> { /* no-op */ } } } } -} \ No newline at end of file + + 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)) + } + } + } + } + } +} 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 952e2bb317..99024b0c20 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 @@ -1,16 +1,15 @@ package com.tangem.tap.features.details.redux - import com.tangem.common.card.Card import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.isTangemTwin import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.extensions.isWalletDataSupported import com.tangem.tap.domain.extensions.signedHashesCount -import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions import com.tangem.tap.store -import java.util.EnumSet import org.rekotlin.Action +import java.util.* class DetailsReducer { companion object { @@ -24,14 +23,21 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { val detailsState = state.detailsState return when (action) { is DetailsAction.PrepareScreen -> { - handlePrepareScreen(action, detailsState) + handlePrepareScreen(action) } + is DetailsAction.PrepareCardSettingsData -> { + handlePrepareCardSettingsScreen(action.card, detailsState) + } + is DetailsAction.ResetCardSettingsData -> detailsState.copy(cardSettingsState = null) is DetailsAction.ResetToFactory -> { handleEraseWallet(action, detailsState) } is DetailsAction.ManageSecurity -> { handleSecurityAction(action, detailsState) } + is DetailsAction.AppSettings -> { + handlePrivacyAction(action, detailsState) + } is DetailsAction.ChangeAppCurrency -> detailsState.copy(appCurrency = action.fiatCurrency) @@ -41,49 +47,69 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { private fun handlePrepareScreen( action: DetailsAction.PrepareScreen, - state: DetailsState, ): DetailsState { return DetailsState( scanResponse = action.scanResponse, wallets = action.wallets, - cardInfo = action.scanResponse.card.toCardInfo(), cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card), createBackupAllowed = action.scanResponse.card.backupStatus == Card.BackupStatus.NoBackup, - appCurrency = store.state.globalState.appCurrency + appCurrency = store.state.globalState.appCurrency, ) } +private fun handlePrepareCardSettingsScreen(card: Card, state: DetailsState): DetailsState { + val cardSettingsState = CardSettingsState( + cardInfo = card.toCardInfo(), + manageSecurityState = prepareSecurityOptions(card), + card = card, + resetCardAllowed = isResetToFactoryAllowedByCard(card), + ) + return state.copy(cardSettingsState = cardSettingsState) +} + +private fun prepareSecurityOptions(card: Card): ManageSecurityState { + val securityOption = when { + card.isAccessCodeSet -> { + SecurityOption.AccessCode + } + card.isPasscodeSet == true -> { + SecurityOption.PassCode + } + else -> { + SecurityOption.LongTap + } + } + val allowedSecurityOptions = when { + card.isStart2Coin || card.isTangemNote() -> { + EnumSet.of(SecurityOption.LongTap) + } + card.settings.isBackupAllowed -> { + EnumSet.of(securityOption) + } + else -> prepareAllowedSecurityOptions(card, securityOption) + } + return ManageSecurityState( + currentOption = securityOption, + allowedOptions = allowedSecurityOptions, + selectedOption = securityOption, + ) +} + +private fun isResetToFactoryAllowedByCard(card: Card): Boolean { + val notAllowedByAnyWallet = card.wallets.any { it.settings.isPermanent } + val notAllowedByCard = notAllowedByAnyWallet || + (card.isWalletDataSupported && (!card.isTangemNote() && !card.settings.isBackupAllowed)) + return !notAllowedByCard +} + private fun handleEraseWallet( action: DetailsAction.ResetToFactory, state: DetailsState, ): DetailsState { return when (action) { - DetailsAction.ResetToFactory.Check -> { - val card = state.scanResponse?.card - val notAllowedByAnyWallet = card?.wallets?.any { it.settings.isPermanent } ?: false - val notAllowedByCard = notAllowedByAnyWallet || - (card?.isWalletDataSupported == true && - (!state.scanResponse.isTangemNote() && !state.scanResponse.supportsBackup())) - - val notEmpty = state.wallets.any { it.hasSendableAmountsOrPendingTransactions() } - val eraseWalletState = when { - notAllowedByCard -> EraseWalletState.NotAllowedByCard - notEmpty -> EraseWalletState.NotEmpty - else -> EraseWalletState.Allowed - } - state.copy(eraseWalletState = eraseWalletState) - } - DetailsAction.ResetToFactory.Proceed -> { - if (state.eraseWalletState == EraseWalletState.Allowed) { - state.copy(confirmScreenState = ConfirmScreenState.EraseWallet) - } else { - state - } - } - DetailsAction.ResetToFactory.Cancel -> state.copy(eraseWalletState = null) - DetailsAction.ResetToFactory.Failure -> state.copy(eraseWalletState = null) - DetailsAction.ResetToFactory.Success -> state.copy(eraseWalletState = null) + is DetailsAction.ResetToFactory.Confirm -> + state.copy(cardSettingsState = state.cardSettingsState?.copy(resetConfirmed = action.confirmed)) else -> state } } @@ -92,68 +118,47 @@ private fun handleSecurityAction( action: DetailsAction.ManageSecurity, state: DetailsState, ): DetailsState { return when (action) { - is DetailsAction.ManageSecurity.SetCurrentOption -> { - val securityOption = when { - action.userCodes.isAccessCodeSet -> { - SecurityOption.AccessCode - } - action.userCodes.isPasscodeSet -> { - SecurityOption.PassCode - } - else -> { - SecurityOption.LongTap - } - } - state.copy(securityScreenState = SecurityScreenState(currentOption = securityOption)) - } - is DetailsAction.ManageSecurity.OpenSecurity -> { - val allowedSecurityOptions = when { - state.scanResponse?.card?.isStart2Coin == true || - state.scanResponse?.isTangemNote() == true -> { - EnumSet.of(SecurityOption.LongTap) - } - state.scanResponse?.supportsBackup() == true -> { - EnumSet.of(state.securityScreenState?.currentOption) - } - else -> prepareAllowedSecurityOptions( - state.scanResponse?.card, state.securityScreenState?.currentOption - ) - } - state.copy(securityScreenState = state.securityScreenState?.copy( - allowedOptions = allowedSecurityOptions, - selectedOption = state.securityScreenState.currentOption - )) - } is DetailsAction.ManageSecurity.SelectOption -> { - state.copy(securityScreenState = state.securityScreenState?.copy( - selectedOption = action.option - )) - } - is DetailsAction.ManageSecurity.ConfirmSelection -> { - val confirmScreenState = when (action.option) { - SecurityOption.LongTap -> ConfirmScreenState.LongTap - SecurityOption.PassCode -> ConfirmScreenState.PassCode - SecurityOption.AccessCode -> ConfirmScreenState.AccessCode - } - state.copy(confirmScreenState = confirmScreenState) + val manageSecurityState = state.cardSettingsState?.manageSecurityState?.copy( + selectedOption = action.option, + ) + state.copy(cardSettingsState = state.cardSettingsState?.copy(manageSecurityState = manageSecurityState)) } is DetailsAction.ManageSecurity.SaveChanges.Success -> { // Setting options to show only LongTap from now on for non-twins + val manageSecurityState = state.cardSettingsState?.manageSecurityState?.copy( + currentOption = state.cardSettingsState.manageSecurityState.selectedOption, + allowedOptions = state.scanResponse?.card?.let { + prepareAllowedSecurityOptions( + it, state.cardSettingsState.manageSecurityState.selectedOption, + ) + } ?: EnumSet.of(SecurityOption.LongTap), + ) state.copy( - securityScreenState = state.securityScreenState?.copy( - currentOption = state.securityScreenState.selectedOption, - allowedOptions = state.scanResponse?.card?.let { - prepareAllowedSecurityOptions( - it, state.securityScreenState.selectedOption - ) - } ?: EnumSet.of(SecurityOption.LongTap) - )) + cardSettingsState = state.cardSettingsState?.copy( + manageSecurityState = manageSecurityState, + ), + ) } else -> state } } +private fun handlePrivacyAction( + action: DetailsAction.AppSettings, state: DetailsState, +): DetailsState { + return when (action) { + is DetailsAction.AppSettings.ConfirmSwitchingSetting -> { + when (action.setting) { + PrivacySetting.SAVE_CARDS -> state.copy(saveCards = action.allow) + PrivacySetting.SAVE_ACCESS_CODE -> state.copy(savePasswords = action.allow) + } + } + is DetailsAction.AppSettings.SwitchPrivacySetting -> state + } +} + private fun prepareAllowedSecurityOptions( card: Card?, currentSecurityOption: SecurityOption?, ): EnumSet { @@ -171,7 +176,6 @@ private fun prepareAllowedSecurityOptions( return allowedSecurityOptions } - private fun Card.toCardInfo(): CardInfo { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.issuer.name 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 ee6fd2bb7e..cf0b3852e5 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 @@ -2,9 +2,11 @@ package com.tangem.tap.features.details.redux import android.net.Uri import com.tangem.blockchain.common.Wallet +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 @@ -14,13 +16,13 @@ import kotlin.properties.ReadOnlyProperty data class DetailsState( val scanResponse: ScanResponse? = null, val wallets: List = emptyList(), - val cardInfo: CardInfo? = null, - val eraseWalletState: EraseWalletState? = null, - val confirmScreenState: ConfirmScreenState? = null, - val securityScreenState: SecurityScreenState? = null, + val cardSettingsState: CardSettingsState? = null, val cardTermsOfUseUrl: Uri? = null, + val privacyPolicyUrl: String? = null, val createBackupAllowed: Boolean = false, - val appCurrency: FiatCurrency = FiatCurrency.Default + val appCurrency: FiatCurrency = FiatCurrency.Default, + val saveCards: Boolean = true, + val savePasswords: Boolean = true ) : StateType { // if you do not delegate - the application crashes on startup, @@ -39,13 +41,31 @@ data class CardInfo( val signedHashes: Int, ) -enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty } -enum class ConfirmScreenState { EraseWallet, LongTap, AccessCode, PassCode } -data class SecurityScreenState( +data class CardSettingsState( + val cardInfo: CardInfo, + val card: Card, + val manageSecurityState: ManageSecurityState?, + val resetCardAllowed: Boolean, + val resetConfirmed: Boolean = false, +) + +data class ManageSecurityState( val currentOption: SecurityOption = SecurityOption.LongTap, val selectedOption: SecurityOption = currentOption, val allowedOptions: EnumSet = EnumSet.allOf(SecurityOption::class.java), val buttonProceed: Button = Button(true), ) -enum class SecurityOption { LongTap, PassCode, AccessCode } \ No newline at end of file +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 +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt deleted file mode 100644 index acbe36e4ae..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.tangem.tap.features.details.ui - -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.transition.TransitionInflater -import by.kirich1409.viewbindingdelegate.viewBinding -import com.tangem.tap.common.extensions.getDrawable -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.details.redux.ConfirmScreenState -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 com.tangem.wallet.databinding.FragmentDetailsConfirmBinding -import org.rekotlin.StoreSubscriber - -class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm), - StoreSubscriber { - - private val binding: FragmentDetailsConfirmBinding by viewBinding(FragmentDetailsConfirmBinding::bind) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - } - }) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - 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 onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.toolbar.setNavigationOnClickListener { - store.dispatch(NavigationAction.PopBackTo()) - } - } - - - override fun newState(state: DetailsState) { - if (activity == null || view == null) return - setState(state) - } - - private fun setState(state: DetailsState) = with(binding) { - when (state.confirmScreenState) { - ConfirmScreenState.EraseWallet -> { - toolbar.title = getString(R.string.details_row_title_reset_factory_settings) - tvWarningDescription.text = getString(R.string.details_row_title_reset_factory_settings_warning) - btnConfirm.text = getString(R.string.details_row_title_reset_factory_settings) - btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds( - null, null, getDrawable(R.drawable.ic_send), null - ) - btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) } - } - ConfirmScreenState.LongTap, ConfirmScreenState.AccessCode, - ConfirmScreenState.PassCode -> { - toolbar.title = getString(R.string.details_manage_security_title) - tvWarningDescription.text = getString(R.string.details_security_management_warning) - btnConfirm.text = getString(R.string.common_save_changes) - btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds( - null, null, getDrawable(R.drawable.ic_save), null - ) - btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) } - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt deleted file mode 100644 index 71b6baa710..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.tangem.tap.features.details.ui - -import android.content.Intent -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.transition.TransitionInflater -import by.kirich1409.viewbindingdelegate.viewBinding -import com.tangem.domain.common.getTwinCardIdForUser -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.feedback.FeedbackEmail -import com.tangem.tap.common.feedback.SupportInfo -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.extensions.isMultiwalletAllowed -import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.features.details.redux.DetailsState -import com.tangem.tap.features.details.redux.SecurityOption -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.store -import com.tangem.wallet.R -import com.tangem.wallet.databinding.FragmentDetailsBinding -import org.rekotlin.StoreSubscriber - -class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber { - - private val binding: FragmentDetailsBinding by viewBinding(FragmentDetailsBinding::bind) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - } - }) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - 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 onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.toolbar.setNavigationOnClickListener { - store.dispatch(NavigationAction.PopBackTo()) - } - } - - - override fun newState(state: DetailsState) { - if (activity == null || view == null) return - setState(state) - } - - private fun setState(state: DetailsState) = with (binding) { - - if (state.cardInfo != null) { - val cardId = if (state.isTangemTwins) { - state.scanResponse?.card?.getTwinCardIdForUser() - } else { - state.cardInfo.cardId - } - tvCardId.text = cardId - tvIssuer.text = state.cardInfo.issuer - tvSignedHashes.text = getString( - R.string.details_row_subtitle_signed_hashes_format, - state.cardInfo.signedHashes.toString() - ) - } - - tvSignedHashes.show(!state.isTangemTwins) - tvSignedHashesTitle.show(!state.isTangemTwins) - - tvDisclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) } - - tvCardTou.show(state.cardTermsOfUseUrl != null) - tvCardTou.setOnClickListener { - val intent = Intent(Intent.ACTION_VIEW) - intent.data = state.cardTermsOfUseUrl - startActivity(intent) - } - - if (state.isTangemTwins) { - tvResetToFactory.show() - tvResetToFactory.text = getText(R.string.details_row_title_twins_recreate) - tvResetToFactory.setOnClickListener { - store.dispatch(DetailsAction.ReCreateTwinsWallet(state.twinCardsState.cardNumber!!)) - } - } else { - tvResetToFactory.show() - tvResetToFactory.text = getText(R.string.details_row_title_reset_factory_settings) - tvResetToFactory.setOnClickListener { - store.dispatch(DetailsAction.ResetToFactory.Check) - store.dispatch(DetailsAction.ResetToFactory.Proceed) - } - } - - tvCreateBackup.show(state.createBackupAllowed) - tvCreateBackup.setOnClickListener { - store.dispatch(DetailsAction.CreateBackup) - } - - tvAppCurrency.show(state.scanResponse?.card?.isMultiwalletAllowed != true) - tvAppCurrencyTitle.show(state.scanResponse?.card?.isMultiwalletAllowed != true) - tvAppCurrency.text = state.appCurrency.code - - tvAppCurrency.setOnClickListener { - store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) - } - - tvSendFeedback.setOnClickListener { - store.dispatch(GlobalAction.SendEmail(FeedbackEmail())) - } - - tvWalletConnect.show(state.scanResponse?.card?.isMultiwalletAllowed == true) - tvWalletConnect.setOnClickListener { - store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions)) - } - - tvSupport.setOnClickListener { - store.dispatch(GlobalAction.OpenChat(SupportInfo())) - } - - llManageSecurity.setOnClickListener { - store.dispatch(DetailsAction.ManageSecurity.CheckCurrentSecurityOption(state.scanResponse!!.card)) - } - - val currentSecurity = when (state.securityScreenState?.currentOption) { - SecurityOption.LongTap -> R.string.details_manage_security_long_tap - SecurityOption.PassCode -> R.string.details_manage_security_passcode - SecurityOption.AccessCode -> R.string.details_manage_security_access_code - null -> null - } - currentSecurity?.let { tvSecurity.text = getString(it) } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt deleted file mode 100644 index e9c8b38565..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt +++ /dev/null @@ -1,147 +0,0 @@ -package com.tangem.tap.features.details.ui - -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.transition.TransitionInflater -import by.kirich1409.viewbindingdelegate.viewBinding -import com.tangem.common.card.Card -import com.tangem.tap.common.extensions.show -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.features.details.redux.SecurityOption -import com.tangem.tap.store -import com.tangem.wallet.R -import com.tangem.wallet.databinding.FragmentDetailsSecurityBinding -import org.rekotlin.StoreSubscriber -import java.util.* - -class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security), - StoreSubscriber { - - private val binding: FragmentDetailsSecurityBinding by viewBinding( - FragmentDetailsSecurityBinding::bind - ) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - } - }) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - 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 onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.toolbar.setNavigationOnClickListener { - store.dispatch(NavigationAction.PopBackTo()) - } - setOnClickListeners() - } - - private fun setOnClickListeners() { - binding.btnSaveChanges.setOnClickListener { - store.state.detailsState.securityScreenState?.selectedOption?.let { - store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection(it)) - } - } - } - - override fun newState(state: DetailsState) { - if (activity == null || view == null) return - - selectSecurityOption(state.securityScreenState?.selectedOption) - for (option in SecurityOption.values()) { - setupOption( - option, - state.securityScreenState?.allowedOptions - ?: EnumSet.noneOf(SecurityOption::class.java) - ) - } - binding.tvAccessCodeUnavailableDisclaimer.show( - state.scanResponse?.card?.backupStatus == Card.BackupStatus.NoBackup - ) - } - - private fun selectSecurityOption(securityOption: SecurityOption?) = with(binding) { - radiobuttonLongTap.isChecked = - securityOption == SecurityOption.LongTap && radiobuttonLongTap.isEnabled - radiobuttonPasscode.isChecked = - securityOption == SecurityOption.PassCode && radiobuttonPasscode.isEnabled - radiobuttonAccessCode.isChecked = - securityOption == SecurityOption.AccessCode && radiobuttonAccessCode.isEnabled - } - - private fun setupOption(option: SecurityOption, allowedOptions: EnumSet) { - when (option) { - SecurityOption.LongTap -> { enableLongTap(allowedOptions.contains(option)) } - SecurityOption.PassCode -> { enablePasscode(allowedOptions.contains(option)) } - SecurityOption.AccessCode -> { enableAccessCode(allowedOptions.contains(option)) } - } - } - - private fun enableLongTap(enable: Boolean) = with(binding) { - groupLongTap.show(enable) - val alpha = if (enable) 1f else 0.5f - tvLongTapDescription.alpha = alpha - tvLongTapTitle.alpha = alpha - radiobuttonLongTap.alpha = alpha - radiobuttonLongTap.isEnabled = enable - vLongTap.isClickable = enable - if (enable) { - vLongTap.setOnClickListener { - store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.LongTap)) - } - } - } - - private fun enablePasscode(enable: Boolean) = with(binding) { - groupPasscode.show(enable) - val alpha = if (enable) 1f else 0.5f - tvPasscodeDescription.alpha = alpha - tvPasscodeTitle.alpha = alpha - radiobuttonPasscode.alpha = alpha - radiobuttonPasscode.isEnabled = enable - vPasscode.isClickable = enable - if (enable) { - vPasscode.setOnClickListener { - store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.PassCode)) - } - } - } - - private fun enableAccessCode(enable: Boolean) = with(binding) { - groupAccessCode.show(enable) - val alpha = if (enable) 1f else 0.5f - tvAccessCodeDescription.alpha = alpha - tvAccessCodeTitle.alpha = alpha - radiobuttonAccessCode.alpha = alpha - radiobuttonAccessCode.isEnabled = enable - vAccessCode.isClickable = enable - if (enable) { - vAccessCode.setOnClickListener { - store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.AccessCode)) - } - } - } -} \ 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 new file mode 100644 index 0000000000..048b390954 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt @@ -0,0 +1,3 @@ +package com.tangem.tap.features.details.ui.appsettings + +class AppSettingsFragment \ No newline at end of file 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 new file mode 100644 index 0000000000..060c8cc343 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.features.details.ui.appsettings + +import androidx.compose.runtime.Composable + +@Composable +fun AppSettingsScreen( + +) { +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt new file mode 100644 index 0000000000..a679020e84 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt @@ -0,0 +1,73 @@ +package com.tangem.tap.features.details.ui.cardsettings + +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 org.rekotlin.StoreSubscriber + +class CardSettingsFragment : Fragment(), StoreSubscriber { + + private val viewModel = CardSettingsViewModel(store) + + private var screenState: MutableState = + mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState)) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return ComposeView(requireContext()).apply { + setContent { + isTransitionGroup = true + AppCompatTheme { + CardSettingsScreen( + 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.cardSettingsState) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt new file mode 100644 index 0000000000..92eec4c59b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -0,0 +1,169 @@ +package com.tangem.tap.features.details.ui.cardsettings + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.tap.common.compose.TangemTypography +import com.tangem.tap.features.details.ui.common.DetailsMainButton +import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold +import com.tangem.wallet.R + +@Composable +fun CardSettingsScreen( + state: CardSettingsScreenState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + SettingsScreensScaffold( + content = + if (state.cardDetails == null) { + { CardSettingsReadCard(state.onScanCardClick, modifier = modifier) } + } else { + { CardSettings(state = state, modifier = modifier) } + }, + titleRes = R.string.card_settings_title, + onBackClick = onBackPressed, + ) +} + +@Composable +fun CardSettingsReadCard( + onScanCardClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween, + ) { + + Box( + modifier = modifier + .fillMaxWidth(), + + ) { + Image( + modifier = modifier + .fillMaxWidth() + .padding(start = 80.dp, end = 80.dp, top = 70.dp) + .rotate(-15f), + painter = painterResource(id = R.drawable.card_placeholder_secondary), + contentDescription = "", + contentScale = ContentScale.FillWidth, + ) + Image( + modifier = modifier + .fillMaxWidth() + .padding(start = 60.dp, end = 60.dp) + .rotate(-1f), + painter = painterResource(id = R.drawable.card_placeholder_black), + contentDescription = "", + contentScale = ContentScale.FillWidth, + ) + + } + + + Column( + modifier = modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 32.dp), + ) { + + Text( + text = stringResource(id = R.string.scan_card_settings_title), + color = colorResource(id = R.color.text_primary_1), + style = TangemTypography.headline3, + ) + Spacer(modifier = modifier.size(20.dp)) + Text( + text = stringResource(id = R.string.scan_card_settings_message), + color = colorResource(id = R.color.text_secondary), + style = TangemTypography.body1, + ) + Spacer(modifier = modifier.size(29.dp)) + DetailsMainButton( + title = stringResource(id = R.string.scan_card_settings_button), + onClick = onScanCardClick, + modifier = modifier, + ) + } + } +} + +@Composable +fun CardSettings( + state: CardSettingsScreenState, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth(), + ) { + state.cardDetails?.map { + val paddingBottom = when (it) { + is CardInfo.CardId, is CardInfo.Issuer -> 12.dp + is CardInfo.SignedHashes -> 14.dp + is CardInfo.SecurityMode -> 16.dp + is CardInfo.ChangeAccessCode -> 16.dp + is CardInfo.ResetToFactorySettings -> 28.dp + } + val paddingTop = when (it) { + is CardInfo.CardId -> 0.dp + is CardInfo.Issuer -> 12.dp + is CardInfo.SignedHashes -> 12.dp + is CardInfo.SecurityMode -> 14.dp + is CardInfo.ChangeAccessCode -> 16.dp + is CardInfo.ResetToFactorySettings -> 16.dp + } + Column( + modifier = modifier + .fillMaxWidth() + .clickable( + enabled = it.clickable, + onClick = { state.onElementClick(it) }, + ) + .padding(start = 20.dp, end = 20.dp, bottom = paddingBottom, top = paddingTop), + ) { + val titleColor = if (it.clickable) R.color.text_primary_1 else R.color.text_tertiary + val subtitleColor = if (it.clickable) R.color.text_secondary else R.color.text_tertiary + Text( + text = it.titleRes.resolveReference(), + color = colorResource(id = titleColor), + style = TangemTypography.subtitle1, + ) + Spacer(modifier = modifier.size(4.dp)) + Text( + text = it.subtitle.resolveReference(), + color = colorResource(id = subtitleColor), + style = TangemTypography.body2, + ) + } + + } + + } +} + +@Composable +@Preview +fun CardSettingsPreview() { + CardSettingsScreen(state = CardSettingsScreenState(onScanCardClick = {}) {}, {}) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt new file mode 100644 index 0000000000..e7354df5c5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -0,0 +1,74 @@ +package com.tangem.tap.features.details.ui.cardsettings + +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.res.stringResource +import com.tangem.tap.features.details.redux.SecurityOption +import com.tangem.tap.features.details.ui.securitymode.toTitleRes +import com.tangem.wallet.R + +data class CardSettingsScreenState( + val cardDetails: List? = null, + val onScanCardClick: () -> Unit, + val onElementClick: (CardInfo) -> Unit, +) + +sealed class CardInfo( + val titleRes: TextReference, val subtitle: TextReference, val clickable: Boolean = false, +) { + class CardId(subtitle: String) : CardInfo( + titleRes = TextReference.Res(R.string.details_row_title_cid), + subtitle = TextReference + .Str(subtitle), + ) + + class Issuer(subtitle: String) : CardInfo( + titleRes = TextReference.Res(R.string.details_row_title_issuer), + subtitle = TextReference + .Str(subtitle), + ) + + class SignedHashes(hashes: String) : CardInfo( + titleRes = TextReference.Res(R.string.details_row_title_signed_hashes), + subtitle = TextReference.Res(R.string.details_row_subtitle_signed_hashes_format, hashes), + ) + + class SecurityMode( + securityOption: SecurityOption, + clickable: Boolean, + ) : CardInfo( + titleRes = TextReference.Res(R.string.card_settings_security_mode), + subtitle = TextReference.Res(securityOption.toTitleRes()), + clickable = clickable, + ) + + object ChangeAccessCode : CardInfo( + titleRes = TextReference.Res(R.string.card_settings_change_access_code), + subtitle = TextReference.Res(R.string.card_settings_change_access_code_footer), + clickable = true, + ) + + object ResetToFactorySettings : CardInfo( + titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory), + subtitle = TextReference.Res(R.string.card_settings_reset_card_to_factory_footer), + clickable = true, + ) +} + +sealed interface TextReference { + class Res(@StringRes val id: Int, val formatArgs: List = emptyList()) : TextReference { + constructor(@StringRes id: Int, vararg formatArgs: Any) : this(id, formatArgs.toList()) + } + + class Str(val value: String) : TextReference +} + +@Composable +@ReadOnlyComposable +fun TextReference.resolveReference(): String { + return when (this) { + is TextReference.Res -> stringResource(this.id, *this.formatArgs.toTypedArray()) + is TextReference.Str -> this.value + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt new file mode 100644 index 0000000000..a90886ff81 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -0,0 +1,75 @@ +package com.tangem.tap.features.details.ui.cardsettings + +import com.tangem.domain.common.getTwinCardIdForUser +import com.tangem.domain.common.isTangemTwins +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.CardSettingsState +import com.tangem.tap.features.details.redux.DetailsAction +import org.rekotlin.Store + +class CardSettingsViewModel(private val store: Store) { + + fun updateState(state: CardSettingsState?): CardSettingsScreenState { + + return if (state?.manageSecurityState == null) { + CardSettingsScreenState( + cardDetails = null, + onElementClick = {}, + onScanCardClick = { + store.dispatch(DetailsAction.ScanCard) + }, + ) + } else { + val cardId = if (state.card.isTangemTwins()) { + state.card.getTwinCardIdForUser() + } else { + state.cardInfo.cardId + } + val cardDetails: MutableList = mutableListOf( + CardInfo.CardId(cardId), + CardInfo.Issuer(state.cardInfo.issuer), + ) + + if (!state.card.isTangemTwins()) { + cardDetails.add(CardInfo.SignedHashes(state.cardInfo.signedHashes.toString())) + } + cardDetails.add( + CardInfo.SecurityMode( + securityOption = state.manageSecurityState.currentOption, + clickable = state.manageSecurityState.allowedOptions.size > 1, + ), + ) + if (state.card.backupStatus?.isActive == true && state.card.isAccessCodeSet) { + cardDetails.add(CardInfo.ChangeAccessCode) + } + if (state.resetCardAllowed) { + cardDetails.add(CardInfo.ResetToFactorySettings) + } + + CardSettingsScreenState( + cardDetails = cardDetails, + onScanCardClick = { }, + onElementClick = { + handleClickingItem(it) + }, + ) + } + } + + private fun handleClickingItem(item: CardInfo) { + when (item) { + is CardInfo.ChangeAccessCode -> { + store.dispatch(DetailsAction.ManageSecurity.ChangeAccessCode) + } + is CardInfo.ResetToFactorySettings -> { + store.dispatch(NavigationAction.NavigateTo(AppScreen.ResetToFactory)) + } + is CardInfo.SecurityMode -> { + store.dispatch(DetailsAction.ManageSecurity.OpenSecurity) + } + else -> {} + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt new file mode 100644 index 0000000000..a0117d0fdc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -0,0 +1,112 @@ +package com.tangem.tap.features.details.ui.common + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.tangem.tap.common.compose.TangemTypography +import com.tangem.wallet.R + +@Composable +fun SettingsScreensScaffold( + content: @Composable () -> Unit, + background: @Composable (() -> Unit)? = null, + titleRes: Int, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + BackHandler(true, onBackClick) + + Scaffold( + topBar = { EmptyTopBarWithNavigation(onBackClick = onBackClick) }, + modifier = modifier.systemBarsPadding(), + backgroundColor = colorResource(id = R.color.background_primary), + ) { + + Box(modifier = modifier.fillMaxSize()) { + + background?.invoke() + + Column( + modifier = modifier.fillMaxWidth(), + ) { + Text( + text = stringResource(id = titleRes), + modifier = modifier.padding(start = 20.dp, end = 20.dp, bottom = 52.dp), + style = TangemTypography.headline1, + color = colorResource(id = R.color.text_primary_1), + ) + content() + } + } + + } +} + +@Composable +fun EmptyTopBarWithNavigation( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + TopAppBar( + title = { }, + navigationIcon = + { + IconButton(onClick = onBackClick) { + Icon( + painterResource(id = R.drawable.ic_back), "", + tint = colorResource(id = R.color.icon_primary_1), + ) + } + }, + backgroundColor = colorResource(id = R.color.background_primary), + elevation = 0.dp, + ) +} + +@Composable +fun DetailsMainButton( + title: String, + enabled: Boolean = true, + onClick: (() -> Unit), + modifier: Modifier = Modifier, +) { + Button( + onClick = onClick, + modifier = modifier + .fillMaxWidth() + .height(48.dp), + shape = RoundedCornerShape(12.dp), + enabled = enabled, + colors = ButtonDefaults.buttonColors( + backgroundColor = colorResource(R.color.button_primary), + contentColor = colorResource(R.color.text_primary_2), + disabledBackgroundColor = colorResource(R.color.button_disabled), + disabledContentColor = colorResource(R.color.text_disabled), + ), + ) { + Text(text = title) + Spacer(modifier = modifier.size(8.dp)) + Icon(painter = painterResource(id = R.drawable.ic_tangem), contentDescription = "") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt new file mode 100644 index 0000000000..6674371319 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt @@ -0,0 +1,69 @@ +package com.tangem.tap.features.details.ui.details + +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.DetailsState +import com.tangem.tap.store +import org.rekotlin.StoreSubscriber + +class DetailsFragment : Fragment(), StoreSubscriber { + + private val detailsViewModel = DetailsViewModel(store) + + private var detailsScreenState: MutableState = + mutableStateOf(detailsViewModel.updateState(store.state.detailsState)) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return ComposeView(requireContext()).apply { + setContent { + isTransitionGroup = true + AppCompatTheme { + DetailsScreen( + state = detailsScreenState.value, + onBackPressed = { 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 + detailsScreenState.value = detailsViewModel.updateState(state) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt new file mode 100644 index 0000000000..f6e353e074 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt @@ -0,0 +1,184 @@ +package com.tangem.tap.features.details.ui.details + +import androidx.compose.foundation.clickable +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +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.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.tap.common.compose.TangemTypography +import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold +import com.tangem.wallet.R + +@Composable +fun DetailsScreen( + state: DetailsScreenState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + + SettingsScreensScaffold( + content = { + Content(state = state, modifier = modifier) + }, + titleRes = R.string.details_title, + onBackClick = onBackPressed, + ) +} + +@Composable +fun Content( + state: DetailsScreenState, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(bottom = 40.dp), + ) { + + state.elements.map { + if (it == SettingsElement.WalletConnect) { + WalletConnectDetailsItem( + onItemsClick = state.onItemsClick, modifier = modifier, + ) + } else { + DetailsItem( + item = it, onItemsClick = state.onItemsClick, + modifier = modifier, + ) + } + } + Spacer(modifier = Modifier.weight(1f)) + TangemSocialAccounts(state.tangemLinks, state.onSocialNetworkClick) + Spacer(modifier = Modifier.size(12.dp)) + Text( + text = "${stringResource(id = state.appNameRes)} ${state.tangemVersion}", + style = TangemTypography.caption, + color = colorResource(id = R.color.text_tertiary), + modifier = modifier.padding(start = 16.dp, end = 16.dp), + ) + } +} + +@Composable +fun WalletConnectDetailsItem( + onItemsClick: (SettingsElement) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .height(84.dp) + .fillMaxWidth() + .clickable { onItemsClick(SettingsElement.WalletConnect) }, + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + + ) { + Icon( + painter = painterResource(id = R.drawable.ic_walletconnect), + contentDescription = stringResource(id = R.string.wallet_connect_title), + modifier = modifier.padding(start = 20.dp, end = 20.dp), + tint = colorResource(id = R.color.all_colors_azure), + ) + Column( + modifier = modifier.height(56.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center, + ) { + Text( + text = stringResource(id = R.string.wallet_connect_title), + modifier = modifier.padding(end = 20.dp, bottom = 4.dp), + style = TangemTypography.headline3, + color = colorResource(id = R.color.text_primary_1), + ) + Text( + text = stringResource(id = R.string.wallet_connect_subtitle), + modifier = modifier.padding(end = 20.dp, bottom = 4.dp), + style = TangemTypography.body1, + color = colorResource(id = R.color.text_secondary), + ) + } + + } +} + +@Composable +fun DetailsItem( + item: SettingsElement, + onItemsClick: (SettingsElement) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .height(56.dp) + .fillMaxWidth() + .clickable { onItemsClick(item) }, + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(id = item.iconRes), + contentDescription = stringResource(id = item.titleRes), + modifier = modifier.padding(start = 20.dp, end = 20.dp), + tint = colorResource(id = R.color.icon_secondary), + ) + Text( + text = stringResource(id = item.titleRes), + modifier = modifier.padding(end = 20.dp), + style = TangemTypography.subtitle1, + color = colorResource(id = R.color.text_primary_1), + ) + } +} + +@Composable +fun TangemSocialAccounts( + links: List, + onSocialNetworkClick: (String) -> Unit, + modifier: Modifier = Modifier, +) { + LazyRow( + modifier = modifier.padding(start = 8.dp, end = 8.dp), + ) { + items(links) { + Icon( + painter = painterResource(id = it.iconRes), + contentDescription = "", + modifier = modifier + .padding(8.dp) + .clickable { onSocialNetworkClick(it.url) }, + tint = colorResource(id = R.color.icon_informative), + ) + } + } +} + +@Composable +@Preview +fun Preview() { + DetailsScreen( + state = DetailsScreenState( + elements = SettingsElement.values().toList(), + tangemLinks = TangemSocialAccounts.accountsEn, + tangemVersion = "Tangem 2.14.12 (343)", + onItemsClick = {}, onSocialNetworkClick = {}, + ), + onBackPressed = {}, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt new file mode 100644 index 0000000000..ffb57970b6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -0,0 +1,54 @@ +package com.tangem.tap.features.details.ui.details + +import com.tangem.wallet.R + +data class DetailsScreenState( + val elements: List, + val tangemLinks: List, + val tangemVersion: String, + val onItemsClick: (SettingsElement) -> Unit, + val onSocialNetworkClick: (String) -> Unit, +) { + val appNameRes: Int = R.string.app_name +} + +enum class SettingsElement( + val iconRes: Int, + val titleRes: Int, +) { + WalletConnect(R.drawable.ic_walletconnect, R.string.wallet_connect_title), + Chat(R.drawable.ic_chat, R.string.details_ask_a_question), + SendFeedback(R.drawable.ic_comment, R.string.details_row_title_send_feedback), + CardSettings(R.drawable.ic_card_settings, R.string.card_settings_title), + AppSettings(R.drawable.ic_settings, R.string.app_settings_title), + LinkMoreCards(R.drawable.ic_more_cards, R.string.details_row_title_create_backup), + TermsOfService(R.drawable.ic_text, R.string.disclaimer_title), + PrivacyPolicy(R.drawable.ic_lock, R.string.details_row_privacy_policy), + ; +} + +data class TangemLink( + val iconRes: Int, + val url: String, +) + +object TangemSocialAccounts { + val accountsEn: List = listOf( + TangemLink(R.drawable.ic_telegram, "https://t.me/TangemCards"), + TangemLink(R.drawable.ic_twitter, "https://twitter.com/tangem"), + TangemLink(R.drawable.ic_facebook, "https://m.facebook.com/TangemCards/"), + TangemLink(R.drawable.ic_instagram, "https://instagram.com/tangemcards"), + TangemLink(R.drawable.ic_github, "https://github.com/tangem"), + TangemLink(R.drawable.ic_youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"), + TangemLink(R.drawable.ic_linkedin, "https://www.linkedin.com/company/tangem"), + ) + val accountsRu: List = listOf( + TangemLink(R.drawable.ic_telegram, "https://t.me/tangem_ru"), + TangemLink(R.drawable.ic_twitter, "https://twitter.com/tangem"), + TangemLink(R.drawable.ic_facebook, "https://m.facebook.com/TangemCards/"), + TangemLink(R.drawable.ic_instagram, "https://instagram.com/tangemcards"), + TangemLink(R.drawable.ic_github, "https://github.com/tangem"), + TangemLink(R.drawable.ic_youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"), + TangemLink(R.drawable.ic_linkedin, "https://www.linkedin.com/company/tangem"), + ) +} \ 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 new file mode 100644 index 0000000000..9e6dc602e2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -0,0 +1,91 @@ +package com.tangem.tap.features.details.ui.details + +import com.tangem.tap.common.feedback.FeedbackEmail +import com.tangem.tap.common.feedback.SupportInfo +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.details.redux.DetailsState +import com.tangem.tap.features.home.LocaleRegionProvider +import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE +import com.tangem.wallet.BuildConfig +import org.rekotlin.Store + +class DetailsViewModel(private val store: Store) { + + fun updateState(state: DetailsState): DetailsScreenState { + val settings = SettingsElement.values().mapNotNull { + when (it) { + SettingsElement.WalletConnect -> { + if (state.scanResponse?.card?.isMultiwalletAllowed == true) it else null + } + SettingsElement.LinkMoreCards -> { + if (state.createBackupAllowed) it else null + } + SettingsElement.PrivacyPolicy -> { + if (state.privacyPolicyUrl != null) it else null + } + else -> it + } + } + + return DetailsScreenState( + settings, + tangemLinks = getSocialLinks(), + tangemVersion = getTangemAppVersion(), + onItemsClick = { handleClickingSettingsItem(it) }, + onSocialNetworkClick = { handleSocialNetworkClick(it) }, + ) + } + + private fun handleSocialNetworkClick(url: String) { + store.dispatch(NavigationAction.OpenUrl(url)) + } + + private fun handleClickingSettingsItem(item: SettingsElement) { + when (item) { + SettingsElement.WalletConnect -> { + store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions)) + } + SettingsElement.Chat -> { + store.dispatch(GlobalAction.OpenChat(SupportInfo())) + } + SettingsElement.SendFeedback -> { + store.dispatch(GlobalAction.SendEmail(FeedbackEmail())) + } + SettingsElement.CardSettings -> { + store.dispatch(NavigationAction.NavigateTo(AppScreen.CardSettings)) + } + SettingsElement.AppSettings -> { + store.dispatch(NavigationAction.NavigateTo(AppScreen.AppSettings)) //TODO: To be available later + } + SettingsElement.LinkMoreCards -> { + store.dispatch(DetailsAction.CreateBackup) + } + SettingsElement.TermsOfService -> { + store.dispatch(DetailsAction.ShowDisclaimer) + } + SettingsElement.PrivacyPolicy -> { + // TODO: To be available later + } + } + } + + private fun getSocialLinks(): List { + val locale = LocaleRegionProvider().getRegion() + return if (locale.lowercase() == RUSSIA_COUNTRY_CODE) { + TangemSocialAccounts.accountsRu + } else { + TangemSocialAccounts.accountsEn + } + } + + private fun getTangemAppVersion(): String { + val versionCode: Int = BuildConfig.VERSION_CODE + val versionName: String = BuildConfig.VERSION_NAME + return "$versionName ($versionCode)" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt new file mode 100644 index 0000000000..fa63874859 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt @@ -0,0 +1,69 @@ +package com.tangem.tap.features.details.ui.resetcard + +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.DetailsState +import com.tangem.tap.store +import org.rekotlin.StoreSubscriber + +class ResetCardFragment : Fragment(), StoreSubscriber { + + private val viewModel = ResetCardViewModel(store) + + private var screenState: MutableState = + mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState)) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return ComposeView(requireContext()).apply { + setContent { + isTransitionGroup = true + AppCompatTheme { + ResetCardScreen( + state = screenState.value, + onBackPressed = { 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.cardSettingsState) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt new file mode 100644 index 0000000000..1b8fec5cdb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -0,0 +1,126 @@ +package com.tangem.tap.features.details.ui.resetcard + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.material.Icon +import androidx.compose.material.IconToggleButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.tap.common.compose.TangemTypography +import com.tangem.tap.features.details.ui.common.DetailsMainButton +import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold +import com.tangem.wallet.R + +@Composable +fun ResetCardScreen( + state: ResetCardScreenState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + + SettingsScreensScaffold( + content = { ResetCardView(state = state, modifier = modifier) }, + background = + { Image(painter = painterResource(id = R.drawable.ic_reset_background), contentDescription = "") }, + titleRes = R.string.reset_card_to_factory_navigation_title, + onBackClick = onBackPressed, + ) +} + +@Composable +fun ResetCardView( + state: ResetCardScreenState, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Bottom, + ) { + + Text( + text = stringResource(id = R.string.common_attention), + modifier = modifier.padding(start = 20.dp, end = 20.dp), + style = TangemTypography.headline3, + color = colorResource(id = R.color.text_primary_1), + ) + + Spacer(modifier = modifier.size(24.dp)) + + Text( + text = stringResource(id = R.string.reset_card_to_factory_message), + modifier = modifier.padding(start = 20.dp, end = 20.dp), + style = TangemTypography.body1, + color = colorResource(id = R.color.text_secondary), + ) + + Spacer(modifier = modifier.size(44.dp)) + Row( + modifier = modifier + .fillMaxWidth() + .padding(end = 20.dp), + ) { + IconToggleButton( + checked = state.accepted, + onCheckedChange = state.onAcceptWarningToggleClick, + modifier = modifier.padding(start = 20.dp, end = 20.dp), + ) { + Icon( + painter = painterResource( + if (state.accepted) { + R.drawable.ic_accepted + } else { + R.drawable.ic_unticked + }, + ), + contentDescription = null, + tint = if (state.accepted) { + colorResource(id = R.color.icon_accent) + } else { + colorResource(id = R.color.icon_secondary) + }, + ) + } + Text( + text = stringResource(id = R.string.reset_card_to_factory_warning_message), + style = TangemTypography.body2, + color = colorResource(id = R.color.text_secondary), + ) + } + + Spacer(modifier = modifier.size(32.dp)) + Box( + modifier = modifier + .padding(start = 16.dp, end = 16.dp, bottom = 32.dp), + ) { + DetailsMainButton( + title = stringResource(id = R.string.reset_card_to_factory_button_title), + onClick = state.onResetButtonClick, + enabled = state.resetButtonEnabled, + ) + } + + } +} + +@Composable +@Preview +fun ResetCardScreenPreview( + +) { + ResetCardScreen(state = ResetCardScreenState(onAcceptWarningToggleClick = {}, accepted = true) {}, {}) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt new file mode 100644 index 0000000000..f658eac463 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.features.details.ui.resetcard + +data class ResetCardScreenState( + val accepted: Boolean = false, + val onAcceptWarningToggleClick: (Boolean) -> Unit, + val onResetButtonClick: () -> Unit, +) { + val resetButtonEnabled: Boolean + get() = accepted +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt new file mode 100644 index 0000000000..15f891dba3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.features.details.ui.resetcard + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.details.redux.CardSettingsState +import com.tangem.tap.features.details.redux.DetailsAction +import org.rekotlin.Store + +class ResetCardViewModel(private val store: Store) { + + fun updateState(state: CardSettingsState?): ResetCardScreenState { + return ResetCardScreenState( + accepted = state?.resetConfirmed ?: false, + onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) }, + onResetButtonClick = { store.dispatch(DetailsAction.ResetToFactory.Proceed) }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt new file mode 100644 index 0000000000..96d55a808a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt @@ -0,0 +1,69 @@ +package com.tangem.tap.features.details.ui.securitymode + +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.DetailsState +import com.tangem.tap.store +import org.rekotlin.StoreSubscriber + +class SecurityModeFragment : Fragment(), StoreSubscriber { + + private val viewModel = SecurityModeViewModel(store) + + private var screenState: MutableState = + mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState?.manageSecurityState)) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return ComposeView(requireContext()).apply { + setContent { + isTransitionGroup = true + AppCompatTheme { + SecurityModeScreen( + state = screenState.value, + onBackPressed = { 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.cardSettingsState?.manageSecurityState) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt new file mode 100644 index 0000000000..1f8692b350 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt @@ -0,0 +1,125 @@ +package com.tangem.tap.features.details.ui.securitymode + +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.selection.selectable +import androidx.compose.material.RadioButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.tap.common.compose.TangemTypography +import com.tangem.tap.features.details.redux.SecurityOption +import com.tangem.tap.features.details.ui.common.DetailsMainButton +import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold +import com.tangem.wallet.R + +@Composable +fun SecurityModeScreen( + state: SecurityModeScreenState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + SettingsScreensScaffold( + content = { SecurityModeOptions(state = state, modifier = modifier) }, + titleRes = R.string.card_settings_security_mode, + onBackClick = onBackPressed, + ) +} + +@Composable +fun SecurityModeOptions( + state: SecurityModeScreenState, + modifier: Modifier = Modifier, +) { + + Column( + modifier = modifier + .fillMaxSize() + .padding(bottom = 28.dp), + verticalArrangement = Arrangement.SpaceBetween, + ) { + state.availableOptions.map { + SecurityOption(option = it, state = state, modifier = modifier) + } + + Spacer(modifier = Modifier.weight(1f)) + + DetailsMainButton( + title = stringResource(id = R.string.common_save_changes), + enabled = state.isSaveChangesEnabled, + onClick = state.onSaveChangesClicked, + modifier = modifier + .padding(start = 20.dp, end = 20.dp), + ) + } +} + +@Composable +fun SecurityOption( + option: SecurityOption, state: SecurityModeScreenState, + modifier: Modifier, +) { + val selected = option == state.selectedSecurityMode + + val title = option.toTitleRes() + + val subtitle = when (option) { + SecurityOption.LongTap -> R.string.details_manage_security_long_tap_description + SecurityOption.PassCode -> R.string.details_manage_security_passcode_description + SecurityOption.AccessCode -> R.string.details_manage_security_access_code_description + } + + Row( + modifier = modifier + .fillMaxWidth() + .selectable( + selected = selected, onClick = { state.onNewModeSelected(option) }, + ) + .padding(start = 20.dp, end = 20.dp, bottom = 32.dp), + ) { + + RadioButton( + selected = selected, onClick = null, + modifier = modifier.padding(end = 20.dp), + ) + + Column { + Text( + text = stringResource(id = title), + style = TangemTypography.subtitle1, + color = colorResource(id = R.color.text_primary_1), + ) + Spacer(modifier = modifier.size(4.dp)) + Text( + text = stringResource(id = subtitle), + style = TangemTypography.body2, + color = colorResource(id = R.color.text_secondary), + ) + } + } +} + +@Preview +@Composable +fun SecurityModeScreenPreview() { + SecurityModeScreen( + state = SecurityModeScreenState( + availableOptions = SecurityOption.values().toList(), + selectedSecurityMode = SecurityOption.LongTap, + isSaveChangesEnabled = false, + onNewModeSelected = {}, + onSaveChangesClicked = {}, + ), + onBackPressed = {}, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreenState.kt new file mode 100644 index 0000000000..97482c7c01 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreenState.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.features.details.ui.securitymode + +import com.tangem.tap.features.details.redux.SecurityOption +import com.tangem.wallet.R + +data class SecurityModeScreenState( + val availableOptions: List, + val selectedSecurityMode: SecurityOption, + val isSaveChangesEnabled: Boolean, + val onNewModeSelected: (SecurityOption) -> Unit, + val onSaveChangesClicked: () -> Unit, +) + +fun SecurityOption.toTitleRes(): Int { + return when (this) { + SecurityOption.LongTap -> R.string.details_manage_security_long_tap + SecurityOption.PassCode -> R.string.details_manage_security_passcode + SecurityOption.AccessCode -> R.string.details_manage_security_access_code + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt new file mode 100644 index 0000000000..b25277864d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.features.details.ui.securitymode + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.details.redux.ManageSecurityState +import com.tangem.tap.features.details.redux.SecurityOption +import org.rekotlin.Store + +class SecurityModeViewModel(val store: Store) { + + fun updateState(state: ManageSecurityState?): SecurityModeScreenState { + if (state == null) return SecurityModeScreenState( + availableOptions = emptyList(), + selectedSecurityMode = SecurityOption.LongTap, + isSaveChangesEnabled = false, + onNewModeSelected = {}, + onSaveChangesClicked = {}, + ) + return SecurityModeScreenState( + availableOptions = state.allowedOptions.toList(), + selectedSecurityMode = state.selectedOption, + isSaveChangesEnabled = state.selectedOption != state.currentOption, + onNewModeSelected = { store.dispatch(DetailsAction.ManageSecurity.SelectOption(it)) }, + onSaveChangesClicked = { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt new file mode 100644 index 0000000000..0d77c0e50c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.features.details.ui.walletconnect + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun WalletConnectScreen( + + modifier: Modifier = Modifier, +) { +} + + +//data class WalletConnectScreenState( +// sessions: WcSessionScreenState +//) +// +//data class WcSessionScreenState( +// val icon: Bitmap, +// +//) \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_accepted.xml b/app/src/main/res/drawable/ic_accepted.xml new file mode 100644 index 0000000000..b94c8e5cf4 --- /dev/null +++ b/app/src/main/res/drawable/ic_accepted.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_alert.xml b/app/src/main/res/drawable/ic_alert.xml new file mode 100644 index 0000000000..befebf5833 --- /dev/null +++ b/app/src/main/res/drawable/ic_alert.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 0000000000..f42551a533 --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_backup.xml b/app/src/main/res/drawable/ic_backup.xml new file mode 100644 index 0000000000..b377ea3f2a --- /dev/null +++ b/app/src/main/res/drawable/ic_backup.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_card_settings.xml b/app/src/main/res/drawable/ic_card_settings.xml new file mode 100644 index 0000000000..c9ac2c388d --- /dev/null +++ b/app/src/main/res/drawable/ic_card_settings.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_cards.xml b/app/src/main/res/drawable/ic_cards.xml new file mode 100644 index 0000000000..9b226dff8e --- /dev/null +++ b/app/src/main/res/drawable/ic_cards.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_chat.xml b/app/src/main/res/drawable/ic_chat.xml new file mode 100644 index 0000000000..465c9f741d --- /dev/null +++ b/app/src/main/res/drawable/ic_chat.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_chevron.xml b/app/src/main/res/drawable/ic_chevron.xml new file mode 100644 index 0000000000..31e861dd95 --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml index 16d6d37dd9..d276f8584c 100644 --- a/app/src/main/res/drawable/ic_close.xml +++ b/app/src/main/res/drawable/ic_close.xml @@ -1,10 +1,5 @@ - - + + diff --git a/app/src/main/res/drawable/ic_comment.xml b/app/src/main/res/drawable/ic_comment.xml new file mode 100644 index 0000000000..53e56127b2 --- /dev/null +++ b/app/src/main/res/drawable/ic_comment.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_facebook.xml b/app/src/main/res/drawable/ic_facebook.xml new file mode 100644 index 0000000000..9343c46fe7 --- /dev/null +++ b/app/src/main/res/drawable/ic_facebook.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml new file mode 100644 index 0000000000..e2c70a78fa --- /dev/null +++ b/app/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml new file mode 100644 index 0000000000..57bbd79618 --- /dev/null +++ b/app/src/main/res/drawable/ic_github.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_instagram.xml b/app/src/main/res/drawable/ic_instagram.xml new file mode 100644 index 0000000000..844639d9e3 --- /dev/null +++ b/app/src/main/res/drawable/ic_instagram.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_linkedin.xml b/app/src/main/res/drawable/ic_linkedin.xml new file mode 100644 index 0000000000..36600f605d --- /dev/null +++ b/app/src/main/res/drawable/ic_linkedin.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000000..51ac9d64af --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000000..2ea52e3dbc --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_cards.xml b/app/src/main/res/drawable/ic_more_cards.xml new file mode 100644 index 0000000000..0cc559021b --- /dev/null +++ b/app/src/main/res/drawable/ic_more_cards.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_plus.xml b/app/src/main/res/drawable/ic_plus.xml new file mode 100644 index 0000000000..e8b4a25be1 --- /dev/null +++ b/app/src/main/res/drawable/ic_plus.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_reset.xml b/app/src/main/res/drawable/ic_reset.xml new file mode 100644 index 0000000000..8c0845dbb0 --- /dev/null +++ b/app/src/main/res/drawable/ic_reset.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_reset_background.xml b/app/src/main/res/drawable/ic_reset_background.xml new file mode 100644 index 0000000000..8ce3137945 --- /dev/null +++ b/app/src/main/res/drawable/ic_reset_background.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml index 476424f743..1e4cd949d6 100644 --- a/app/src/main/res/drawable/ic_scan.xml +++ b/app/src/main/res/drawable/ic_scan.xml @@ -1,16 +1,8 @@ - - - - + + + + + diff --git a/app/src/main/res/drawable/ic_security.xml b/app/src/main/res/drawable/ic_security.xml new file mode 100644 index 0000000000..ba3c214505 --- /dev/null +++ b/app/src/main/res/drawable/ic_security.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml index 41a82ede8b..46fe7fa730 100644 --- a/app/src/main/res/drawable/ic_settings.xml +++ b/app/src/main/res/drawable/ic_settings.xml @@ -1,10 +1,5 @@ - - + + diff --git a/app/src/main/res/drawable/ic_tangem.xml b/app/src/main/res/drawable/ic_tangem.xml new file mode 100644 index 0000000000..5140d0444c --- /dev/null +++ b/app/src/main/res/drawable/ic_tangem.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_telegram.xml b/app/src/main/res/drawable/ic_telegram.xml new file mode 100644 index 0000000000..5d2351d73e --- /dev/null +++ b/app/src/main/res/drawable/ic_telegram.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_text.xml b/app/src/main/res/drawable/ic_text.xml new file mode 100644 index 0000000000..3e1f8a08a9 --- /dev/null +++ b/app/src/main/res/drawable/ic_text.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_trash.xml b/app/src/main/res/drawable/ic_trash.xml new file mode 100644 index 0000000000..38e732373b --- /dev/null +++ b/app/src/main/res/drawable/ic_trash.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_twitter.xml b/app/src/main/res/drawable/ic_twitter.xml new file mode 100644 index 0000000000..de09724525 --- /dev/null +++ b/app/src/main/res/drawable/ic_twitter.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_unticked.xml b/app/src/main/res/drawable/ic_unticked.xml new file mode 100644 index 0000000000..8eb2fe34df --- /dev/null +++ b/app/src/main/res/drawable/ic_unticked.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_walletconnect.xml b/app/src/main/res/drawable/ic_walletconnect.xml new file mode 100644 index 0000000000..925a858a8a --- /dev/null +++ b/app/src/main/res/drawable/ic_walletconnect.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_youtube.xml b/app/src/main/res/drawable/ic_youtube.xml new file mode 100644 index 0000000000..81eb8d7e8c --- /dev/null +++ b/app/src/main/res/drawable/ic_youtube.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/font/roboto.ttf b/app/src/main/res/font/roboto.ttf new file mode 100644 index 0000000000..ddf4bfacb3 Binary files /dev/null and b/app/src/main/res/font/roboto.ttf differ diff --git a/app/src/main/res/font/roboto_medium.ttf b/app/src/main/res/font/roboto_medium.ttf new file mode 100644 index 0000000000..ac0f908b9c Binary files /dev/null and b/app/src/main/res/font/roboto_medium.ttf differ diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml deleted file mode 100644 index 2171b91e61..0000000000 --- a/app/src/main/res/layout/fragment_details.xml +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_details_security.xml b/app/src/main/res/layout/fragment_details_security.xml deleted file mode 100644 index 25d17cf8cc..0000000000 --- a/app/src/main/res/layout/fragment_details_security.xml +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index e859d46e47..cb51a59e81 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -61,4 +61,36 @@ %s network not found. Please, add it first and try again. This network is not supported. Please select another network. %s network + + Card Settings + Security Mode + Selected application protection method + Change access code + Access code will be changed on this card only + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. + Scan Card + App settings + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem app + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + + Link More Cards + You can synchronize up to three cards into one wallet. It can only be done once. + + Privacy policy + + Reset to factory settings + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml index c4e1f946db..db5cb3bd3d 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -61,4 +61,36 @@ %s network not found. Please, add it first and try again. This network is not supported. Please select another network. %s network + + Card Settings + Security Mode + Selected application protection method + Change access code + Access code will be changed on this card only + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. + Scan Card + App settings + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem app + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + + Link More Cards + You can synchronize up to three cards into one wallet. It can only be done once. + + Privacy policy + + Reset to factory settings + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index bb1cf7be30..aa671c19a4 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -62,4 +62,36 @@ This network is not supported. Please select another network. %s network + + Card Settings + Security Mode + Selected application protection method + Change access code + Access code will be changed on this card only + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. + Scan Card + App settings + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem app + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + + Link More Cards + You can synchronize up to three cards into one wallet. It can only be done once. + + Privacy policy + + Reset to factory settings + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000000..9be291c700 --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e90d92bbff..9b6b5d5725 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -307,7 +307,6 @@ Активный. Количество резервных карт: %d Связано. Количество резервных карт: %d Нет резервной копии - Создать резервную копию У Вас есть прерванное резервное копирование. Хотите возобновить? Да, возобновить Отказаться @@ -323,7 +322,6 @@ Подготовьте резервную карту с номером %s Подготовьте основную карту с номером %s Сброс до заводских настроек - Это действие необратимо. Если после сброса карты кто-то отправит на нее средства, то Вы не сможете их вывести. Код доступа доступен только для карт с резервной копией Сеть Solana взымает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы использовать его бесплатно. %1s (%2s) diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index a7ebbef56b..9c4dc47dd3 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -76,7 +76,38 @@ У вас есть карта банка другой страны или платежной системы UnionPay? Да - Задать вопрос - + Чат Tangem Bot + + Настройки карты + Тип безопасности + Выбранный способ защиты приложения + Смена кода доступа + Код доступа будет изменен только на данной карте + Приготовьте свою карту + Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. + Сканировать + Настройки приложения + Включите биометрическую аутентификацию + Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem + Cохранение кошелька + Если эта опция включена, кошельки всех отсканированных карт будут привязываться к приложению и при открытии приложения будет необходимо пройти биометрическую аутентификацию. Подпись транзакций все так же потребует карту. + Сохранение кода доступа + Если эта опция включена, пароли от карты будут хранится на телефоне в зашифрованном виде и при работе с картой вместо пароля будет запрашиваться биометрическая аутентификация + При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены. + Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. + WalletConnect + Подключение к Dapps + + Добавить еще карты + Вы можете объеденить до трех карт в одним кошельке. Это можно сделать только один раз. + + Privacy policy + + Сброс к заводским настройкам + Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа + Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку + Сбросить карту + Сброс к заводским настройкам + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 70a942b055..b00c6603a8 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -64,4 +64,82 @@ #CA0F03 #EFEFF0 + + #DE1010 + #007AFF + #000000 + #0029FF + #919191 + #656565 + #494949 + #3B3B3B + #303030 + #1E1E1E + #06311F + #ED7979 + #F5F5F5 + #EBEBEB + #D3D3D3 + #C9C9C9 + #B0B0B0 + #A3EBCC + #1ACE80 + #FFB71B + #FFFFFF + + + #000000 + #FFFFFF + #FFFFFF + #F5F5F5 + + + + #EBEBEB + #1ACE80 + #A3EBCC + #1E1E1E + #EBEBEB + + + #1ACE80 + #FFFFFF + #EBEBEB + + + + #EBEBEB + #F5F5F5 + + + + #1ACE80 + #FFB71B + #C9C9C9 + #B0B0B0 + #000000 + #FFFFFF + #656565 + #DE1010 + + + + #EBEBEB + #663B3B3B + #FFFFFF + + + + #1ACE80 + #FFB71B + #FFFFFF + #C9C9C9 + #1E1E1E + #FFFFFF + #656565 + #919191 + #DE1010 + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index f496ca1548..44984bde11 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -77,7 +77,39 @@ Yes No - Ask a question - + Chat Tangem Bot + + + Card Settings + Security Mode + Selected application protection method + Change access code + Access code will be changed on this card only + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. + Scan Card + App settings + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem app + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + + Link More Cards + You can synchronize up to three cards into one wallet. It can only be done once. + + Privacy policy + + Reset to factory settings + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 4a49d725cc..119c258d60 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -131,7 +131,7 @@ There are only %s signatures available on this card. You must withdraw all of your funds. //Wallet Connect - Wallet Connect + WalletConnect Wallet Connect Sessions WalletConnect session opened with %s Ooops. No Sessions. @@ -283,7 +283,6 @@ Active. Backup cards count: %d Linked. Backup cards count: %d No backup - Create backup You have an interrupted backup. Do you want to resume? Yes, resume Discard @@ -299,7 +298,6 @@ Prepare the backup card with number %s Prepare the primary card with number %s Reset to factory settings - This action is irreversible. If, after resetting the card, someone sends funds to it, then you will not be able to withdraw them. Access code is available only for cards with a backup. diff --git a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt index 644cc7525e..e273a4dae2 100644 --- a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt +++ b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt @@ -42,8 +42,7 @@ object TapWorkarounds { return false } - @Deprecated("Use ScanResponse.isTangemNote") - fun isTangemNote(card: Card): Boolean = tangemNoteBatches.contains(card.batchId) + fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) fun isTangemWalletBatch(card: Card): Boolean = tangemWalletBatches.contains(card.batchId)