diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index a4dbc85627..babd88d801 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ 11B350CB4E7C006C26AE5FB3 /* EnabledWalletStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35763ED14419B9EE4C6F9 /* EnabledWalletStorage.swift */; }; 11B350D00FA0A18EF540C945 /* BottomSingleSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35EF3688D60C8E6823267 /* BottomSingleSelectorViewController.swift */; }; 11B350D6CBB602F510882F1E /* WalletConnectRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35CD5EBBB403D46BDEF0B /* WalletConnectRequest.swift */; }; + 11B350D931616C0C296B6082 /* DuressModeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F5B696CF0677865FA2C /* DuressModeViewModel.swift */; }; 11B350DBF23645FE8641A193 /* FiatService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350EE043CD96E484F9524 /* FiatService.swift */; }; 11B350DCAD95F45727869A56 /* EvmMethodLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E3F01D5A5CFE5A4E94B /* EvmMethodLabel.swift */; }; 11B350DD9B9E483A88C064D2 /* SimpleActivateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A3B86D99FBB036C74C7 /* SimpleActivateView.swift */; }; @@ -140,6 +141,7 @@ 11B35183F103B22537F9F9DF /* ManageWalletsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3550ED151B4C6824B9779 /* ManageWalletsViewModel.swift */; }; 11B351856787DD75A41861B6 /* CoinInvestorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A0F912218FEC2A196C0 /* CoinInvestorsViewController.swift */; }; 11B3518578A4531274D73A21 /* UnlinkWatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35592753D3F2A9CCA5809 /* UnlinkWatchViewController.swift */; }; + 11B35189844EFD9E4B58269D /* PageDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B351FDDBEF227E161F6A0E /* PageDescription.swift */; }; 11B3518B594ECB199242C5CB /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352E52084020190C21D8C /* InputView.swift */; }; 11B3518BEA8865CADA5DA684 /* LaunchScreenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BEEC0AB0B09C7E4209A /* LaunchScreenManager.swift */; }; 11B3518C9B837CB6C740AABB /* CreatePasscodeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352951AD68524C33022C0 /* CreatePasscodeModule.swift */; }; @@ -270,6 +272,7 @@ 11B3530088E70831A648EC63 /* CexDepositNetworkRaw.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3502198C667A95C21DCF3 /* CexDepositNetworkRaw.swift */; }; 11B35307AE70D7996F483DAE /* InputStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B353BA87FDCB1BCBA92E61 /* InputStackView.swift */; }; 11B353096900F82EDF084F3B /* SetPasscodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F99E093B7DDB24D39C9 /* SetPasscodeViewModel.swift */; }; + 11B35309CE9FBDA200067C4F /* ActiveAccount_v_0_36.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3528DDD55DDA1BAC2BADB /* ActiveAccount_v_0_36.swift */; }; 11B3530E7755A4882F7E0C0A /* SelectorModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B353C09FE554834C760777 /* SelectorModule.swift */; }; 11B35311CEEC40EA3089293D /* SubscriptionInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B351CD91AE01747F66E746 /* SubscriptionInfoViewController.swift */; }; 11B35313AC2978EE7DBC3EA9 /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B354712C102B954BCEE258 /* FilterView.swift */; }; @@ -301,6 +304,7 @@ 11B35355FF6481B50773C868 /* NftCollectionOverviewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C4F17D4CC8E89F7DC3B /* NftCollectionOverviewService.swift */; }; 11B35356AA508971CA689290 /* CoinAnalyticsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3540B41309A446C1DDB83 /* CoinAnalyticsViewController.swift */; }; 11B35357032B368120BA1C06 /* TestNetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35EE072CE5471B0DFF841 /* TestNetManager.swift */; }; + 11B353577381981235B90A82 /* ListStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356EF92FFD23F4385A991 /* ListStyle.swift */; }; 11B3535ABA61D7BD84EE500C /* NftEventMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352BACB38FE566F6F575B /* NftEventMetadata.swift */; }; 11B3535C10D649F8CD1BCDAF /* HsLabelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35EDE31BA3EF80F78859A /* HsLabelProvider.swift */; }; 11B3535EF39FCD22171AC21C /* FaqService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352B4E116BEC01B972A39 /* FaqService.swift */; }; @@ -320,6 +324,7 @@ 11B3539B3634BF7B3B1B9061 /* DescriptionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3515BDAF15B6F7EEAB609 /* DescriptionCell.swift */; }; 11B3539E833ABB2D6F696916 /* BlockchainSettingRecord_v_0_24.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3526A40F07F6C8E77BEF9 /* BlockchainSettingRecord_v_0_24.swift */; }; 11B353A07F9259765D90F3BA /* NftService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355129D9F61172FCAB8C0 /* NftService.swift */; }; + 11B353A8B524526D20195D37 /* DuressModeIntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35420841B4F9B886A6507 /* DuressModeIntroView.swift */; }; 11B353AA4AFFB020A68E09B6 /* AccountFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C4D6F474C2EB3687EB4 /* AccountFactory.swift */; }; 11B353AD1FE351B86CA538EA /* RestoreMnemonicViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3598A8D7D1A8D5E17BE15 /* RestoreMnemonicViewModel.swift */; }; 11B353AE1D1D9A8E5CF8E7A2 /* BaseTransactionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35935EF1B2237E0289669 /* BaseTransactionsViewModel.swift */; }; @@ -365,6 +370,7 @@ 11B3542D112D915738AB1045 /* SimpleActivateModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35178643181B9CE1D6C8B /* SimpleActivateModule.swift */; }; 11B35434C09F1E3818DC857B /* ReceiveDerivationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B357EEC98939F9C7AA3271 /* ReceiveDerivationViewModel.swift */; }; 11B3543A420A23064B056925 /* ReceiveAddressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3532A1DC90E3D0E3403F8 /* ReceiveAddressViewModel.swift */; }; + 11B3543A7A9EB1E0E0E8753D /* DuressModeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F5B696CF0677865FA2C /* DuressModeViewModel.swift */; }; 11B35440714FF3AAF24542D4 /* WalletCexElementService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C7CCC41913AA8D36CBC /* WalletCexElementService.swift */; }; 11B35444DADD43277D30DFE6 /* AmountData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B617A9CE668EEF4978B /* AmountData.swift */; }; 11B354460024FA6EDB8B27DC /* BackupVerifyWordsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35FA70D9570CB2708E1CA /* BackupVerifyWordsService.swift */; }; @@ -408,6 +414,7 @@ 11B354D754D2E2312223F9C0 /* ReceiveSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3580D6EDF1BB135965CC5 /* ReceiveSelectorViewController.swift */; }; 11B354D8DCBDAA82A6C51205 /* ManageAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355949F6D268EF1977DC9 /* ManageAccountViewModel.swift */; }; 11B354DB9BD0F91CFF4EB9C6 /* TransactionsViewItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B357D89546EBA13B01A1ED /* TransactionsViewItemFactory.swift */; }; + 11B354DC983042AD922339A6 /* DuressModeSelectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3554BC96C9C24C24CC2B0 /* DuressModeSelectView.swift */; }; 11B354DEFBE83147106A5FFE /* CexAssetRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35195509787CD52A6873A /* CexAssetRecord.swift */; }; 11B354E72D9BDF04E75C8748 /* WalletHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3597A2B0B529BE97F85C8 /* WalletHeaderCell.swift */; }; 11B354E85FD7EE82D34FD1C4 /* BalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BB7206DA0EDBB43C814 /* BalanceCell.swift */; }; @@ -450,6 +457,7 @@ 11B3555CA9B2F01358E055BE /* UnlinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35419B0C846238DDC50F3 /* UnlinkViewModel.swift */; }; 11B3555F968EFA0AF7D1DF46 /* WalletElementServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F4B9522FCCD91582AAF /* WalletElementServiceFactory.swift */; }; 11B35567A098667C9955F1F9 /* RecoveryPhraseModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3517B0E763E2C217654A7 /* RecoveryPhraseModule.swift */; }; + 11B355696714B5570748EF03 /* AccountRecord_v_0_36.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356A734526DECD9606A66 /* AccountRecord_v_0_36.swift */; }; 11B3556B3FAAA6B1FA63C8B1 /* TransactionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B351167EBAE5FE1AA45882 /* TransactionsViewModel.swift */; }; 11B3556B4E9B6E54C93205D6 /* CexCoinSelectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352F071CE0EF1505A8380 /* CexCoinSelectViewController.swift */; }; 11B3556C12B91FD86A72A193 /* LitecoinAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356861F703A5A5C6630B6 /* LitecoinAdapter.swift */; }; @@ -504,7 +512,7 @@ 11B3562466F0ADD109244158 /* NftCollectionAssetsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35100DD6E2DBF905FD19B /* NftCollectionAssetsModule.swift */; }; 11B3562D78E70F5F14B81B3A /* CexWithdrawNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3572F134D41A670EE9244 /* CexWithdrawNetwork.swift */; }; 11B3562EE896D758066FEECB /* CexDepositNetworkSelectService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35850DF16D11D45C44A60 /* CexDepositNetworkSelectService.swift */; }; - 11B35631BD5C6570C9359BEC /* RowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BAA4EA85B4A3A173498 /* RowButton.swift */; }; + 11B35631BD5C6570C9359BEC /* RowButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BAA4EA85B4A3A173498 /* RowButtonStyle.swift */; }; 11B35631E5455A54854A2A6F /* RestoreMnemonicService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35D55DCC92BED4FA87CA0 /* RestoreMnemonicService.swift */; }; 11B356330572A72E56DC2FEA /* PasscodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B359FC4FE023FBA0E1726C /* PasscodeView.swift */; }; 11B35633B952154A098532A4 /* NftCollectionModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35708A630D70385F34A8B /* NftCollectionModule.swift */; }; @@ -536,6 +544,7 @@ 11B356A19A721D3557D7213C /* CoinReportsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E767DA0B5D7C0DAF203 /* CoinReportsViewModel.swift */; }; 11B356A2666F52C272B4E465 /* WalletTokenListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35136653741E9703E61DE /* WalletTokenListViewModel.swift */; }; 11B356A300ED689602ACD35D /* BalanceCoinIconHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3560C1FC3F73833FA4439 /* BalanceCoinIconHolder.swift */; }; + 11B356A35A5981DD231E580C /* ListStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356EF92FFD23F4385A991 /* ListStyle.swift */; }; 11B356A4B22FA16BE27AFAB1 /* LogRecordStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35822E26E7298100CD69D /* LogRecordStorage.swift */; }; 11B356A5B50D4E6EF2282398 /* EditDuressPasscodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3594CBF3EA39A848D22EB /* EditDuressPasscodeViewModel.swift */; }; 11B356A8E75C3F3C9FC4E530 /* ShortcutInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A1C200EC15159154E3F /* ShortcutInputCell.swift */; }; @@ -616,6 +625,7 @@ 11B357BD9D9681D0D79DDEBE /* UITabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350369A891BEA3A525E5B /* UITabBarItem.swift */; }; 11B357BF378060E7E35F7052 /* AdditionalDataCellNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E67C1B1AB7A13074894 /* AdditionalDataCellNew.swift */; }; 11B357BF7588CB317EA62167 /* MarketOverviewCategoryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A0AF4D03160AF66D1D9 /* MarketOverviewCategoryService.swift */; }; + 11B357C425D633543FD109C3 /* DuressModeSelectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3554BC96C9C24C24CC2B0 /* DuressModeSelectView.swift */; }; 11B357C5FC1B7FDE86244DA5 /* SingleSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35CAE2327342F9CEC6AC9 /* SingleSelectorViewController.swift */; }; 11B357CD9544E312865CE36F /* WalletConnectInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A9F154EC84CEFA909B9 /* WalletConnectInteractor.swift */; }; 11B357D1A2BD673DAB7B4C61 /* SecondaryButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3587A6A05EFF1036F6C4B /* SecondaryButtonCell.swift */; }; @@ -634,6 +644,7 @@ 11B357F4C63379217B25AA75 /* RestoreSelectModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B358C7DF6F82875527031E /* RestoreSelectModule.swift */; }; 11B357FDC1C6BD6C39FE6853 /* MarketAdvancedSearchResultModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3598FB2653DB1DC1429CA /* MarketAdvancedSearchResultModule.swift */; }; 11B357FE4C2E1EC8E26ED68F /* StorageMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A1AE56A94BEB52AC4D1 /* StorageMigrator.swift */; }; + 11B357FF80E87451A99BEE4A /* AccountRecord_v_0_36.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356A734526DECD9606A66 /* AccountRecord_v_0_36.swift */; }; 11B357FF94D326846E12B940 /* WalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352D547F1BB38D2AD6AD5 /* WalletManager.swift */; }; 11B358006AEB85BBE0BF47A7 /* EditPasscodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35CF718BD36A9F07BC293 /* EditPasscodeViewModel.swift */; }; 11B358033DAB0FF23CF0E309 /* NftActivityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B351E034126F57DB7B4263 /* NftActivityService.swift */; }; @@ -678,6 +689,7 @@ 11B358623111DC1A8ED499DC /* EvmAddressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35962622F74F89FD32D2B /* EvmAddressViewModel.swift */; }; 11B358657FCC50C9B3A10294 /* ManageWalletsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C4D645B4468F84EADB7 /* ManageWalletsViewController.swift */; }; 11B3586BF6AC0538272E71A4 /* NftCollectionModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35708A630D70385F34A8B /* NftCollectionModule.swift */; }; + 11B3586F6BFCA16BDFD5921D /* DuressModeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A81FB3D4C06BBFEE7E7 /* DuressModeModule.swift */; }; 11B35871BA700133050E9241 /* CexWithdrawViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B2465CB748311AF03D5 /* CexWithdrawViewModel.swift */; }; 11B3587D9E89A97F63CD0C5A /* EditPasscodeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3529CF33E51DA1C872106 /* EditPasscodeModule.swift */; }; 11B3587DEC9342190880D3C3 /* TransactionsCoinSelectModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35FF9B3B86F74961FADE1 /* TransactionsCoinSelectModule.swift */; }; @@ -755,6 +767,7 @@ 11B35953182487E864EB4946 /* ActivateSubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B351E1107158B6A2BF2149 /* ActivateSubscriptionService.swift */; }; 11B35953404F5C8903DDA70D /* RecipientAddressCautionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B358B22BAF021E8FA028BF /* RecipientAddressCautionCell.swift */; }; 11B35959AAF414186CE39698 /* AddTokenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355E8892971578502EF33 /* AddTokenViewModel.swift */; }; + 11B3595AD0AA7108CAC814CC /* DuressModeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A81FB3D4C06BBFEE7E7 /* DuressModeModule.swift */; }; 11B3595BD960FE1B998ADF6F /* BinanceCexProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355E9CE0702287077F975 /* BinanceCexProvider.swift */; }; 11B3595CF65B69B3B04635E0 /* TermsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35EB9BA551F2F1AF7739D /* TermsManager.swift */; }; 11B3595D3E150BF50856A746 /* PoolSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35EC9E0E936067225C787 /* PoolSource.swift */; }; @@ -835,6 +848,7 @@ 11B35A426FD3D729DEB89DEA /* MarketTopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3505AD2C1640DEAD8CFFC /* MarketTopViewController.swift */; }; 11B35A42BF19B93C6005FBD9 /* AddTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356FFA77A8F6918B13FCA /* AddTokenService.swift */; }; 11B35A42D28B8BC4CDA57D8E /* AccountRecord_v_0_19.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350F6C5F6ABC288511AF0 /* AccountRecord_v_0_19.swift */; }; + 11B35A48CF68A2A45E1A429E /* PageDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B351FDDBEF227E161F6A0E /* PageDescription.swift */; }; 11B35A4CBD60780E0870E77C /* NftAssetBriefMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B359E32AEEE37347E255C4 /* NftAssetBriefMetadata.swift */; }; 11B35A4D9BD4B8C29FBAFACF /* AboutModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B353E80D544DAF20B12B56 /* AboutModule.swift */; }; 11B35A4E8657330B03FB2BCF /* SwitchAccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B359D884F1698E70F2536E /* SwitchAccountService.swift */; }; @@ -878,6 +892,7 @@ 11B35AB06F713851D58C60E3 /* ChooseBlockchainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35249BB89CF45176701EA /* ChooseBlockchainService.swift */; }; 11B35AB0C3F757E23D249330 /* TransactionTypeFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3592E4DA65E72C0BC6BEB /* TransactionTypeFilter.swift */; }; 11B35AB1A8FB2E49C98FCBEB /* NftCollectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35396831B92AAC156DF1D /* NftCollectionViewModel.swift */; }; + 11B35AB1D397D409EA179917 /* ActiveAccount_v_0_36.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3528DDD55DDA1BAC2BADB /* ActiveAccount_v_0_36.swift */; }; 11B35AB1DB6398B5C0ADF32A /* CexWithdrawConfirmModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F24FEE8233477BCDA18 /* CexWithdrawConfirmModule.swift */; }; 11B35AB6026D794BAFEC094E /* NftCollectionAssetsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350CD0F79715E1A5EE8BF /* NftCollectionAssetsService.swift */; }; 11B35AB97CD3E6C07C2D008C /* ManageAccountsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B359F01A63378AFAAEE113 /* ManageAccountsModule.swift */; }; @@ -1131,6 +1146,7 @@ 11B35DE80BDECA16EF0C74EA /* SimpleActivateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3564CFEC257DB52301CFC /* SimpleActivateViewModel.swift */; }; 11B35DF1D8B5125CF13A1812 /* RestoreMnemonicHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35CB288AF5A54B99A51E4 /* RestoreMnemonicHintView.swift */; }; 11B35DF3813AEB74E254A05A /* NftAssetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350E1584E954D281FA87D /* NftAssetView.swift */; }; + 11B35DF625EA2A1412C2D984 /* DuressModeIntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35420841B4F9B886A6507 /* DuressModeIntroView.swift */; }; 11B35DFCD3AD44FF72A38BBA /* CoinMarketsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F0A7192BA590254A16E /* CoinMarketsViewController.swift */; }; 11B35DFCEC1D363B160479EE /* MarketTopService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35770F0C72E1CD3F99985 /* MarketTopService.swift */; }; 11B35DFF8F15AA74356061A0 /* ReceiveSelectCoinService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35615F3ECB5D6E467B49A /* ReceiveSelectCoinService.swift */; }; @@ -1282,7 +1298,7 @@ 11B35F9F489F4B358FCCE893 /* MarkdownParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3543968337A40168D3EB0 /* MarkdownParser.swift */; }; 11B35FA3A00690573A482BAC /* CoinRankViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A1E2AE3DC240D5B785E /* CoinRankViewController.swift */; }; 11B35FA6F9EE876BD65E9AD6 /* LaunchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3595BAA550B6BEC8C3F72 /* LaunchScreen.swift */; }; - 11B35FA70EB07440E1576A56 /* RowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BAA4EA85B4A3A173498 /* RowButton.swift */; }; + 11B35FA70EB07440E1576A56 /* RowButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BAA4EA85B4A3A173498 /* RowButtonStyle.swift */; }; 11B35FAB3263E489CB9017FC /* AddTokenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356D5A5F32E88FEC7629D /* AddTokenViewController.swift */; }; 11B35FB1B7B34756830942DC /* LaunchErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A4096D259C9B1540D10 /* LaunchErrorViewController.swift */; }; 11B35FB28152F8881369DD9D /* AdapterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A4E49ED2D2BF8E60863 /* AdapterManager.swift */; }; @@ -2799,6 +2815,7 @@ 11B351F1248EDA20F7141AB8 /* ExtendedKeyModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedKeyModule.swift; sourceTree = ""; }; 11B351F33517C6DDA1E7AF59 /* AddEvmSyncSourceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEvmSyncSourceViewController.swift; sourceTree = ""; }; 11B351F8A0A9EB045377C152 /* BaseUnlockViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseUnlockViewModel.swift; sourceTree = ""; }; + 11B351FDDBEF227E161F6A0E /* PageDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageDescription.swift; sourceTree = ""; }; 11B352034B036C9CB7A52724 /* BaseCurrencySettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCurrencySettingsViewController.swift; sourceTree = ""; }; 11B352044BCE494491257933 /* LocalStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalStorage.swift; sourceTree = ""; }; 11B35216E1F4300730E08C5D /* CheckboxCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxCell.swift; sourceTree = ""; }; @@ -2820,6 +2837,7 @@ 11B3527F1528AA697AAA6E61 /* TopPlatformViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopPlatformViewModel.swift; sourceTree = ""; }; 11B3528090862B6792A76DA4 /* FaqCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaqCell.swift; sourceTree = ""; }; 11B352884D47E0B23DCF2C2C /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = ""; }; + 11B3528DDD55DDA1BAC2BADB /* ActiveAccount_v_0_36.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveAccount_v_0_36.swift; sourceTree = ""; }; 11B3529499CD211CC5A21CA2 /* NftCollectionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NftCollectionService.swift; sourceTree = ""; }; 11B352951AD68524C33022C0 /* CreatePasscodeModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreatePasscodeModule.swift; sourceTree = ""; }; 11B352970EA9924258E5BB75 /* ListSectionFooter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListSectionFooter.swift; sourceTree = ""; }; @@ -2899,6 +2917,7 @@ 11B35410733A35D1558E55B2 /* EvmCoinServiceFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EvmCoinServiceFactory.swift; sourceTree = ""; }; 11B35419084A6CB11230E3C6 /* NftViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NftViewController.swift; sourceTree = ""; }; 11B35419B0C846238DDC50F3 /* UnlinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnlinkViewModel.swift; sourceTree = ""; }; + 11B35420841B4F9B886A6507 /* DuressModeIntroView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuressModeIntroView.swift; sourceTree = ""; }; 11B35420B8191814543CBFA8 /* AddressInputCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressInputCell.swift; sourceTree = ""; }; 11B3543968337A40168D3EB0 /* MarkdownParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownParser.swift; sourceTree = ""; }; 11B3543F4D196A47EFE3E6F7 /* MarketHeaderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketHeaderCell.swift; sourceTree = ""; }; @@ -2940,6 +2959,7 @@ 11B3554159E6E5B7C1E71F04 /* MarketOverviewService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketOverviewService.swift; sourceTree = ""; }; 11B35542A7D7FE1BDC2E73E2 /* AccountType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountType.swift; sourceTree = ""; }; 11B355436F62829DBE3C92B4 /* CellComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellComponent.swift; sourceTree = ""; }; + 11B3554BC96C9C24C24CC2B0 /* DuressModeSelectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuressModeSelectView.swift; sourceTree = ""; }; 11B3555A19D9E41785D88A5E /* KeychainKitDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainKitDelegate.swift; sourceTree = ""; }; 11B35564351D59D37278C723 /* ExtendedKeyService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedKeyService.swift; sourceTree = ""; }; 11B35577CFC2384E3A454329 /* EnabledWallet_v_0_20.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnabledWallet_v_0_20.swift; sourceTree = ""; }; @@ -2985,6 +3005,7 @@ 11B35690912F374FEE910193 /* NftCollectionMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NftCollectionMetadata.swift; sourceTree = ""; }; 11B356940B04C8486835FDAA /* SwapApproveConfirmationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwapApproveConfirmationViewController.swift; sourceTree = ""; }; 11B3569F2E6BD5E9CBCFCA1F /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; + 11B356A734526DECD9606A66 /* AccountRecord_v_0_36.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountRecord_v_0_36.swift; sourceTree = ""; }; 11B356B9F833E1AEE0D6D589 /* CexDepositService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CexDepositService.swift; sourceTree = ""; }; 11B356BEB2B4DFC3E9C950C5 /* MarketAdvancedSearchViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketAdvancedSearchViewModel.swift; sourceTree = ""; }; 11B356C2E5AF8ED41E2B545D /* WalletConnectRequestModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectRequestModule.swift; sourceTree = ""; }; @@ -2995,6 +3016,7 @@ 11B356E0F2BC23304E545B13 /* NftModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NftModule.swift; sourceTree = ""; }; 11B356E4E27F5C12FC3859D1 /* CustomToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomToken.swift; sourceTree = ""; }; 11B356E71050EDF5C82FEFD9 /* BalanceTopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalanceTopView.swift; sourceTree = ""; }; + 11B356EF92FFD23F4385A991 /* ListStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListStyle.swift; sourceTree = ""; }; 11B356F4578E266268264021 /* QrCodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QrCodeCell.swift; sourceTree = ""; }; 11B356F9C155F16A441EC3A0 /* PoolGroupFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PoolGroupFactory.swift; sourceTree = ""; }; 11B356FFA77A8F6918B13FCA /* AddTokenService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddTokenService.swift; sourceTree = ""; }; @@ -3166,6 +3188,7 @@ 11B35A6DE18A1E6E837DFB21 /* ContactBookManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactBookManager.swift; sourceTree = ""; }; 11B35A774105F0F012935845 /* ExtendedKeyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedKeyViewController.swift; sourceTree = ""; }; 11B35A81AD46F48B63E59ED3 /* ReceiveBitcoinCashCoinTypeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiveBitcoinCashCoinTypeViewModel.swift; sourceTree = ""; }; + 11B35A81FB3D4C06BBFEE7E7 /* DuressModeModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuressModeModule.swift; sourceTree = ""; }; 11B35A8342513D5834B2145A /* ManageAccountsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManageAccountsViewModel.swift; sourceTree = ""; }; 11B35A8370C726989F4F456E /* WatchEvmAddressViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchEvmAddressViewModel.swift; sourceTree = ""; }; 11B35A9DB4112F41D7FCAC12 /* PrivateKeysViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateKeysViewModel.swift; sourceTree = ""; }; @@ -3210,7 +3233,7 @@ 11B35B7D66631DD5D91D0773 /* CoinMarketsModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinMarketsModule.swift; sourceTree = ""; }; 11B35B968B299A67FC7FEAE3 /* WalletConnectManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectManager.swift; sourceTree = ""; }; 11B35B96D2BC5994AC8EC794 /* MainModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainModule.swift; sourceTree = ""; }; - 11B35BAA4EA85B4A3A173498 /* RowButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowButton.swift; sourceTree = ""; }; + 11B35BAA4EA85B4A3A173498 /* RowButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowButtonStyle.swift; sourceTree = ""; }; 11B35BAABF1F6A9EFF769C47 /* NftCollectionOverviewViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NftCollectionOverviewViewController.swift; sourceTree = ""; }; 11B35BB370AE2C896BB9F877 /* TopPlatformViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopPlatformViewController.swift; sourceTree = ""; }; 11B35BB3B8928864A742C83E /* ReceiveAddressModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiveAddressModule.swift; sourceTree = ""; }; @@ -3356,6 +3379,7 @@ 11B35F4B9522FCCD91582AAF /* WalletElementServiceFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletElementServiceFactory.swift; sourceTree = ""; }; 11B35F57D462E2C9E9AEF67C /* LockManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockManager.swift; sourceTree = ""; }; 11B35F5A3CC8C229C0849756 /* PublicKeysViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicKeysViewController.swift; sourceTree = ""; }; + 11B35F5B696CF0677865FA2C /* DuressModeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuressModeViewModel.swift; sourceTree = ""; }; 11B35F60AFA103D0CD2369C3 /* BlockchainTokensView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockchainTokensView.swift; sourceTree = ""; }; 11B35F6B511DA5E0C60ED156 /* SendEvmViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendEvmViewModel.swift; sourceTree = ""; }; 11B35F7D3814B59092D32FF9 /* FeeCoinProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeeCoinProvider.swift; sourceTree = ""; }; @@ -4397,6 +4421,8 @@ 11B35EBD933DD3C9E72F1CA8 /* EnabledWallet_v_0_25.swift */, 11B357B2D07C69579BAEC997 /* CoinType.swift */, 11B3509AC90AEDF72F5989C6 /* EnabledWallet_v_0_34.swift */, + 11B3528DDD55DDA1BAC2BADB /* ActiveAccount_v_0_36.swift */, + 11B356A734526DECD9606A66 /* AccountRecord_v_0_36.swift */, ); path = Deprecated; sourceTree = ""; @@ -4600,7 +4626,7 @@ 11B35E7E7A5DBB09A2A5197D /* ThemeView.swift */, 11B35AC2D01DF06DC50EAC6A /* HighlightedTextView.swift */, 11B3578FB80AA013BD351A26 /* NavigationRow.swift */, - 11B35BAA4EA85B4A3A173498 /* RowButton.swift */, + 11B35BAA4EA85B4A3A173498 /* RowButtonStyle.swift */, 11B35D179817528224E926D1 /* ClickableRow.swift */, 11B352970EA9924258E5BB75 /* ListSectionFooter.swift */, 11B350C0CB7083E2738D356C /* ListSectionHeader.swift */, @@ -4610,6 +4636,8 @@ 11B3586B2387D758371A07AB /* InteractiveDismiss.swift */, 11B3572105A456CCDD63E94D /* SecondaryButtonStyle.swift */, 11B3584D2C3754A605975D6C /* SecondaryCircleButtonStyle.swift */, + 11B351FDDBEF227E161F6A0E /* PageDescription.swift */, + 11B356EF92FFD23F4385A991 /* ListStyle.swift */, ); path = SwiftUI; sourceTree = ""; @@ -5095,6 +5123,7 @@ 11B3566C587E62F8E154C9BC /* Unlock */, 11B353E1284B381BE56AC663 /* NumPadView.swift */, 11B359FC4FE023FBA0E1726C /* PasscodeView.swift */, + 11B3580CFABA60F3840C093E /* DuressMode */, ); path = Passcode; sourceTree = ""; @@ -5225,6 +5254,17 @@ path = ManageWallets; sourceTree = ""; }; + 11B3580CFABA60F3840C093E /* DuressMode */ = { + isa = PBXGroup; + children = ( + 11B35420841B4F9B886A6507 /* DuressModeIntroView.swift */, + 11B35F5B696CF0677865FA2C /* DuressModeViewModel.swift */, + 11B35A81FB3D4C06BBFEE7E7 /* DuressModeModule.swift */, + 11B3554BC96C9C24C24CC2B0 /* DuressModeSelectView.swift */, + ); + path = DuressMode; + sourceTree = ""; + }; 11B3581839DBB7AA34EFEF90 /* Assets */ = { isa = PBXGroup; children = ( @@ -9198,7 +9238,7 @@ 11B35AFE3ECB8A5EE7649F2D /* ExperimentalFeaturesView.swift in Sources */, 11B35ACD13702502B1ED3362 /* HighlightedTextView.swift in Sources */, 11B35F134E5EF8572BF330CB /* NavigationRow.swift in Sources */, - 11B35FA70EB07440E1576A56 /* RowButton.swift in Sources */, + 11B35FA70EB07440E1576A56 /* RowButtonStyle.swift in Sources */, 11B35CA92AA402BE72B4F5D6 /* Image.swift in Sources */, ABC9AD2688A8DF327A3F92FC /* NoAccountWalletTokenListService.swift in Sources */, ABC9A3FCFC46EC73A7E57EA3 /* WalletConnectPairingModule.swift in Sources */, @@ -9248,6 +9288,14 @@ 11B3553AD73FD1179249F277 /* SecondaryButtonStyle.swift in Sources */, 11B3523E8B466F259DB32E37 /* SecondaryCircleButtonStyle.swift in Sources */, ABC9AD46006A85E907826E2B /* EnabledWalletCache_v_0_36.swift in Sources */, + 11B35309CE9FBDA200067C4F /* ActiveAccount_v_0_36.swift in Sources */, + 11B357FF80E87451A99BEE4A /* AccountRecord_v_0_36.swift in Sources */, + 11B35DF625EA2A1412C2D984 /* DuressModeIntroView.swift in Sources */, + 11B35A48CF68A2A45E1A429E /* PageDescription.swift in Sources */, + 11B350D931616C0C296B6082 /* DuressModeViewModel.swift in Sources */, + 11B3595AD0AA7108CAC814CC /* DuressModeModule.swift in Sources */, + 11B356A35A5981DD231E580C /* ListStyle.swift in Sources */, + 11B357C425D633543FD109C3 /* DuressModeSelectView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -10501,7 +10549,7 @@ 11B35DDA6B6FB48499F6E0D3 /* ExperimentalFeaturesView.swift in Sources */, 11B353FD73E7731A9BC50C4E /* HighlightedTextView.swift in Sources */, 11B3574287AAA5FC16E3E3DA /* NavigationRow.swift in Sources */, - 11B35631BD5C6570C9359BEC /* RowButton.swift in Sources */, + 11B35631BD5C6570C9359BEC /* RowButtonStyle.swift in Sources */, 11B3541ED37746BAFF1832BA /* Image.swift in Sources */, ABC9AC5671A5EA9BF5ACBC5D /* NoAccountWalletTokenListService.swift in Sources */, ABC9A9CDDC14BA6259450ECA /* WalletConnectPairingModule.swift in Sources */, @@ -10551,6 +10599,14 @@ 11B35DDBD7EC98FAE5794F76 /* SecondaryButtonStyle.swift in Sources */, 11B35224D7A5A864C1C6F167 /* SecondaryCircleButtonStyle.swift in Sources */, ABC9AB2E235EA006E2DAD8DD /* EnabledWalletCache_v_0_36.swift in Sources */, + 11B35AB1D397D409EA179917 /* ActiveAccount_v_0_36.swift in Sources */, + 11B355696714B5570748EF03 /* AccountRecord_v_0_36.swift in Sources */, + 11B353A8B524526D20195D37 /* DuressModeIntroView.swift in Sources */, + 11B35189844EFD9E4B58269D /* PageDescription.swift in Sources */, + 11B3543A7A9EB1E0E0E8753D /* DuressModeViewModel.swift in Sources */, + 11B3586F6BFCA16BDFD5921D /* DuressModeModule.swift in Sources */, + 11B353577381981235B90A82 /* ListStyle.swift in Sources */, + 11B354DC983042AD922339A6 /* DuressModeSelectView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/UnstoppableWallet/UnstoppableWallet/Core/App.swift b/UnstoppableWallet/UnstoppableWallet/Core/App.swift index 25bcffc310..88ae3837cb 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/App.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/App.swift @@ -157,11 +157,17 @@ class App { pasteboardManager = PasteboardManager() reachabilityManager = ReachabilityManager() + biometryManager = BiometryManager(localStorage: StorageKit.LocalStorage.default) + passcodeManager = PasscodeManager(biometryManager: biometryManager, secureStorage: keychainKit.secureStorage) + lockManager = LockManager(passcodeManager: passcodeManager, localStorage: StorageKit.LocalStorage.default, delegate: lockDelegate) + lockoutManager = LockoutManager(secureStorage: keychainKit.secureStorage) + + blurManager = BlurManager(lockManager: lockManager) + let accountRecordStorage = AccountRecordStorage(dbPool: dbPool) let accountStorage = AccountStorage(secureStorage: keychainKit.secureStorage, storage: accountRecordStorage) let activeAccountStorage = ActiveAccountStorage(dbPool: dbPool) - let accountCachedStorage = AccountCachedStorage(accountStorage: accountStorage, activeAccountStorage: activeAccountStorage) - accountManager = AccountManager(storage: accountCachedStorage) + accountManager = AccountManager(passcodeManager: passcodeManager, accountStorage: accountStorage, activeAccountStorage: activeAccountStorage) accountRestoreWarningManager = AccountRestoreWarningManager(accountManager: accountManager, localStorage: StorageKit.LocalStorage.default) accountFactory = AccountFactory(accountManager: accountManager) @@ -244,13 +250,6 @@ class App { let favoriteCoinRecordStorage = FavoriteCoinRecordStorage(dbPool: dbPool) favoritesManager = FavoritesManager(storage: favoriteCoinRecordStorage) - biometryManager = BiometryManager(localStorage: StorageKit.LocalStorage.default) - passcodeManager = PasscodeManager(biometryManager: biometryManager, secureStorage: keychainKit.secureStorage) - lockManager = LockManager(passcodeManager: passcodeManager, localStorage: StorageKit.LocalStorage.default, delegate: lockDelegate) - lockoutManager = LockoutManager(secureStorage: keychainKit.secureStorage) - - blurManager = BlurManager(lockManager: lockManager) - let appVersionRecordStorage = AppVersionRecordStorage(dbPool: dbPool) let appVersionStorage = AppVersionStorage(storage: appVersionRecordStorage) diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Factories/AccountFactory.swift b/UnstoppableWallet/UnstoppableWallet/Core/Factories/AccountFactory.swift index 42dd93cf5a..778d6af7c7 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Factories/AccountFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Factories/AccountFactory.swift @@ -1,5 +1,5 @@ -import Foundation import EvmKit +import Foundation class AccountFactory { private let accountManager: AccountManager @@ -7,11 +7,9 @@ class AccountFactory { init(accountManager: AccountManager) { self.accountManager = accountManager } - } extension AccountFactory { - var nextAccountName: String { let nonWatchAccounts = accountManager.accounts.filter { !$0.watchAccount } let order = nonWatchAccounts.count + 1 @@ -22,7 +20,7 @@ extension AccountFactory { func nextAccountName(cex: Cex) -> String { let cexAccounts = accountManager.accounts.filter { account in switch account.type { - case .cex(let cexAccount): return cexAccount.cex == cex + case let .cex(cexAccount): return cexAccount.cex == cex default: return false } } @@ -40,22 +38,23 @@ extension AccountFactory { func account(type: AccountType, origin: AccountOrigin, backedUp: Bool, name: String) -> Account { Account( - id: UUID().uuidString, - name: name, - type: type, - origin: origin, - backedUp: backedUp + id: UUID().uuidString, + level: accountManager.currentLevel, + name: name, + type: type, + origin: origin, + backedUp: backedUp ) } func watchAccount(type: AccountType, name: String) -> Account { Account( - id: UUID().uuidString, - name: name, - type: type, - origin: .restored, - backedUp: true + id: UUID().uuidString, + level: accountManager.currentLevel, + name: name, + type: type, + origin: .restored, + backedUp: true ) } - } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/AccountManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/AccountManager.swift index 27f9915ea4..9d5043b69b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/AccountManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/AccountManager.swift @@ -1,8 +1,11 @@ -import RxSwift +import Combine import RxRelay +import RxSwift class AccountManager { + private let passcodeManager: PasscodeManager private let storage: AccountCachedStorage + private var cancellables = Set() private let activeAccountRelay = PublishRelay() private let accountsRelay = PublishRelay<[Account]>() @@ -12,8 +15,44 @@ class AccountManager { private var lastCreatedAccount: Account? - init(storage: AccountCachedStorage) { - self.storage = storage + init(passcodeManager: PasscodeManager, accountStorage: AccountStorage, activeAccountStorage: ActiveAccountStorage) { + self.passcodeManager = passcodeManager + + storage = AccountCachedStorage(level: passcodeManager.currentPasscodeLevel, accountStorage: accountStorage, activeAccountStorage: activeAccountStorage) + + passcodeManager.$currentPasscodeLevel + .sink { [weak self] level in + self?.handle(level: level) + } + .store(in: &cancellables) + + passcodeManager.$isDuressPasscodeSet + .sink { [weak self] isSet in + if !isSet { + self?.handleDisableDuress() + } + } + .store(in: &cancellables) + } + + private func handle(level: Int) { + storage.set(level: level) + + accountsRelay.accept(storage.accounts) + activeAccountRelay.accept(storage.activeAccount) + } + + private func handleDisableDuress() { + let currentLevel = passcodeManager.currentPasscodeLevel + + for account in storage.accounts { + if account.level > currentLevel { + account.level = currentLevel + storage.save(account: account) + } + } + + accountsRelay.accept(storage.accounts) } private func clearAccounts(ids: [String]) { @@ -21,7 +60,7 @@ class AccountManager { storage.delete(accountId: $0) } - if storage.accounts.isEmpty { + if storage.allAccounts.isEmpty { accountsLostRelay.accept(true) } } @@ -29,7 +68,6 @@ class AccountManager { } extension AccountManager { - var activeAccountObservable: Observable { activeAccountRelay.asObservable() } @@ -50,6 +88,10 @@ extension AccountManager { accountsLostRelay.asObservable() } + var currentLevel: Int { + passcodeManager.currentPasscodeLevel + } + var activeAccount: Account? { storage.activeAccount } @@ -145,21 +187,47 @@ extension AccountManager { return account } + func setDuress(accountIds: [String]) { + let currentLevel = passcodeManager.currentPasscodeLevel + + for account in storage.accounts { + if accountIds.contains(account.id) { + account.level = currentLevel + 1 + storage.save(account: account) + } + } + + accountsRelay.accept(storage.accounts) + } } class AccountCachedStorage { private let accountStorage: AccountStorage private let activeAccountStorage: ActiveAccountStorage - private var _accounts: [String: Account] + private var _allAccounts: [String: Account] + + private var level: Int + private var _accounts = [String: Account]() private var _activeAccount: Account? - init(accountStorage: AccountStorage, activeAccountStorage: ActiveAccountStorage) { + init(level: Int, accountStorage: AccountStorage, activeAccountStorage: ActiveAccountStorage) { + self.level = level self.accountStorage = accountStorage self.activeAccountStorage = activeAccountStorage - _accounts = accountStorage.allAccounts.reduce(into: [String: Account]()) { $0[$1.id] = $1 } - _activeAccount = activeAccountStorage.activeAccountId.flatMap { _accounts[$0] } + _allAccounts = accountStorage.allAccounts.reduce(into: [String: Account]()) { $0[$1.id] = $1 } + + syncAccounts() + } + + private func syncAccounts() { + _accounts = _allAccounts.filter { _, account in account.level >= level } + _activeAccount = activeAccountStorage.activeAccountId(level: level).flatMap { _accounts[$0] } + } + + var allAccounts: [Account] { + Array(_allAccounts.values) } var accounts: [Account] { @@ -174,33 +242,46 @@ class AccountCachedStorage { accountStorage.lostAccountIds } + func set(level: Int) { + self.level = level + syncAccounts() + } + func account(id: String) -> Account? { - _accounts[id] + _allAccounts[id] } func set(activeAccountId: String?) { - activeAccountStorage.activeAccountId = activeAccountId + activeAccountStorage.save(activeAccountId: activeAccountId, level: level) _activeAccount = activeAccountId.flatMap { _accounts[$0] } } func save(account: Account) { accountStorage.save(account: account) - _accounts[account.id] = account + _allAccounts[account.id] = account + + if account.level >= level { + _accounts[account.id] = account + } else { + _accounts.removeValue(forKey: account.id) + } } func delete(account: Account) { accountStorage.delete(account: account) + _allAccounts.removeValue(forKey: account.id) _accounts.removeValue(forKey: account.id) } func delete(accountId: String) { accountStorage.delete(accountId: accountId) + _allAccounts.removeValue(forKey: accountId) _accounts.removeValue(forKey: accountId) } func clear() { accountStorage.clear() + _allAccounts = [:] _accounts = [:] } - } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/PasscodeManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/PasscodeManager.swift index 18ce8b5025..dacbd33672 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/PasscodeManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/PasscodeManager.swift @@ -11,9 +11,9 @@ class PasscodeManager { private var passcodes = [String]() - @PostPublished private(set) var currentPasscodeLevel: Int - @PostPublished private(set) var isPasscodeSet = false - @PostPublished private(set) var isDuressPasscodeSet = false + @DistinctPublished private(set) var currentPasscodeLevel: Int + @DistinctPublished private(set) var isPasscodeSet = false + @DistinctPublished private(set) var isDuressPasscodeSet = false init(biometryManager: BiometryManager, secureStorage: ISecureStorage) { self.biometryManager = biometryManager diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Storage/AccountStorage.swift b/UnstoppableWallet/UnstoppableWallet/Core/Storage/AccountStorage.swift index 15f804af2a..a8f63fab4a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Storage/AccountStorage.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Storage/AccountStorage.swift @@ -83,6 +83,7 @@ class AccountStorage { return Account( id: id, + level: record.level, name: record.name, type: type, origin: origin, @@ -126,6 +127,7 @@ class AccountStorage { return AccountRecord( id: id, + level: account.level, name: account.name, type: typeName.rawValue, origin: account.origin.rawValue, diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Storage/ActiveAccountStorage.swift b/UnstoppableWallet/UnstoppableWallet/Core/Storage/ActiveAccountStorage.swift index 4b9c934007..5469d59b53 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Storage/ActiveAccountStorage.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Storage/ActiveAccountStorage.swift @@ -11,19 +11,18 @@ class ActiveAccountStorage { extension ActiveAccountStorage { - var activeAccountId: String? { - get { - try! dbPool.read { db in - try ActiveAccount.fetchOne(db)?.accountId - } + func activeAccountId(level: Int) -> String? { + try? dbPool.read { db in + try ActiveAccount.filter(ActiveAccount.Columns.level == level).fetchOne(db)?.accountId } - set { - _ = try! dbPool.write { db in - if let accountId = newValue { - try ActiveAccount(accountId: accountId).insert(db) - } else { - try ActiveAccount.deleteAll(db) - } + } + + func save(activeAccountId: String?, level: Int) { + _ = try? dbPool.write { db in + if let activeAccountId { + try ActiveAccount(level: level, accountId: activeAccountId).insert(db) + } else { + try ActiveAccount.filter(ActiveAccount.Columns.level == level).deleteAll(db) } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift b/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift index d9c1e3eb91..03edb45cdd 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift @@ -343,11 +343,11 @@ class StorageMigrator { } migrator.registerMigration("createActiveAccount") { db in - try db.create(table: ActiveAccount.databaseTableName) { t in - t.column(ActiveAccount.Columns.uniqueId.name, .text).notNull() - t.column(ActiveAccount.Columns.accountId.name, .text).notNull() + try db.create(table: ActiveAccount_v_0_36.databaseTableName) { t in + t.column(ActiveAccount_v_0_36.Columns.uniqueId.name, .text).notNull() + t.column(ActiveAccount_v_0_36.Columns.accountId.name, .text).notNull() - t.primaryKey([ActiveAccount.Columns.uniqueId.name], onConflict: .replace) + t.primaryKey([ActiveAccount_v_0_36.Columns.uniqueId.name], onConflict: .replace) } } @@ -367,17 +367,17 @@ class StorageMigrator { try db.drop(table: AccountRecord_v_0_20.databaseTableName) - try db.create(table: AccountRecord.databaseTableName) { t in - t.column(AccountRecord.Columns.id.name, .text).notNull() - t.column(AccountRecord.Columns.name.name, .text).notNull() - t.column(AccountRecord.Columns.type.name, .text).notNull() - t.column(AccountRecord.Columns.origin.name, .text).notNull() - t.column(AccountRecord.Columns.backedUp.name, .boolean).notNull() - t.column(AccountRecord.Columns.wordsKey.name, .text) - t.column(AccountRecord.Columns.saltKey.name, .text) - t.column(AccountRecord.Columns.dataKey.name, .text) + try db.create(table: AccountRecord_v_0_36.databaseTableName) { t in + t.column(AccountRecord_v_0_36.Columns.id.name, .text).notNull() + t.column(AccountRecord_v_0_36.Columns.name.name, .text).notNull() + t.column(AccountRecord_v_0_36.Columns.type.name, .text).notNull() + t.column(AccountRecord_v_0_36.Columns.origin.name, .text).notNull() + t.column(AccountRecord_v_0_36.Columns.backedUp.name, .boolean).notNull() + t.column(AccountRecord_v_0_36.Columns.wordsKey.name, .text) + t.column(AccountRecord_v_0_36.Columns.saltKey.name, .text) + t.column(AccountRecord_v_0_36.Columns.dataKey.name, .text) - t.primaryKey([AccountRecord.Columns.id.name], onConflict: .replace) + t.primaryKey([AccountRecord_v_0_36.Columns.id.name], onConflict: .replace) } for (index, oldAccount) in oldAccounts.enumerated() { @@ -402,7 +402,7 @@ class StorageMigrator { accountType = "mnemonic" } - let newAccount = AccountRecord( + let newAccount = AccountRecord_v_0_36( id: oldAccount.id, name: "Wallet \(index + 1)", type: accountType, @@ -417,7 +417,7 @@ class StorageMigrator { try newAccount.insert(db) if index == 0 { - let activeAccount = ActiveAccount(accountId: oldAccount.id) + let activeAccount = ActiveAccount_v_0_36(accountId: oldAccount.id) try activeAccount.insert(db) } } @@ -495,7 +495,7 @@ class StorageMigrator { migrator.registerMigration("fillSaltToAccountsKeychain") { db in let keychain = Keychain(service: "io.horizontalsystems.bank.dev") - let records = try AccountRecord.fetchAll(db) + let records = try AccountRecord_v_0_36.fetchAll(db) for record in records { try keychain.set("", key: "mnemonic_\(record.id)_salt") @@ -697,8 +697,8 @@ class StorageMigrator { } migrator.registerMigration("checkBIP39Compliance") { db in - try db.alter(table: AccountRecord.databaseTableName) { t in - t.add(column: AccountRecord.Columns.bip39Compliant.name, .boolean) + try db.alter(table: AccountRecord_v_0_36.databaseTableName) { t in + t.add(column: AccountRecord_v_0_36.Columns.bip39Compliant.name, .boolean) } } @@ -784,6 +784,29 @@ class StorageMigrator { } } + migrator.registerMigration("Add level to AccountRecord") { db in + try db.alter(table: AccountRecord.databaseTableName) { t in + t.add(column: AccountRecord.Columns.level.name, .integer).defaults(to: 0) + } + } + + migrator.registerMigration("Update ActiveAccount table") { db in + let activeAccountId = try ActiveAccount_v_0_36.fetchOne(db)?.accountId + + try db.drop(table: ActiveAccount_v_0_36.databaseTableName) + + try db.create(table: ActiveAccount.databaseTableName) { t in + t.column(ActiveAccount.Columns.level.name, .integer).notNull() + t.column(ActiveAccount.Columns.accountId.name, .text).notNull() + + t.primaryKey([ActiveAccount.Columns.level.name], onConflict: .replace) + } + + if let activeAccountId { + try ActiveAccount(level: 0, accountId: activeAccountId).insert(db) + } + } + try migrator.migrate(dbPool) } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/Account.swift b/UnstoppableWallet/UnstoppableWallet/Models/Account.swift index b90f0a9026..3852a1ff9d 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/Account.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/Account.swift @@ -1,14 +1,16 @@ import HdWalletKit -class Account { +class Account: Identifiable { let id: String + var level: Int var name: String let type: AccountType let origin: AccountOrigin var backedUp: Bool - init(id: String, name: String, type: AccountType, origin: AccountOrigin, backedUp: Bool) { + init(id: String, level: Int, name: String, type: AccountType, origin: AccountOrigin, backedUp: Bool) { self.id = id + self.level = level self.name = name self.type = type self.origin = origin @@ -19,7 +21,7 @@ class Account { switch type { case .evmAddress, .tronAddress: return true - case .hdExtendedKey(let key): + case let .hdExtendedKey(key): switch key { case .public: return true default: return false @@ -37,7 +39,7 @@ class Account { } var nonStandard: Bool { - guard case .mnemonic(_, _, let bip39Compliant) = type else { + guard case let .mnemonic(_, _, bip39Compliant) = type else { return false } @@ -45,7 +47,7 @@ class Account { } var nonRecommended: Bool { - guard case .mnemonic(let words, let salt, let bip39Compliant) = type, bip39Compliant else { + guard case let .mnemonic(words, salt, bip39Compliant) = type, bip39Compliant else { return false } @@ -58,19 +60,16 @@ class Account { case .hdExtendedKey, .evmAddress, .tronAddress, .evmPrivateKey, .cex: return false } } - } extension Account: Hashable { - - public static func ==(lhs: Account, rhs: Account) -> Bool { + public static func == (lhs: Account, rhs: Account) -> Bool { lhs.id == rhs.id } public func hash(into hasher: inout Hasher) { hasher.combine(id) } - } enum AccountOrigin: String { diff --git a/UnstoppableWallet/UnstoppableWallet/Models/AccountRecord.swift b/UnstoppableWallet/UnstoppableWallet/Models/AccountRecord.swift index 9c7aa1687f..87027f01b5 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/AccountRecord.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/AccountRecord.swift @@ -2,6 +2,7 @@ import GRDB class AccountRecord: Record { let id: String + let level: Int let name: String let type: String let origin: String @@ -11,8 +12,9 @@ class AccountRecord: Record { var dataKey: String? var bip39Compliant: Bool? - init(id: String, name: String, type: String, origin: String, backedUp: Bool, wordsKey: String?, saltKey: String?, dataKey: String?, bip39Compliant: Bool?) { + init(id: String, level: Int, name: String, type: String, origin: String, backedUp: Bool, wordsKey: String?, saltKey: String?, dataKey: String?, bip39Compliant: Bool?) { self.id = id + self.level = level self.name = name self.type = type self.origin = origin @@ -30,11 +32,12 @@ class AccountRecord: Record { } enum Columns: String, ColumnExpression { - case id, name, type, origin, backedUp, wordsKey, saltKey, dataKey, bip39Compliant + case id, level, name, type, origin, backedUp, wordsKey, saltKey, dataKey, bip39Compliant } required init(row: Row) { id = row[Columns.id] + level = row[Columns.level] name = row[Columns.name] type = row[Columns.type] origin = row[Columns.origin] @@ -49,6 +52,7 @@ class AccountRecord: Record { override func encode(to container: inout PersistenceContainer) { container[Columns.id] = id + container[Columns.level] = level container[Columns.name] = name container[Columns.type] = type container[Columns.origin] = origin diff --git a/UnstoppableWallet/UnstoppableWallet/Models/ActiveAccount.swift b/UnstoppableWallet/UnstoppableWallet/Models/ActiveAccount.swift index ab4daff5b1..cb88e26b4d 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/ActiveAccount.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/ActiveAccount.swift @@ -1,10 +1,11 @@ import GRDB class ActiveAccount: Record { - let uniqueId: String = "active_account" + let level: Int let accountId: String - init(accountId: String) { + init(level: Int, accountId: String) { + self.level = level self.accountId = accountId super.init() @@ -15,17 +16,18 @@ class ActiveAccount: Record { } enum Columns: String, ColumnExpression { - case uniqueId, accountId + case level, accountId } required init(row: Row) { + level = row[Columns.level] accountId = row[Columns.accountId] super.init(row: row) } override func encode(to container: inout PersistenceContainer) { - container[Columns.uniqueId] = uniqueId + container[Columns.level] = level container[Columns.accountId] = accountId } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/AccountRecord_v_0_36.swift b/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/AccountRecord_v_0_36.swift new file mode 100644 index 0000000000..3a970e1f8e --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/AccountRecord_v_0_36.swift @@ -0,0 +1,62 @@ +import GRDB + +class AccountRecord_v_0_36: Record { + let id: String + let name: String + let type: String + let origin: String + let backedUp: Bool + var wordsKey: String? + var saltKey: String? + var dataKey: String? + var bip39Compliant: Bool? + + init(id: String, name: String, type: String, origin: String, backedUp: Bool, wordsKey: String?, saltKey: String?, dataKey: String?, bip39Compliant: Bool?) { + self.id = id + self.name = name + self.type = type + self.origin = origin + self.backedUp = backedUp + self.wordsKey = wordsKey + self.saltKey = saltKey + self.dataKey = dataKey + self.bip39Compliant = bip39Compliant + + super.init() + } + + override class var databaseTableName: String { + "account_records" + } + + enum Columns: String, ColumnExpression { + case id, name, type, origin, backedUp, wordsKey, saltKey, dataKey, bip39Compliant + } + + required init(row: Row) { + id = row[Columns.id] + name = row[Columns.name] + type = row[Columns.type] + origin = row[Columns.origin] + backedUp = row[Columns.backedUp] + wordsKey = row[Columns.wordsKey] + saltKey = row[Columns.saltKey] + dataKey = row[Columns.dataKey] + bip39Compliant = row[Columns.bip39Compliant] + + super.init(row: row) + } + + override func encode(to container: inout PersistenceContainer) { + container[Columns.id] = id + container[Columns.name] = name + container[Columns.type] = type + container[Columns.origin] = origin + container[Columns.backedUp] = backedUp + container[Columns.wordsKey] = wordsKey + container[Columns.saltKey] = saltKey + container[Columns.dataKey] = dataKey + container[Columns.bip39Compliant] = bip39Compliant + } + +} diff --git a/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/ActiveAccount_v_0_36.swift b/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/ActiveAccount_v_0_36.swift new file mode 100644 index 0000000000..a625c52efe --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/ActiveAccount_v_0_36.swift @@ -0,0 +1,32 @@ +import GRDB + +class ActiveAccount_v_0_36: Record { + let uniqueId: String = "active_account" + let accountId: String + + init(accountId: String) { + self.accountId = accountId + + super.init() + } + + override class var databaseTableName: String { + "active_account" + } + + enum Columns: String, ColumnExpression { + case uniqueId, accountId + } + + required init(row: Row) { + accountId = row[Columns.accountId] + + super.init(row: row) + } + + override func encode(to container: inout PersistenceContainer) { + container[Columns.uniqueId] = uniqueId + container[Columns.accountId] = accountId + } + +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeIntroView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeIntroView.swift new file mode 100644 index 0000000000..9d66405c1d --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeIntroView.swift @@ -0,0 +1,79 @@ +import LocalAuthentication +import SwiftUI + +struct DuressModeIntroView: View { + let viewModel: DuressModeViewModel + @Binding var showParentSheet: Bool + + var body: some View { + ThemeView { + BottomGradientWrapper { + VStack(spacing: 0) { + PageDescription(text: "enable_duress_mode.intro.description".localized) + + VStack(spacing: 0) { + ListSectionHeader(text: "enable_duress_mode.intro.notes".localized) + ListSection { + if let biometryType = viewModel.biometryType { + InfoRow( + icon: Image(biometryType.iconName), + title: biometryType.title, + description: "enable_duress_mode.intro.biometrics.description".localized(biometryType.title, biometryType.title) + ) + } + + InfoRow( + icon: Image("dialpad_alt_2_24"), + title: "enable_duress_mode.intro.passcode_disabling".localized, + description: "enable_duress_mode.intro.passcode_disabling.description".localized + ) + + InfoRow( + icon: Image("edit_24"), + title: "enable_duress_mode.intro.passcode_change".localized, + description: "enable_duress_mode.intro.passcode_change.description".localized + ) + } + .listStyle(.bordered) + } + .padding(EdgeInsets(top: 0, leading: .margin16, bottom: .margin32, trailing: .margin16)) + } + } bottomContent: { + NavigationLink(destination: { + if (viewModel.regularAccounts + viewModel.watchAccounts).isEmpty { + CreatePasscodeModule.createDuressPasscodeView(accountIds: [], showParentSheet: $showParentSheet) + } else { + DuressModeSelectView(viewModel: viewModel, showParentSheet: $showParentSheet) + } + }) { + Text("button.continue".localized) + } + .buttonStyle(PrimaryButtonStyle(style: .yellow)) + } + } + .navigationTitle("enable_duress_mode.intro.title".localized) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + Button("button.cancel".localized) { + showParentSheet = false + } + } + } + + private struct InfoRow: View { + let icon: Image + let title: String + let description: String + + var body: some View { + ListRow { + icon.themeIcon(color: .themeJacob) + + VStack(spacing: .margin4) { + Text(title).themeBody() + Text(description).themeSubhead2() + } + } + } + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeModule.swift new file mode 100644 index 0000000000..2377f8ad41 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeModule.swift @@ -0,0 +1,11 @@ +import SwiftUI + +struct DuressModeModule { + static func view(showParentSheet: Binding) -> some View { + let viewModel = DuressModeViewModel( + biometryManager: App.shared.biometryManager, + accountManager: App.shared.accountManager + ) + return DuressModeIntroView(viewModel: viewModel, showParentSheet: showParentSheet) + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeSelectView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeSelectView.swift new file mode 100644 index 0000000000..a08173a7ee --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeSelectView.swift @@ -0,0 +1,85 @@ +import SwiftUI + +struct DuressModeSelectView: View { + @ObservedObject var viewModel: DuressModeViewModel + @Binding var showParentSheet: Bool + + var body: some View { + ThemeView { + BottomGradientWrapper { + VStack(spacing: 0) { + PageDescription(text: "enable_duress_mode.select.description".localized) + + VStack(spacing: .margin24) { + if !viewModel.regularAccounts.isEmpty { + VStack(spacing: 0) { + ListSectionHeader(text: "enable_duress_mode.select.wallets".localized) + ListSection { + ForEach(viewModel.regularAccounts) { account in + AccountRow(account: account, selectedAccountIds: $viewModel.selectedAccountIds) + } + } + } + } + + if !viewModel.watchAccounts.isEmpty { + VStack(spacing: 0) { + ListSectionHeader(text: "enable_duress_mode.select.watch_wallets".localized) + ListSection { + ForEach(viewModel.watchAccounts) { account in + AccountRow(account: account, selectedAccountIds: $viewModel.selectedAccountIds) + } + } + } + } + } + .padding(EdgeInsets(top: 0, leading: .margin16, bottom: .margin32, trailing: .margin16)) + } + } bottomContent: { + NavigationLink(destination: { + CreatePasscodeModule.createDuressPasscodeView(accountIds: Array(viewModel.selectedAccountIds), showParentSheet: $showParentSheet) + }) { + Text("button.next".localized) + } + .buttonStyle(PrimaryButtonStyle(style: .yellow)) + } + } + .navigationTitle("enable_duress_mode.select.title".localized) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + Button("button.cancel".localized) { + showParentSheet = false + } + } + } + + private struct AccountRow: View { + let account: Account + @Binding var selectedAccountIds: Set + + var body: some View { + ClickableRow(action: { + if selectedAccountIds.contains(account.id) { + selectedAccountIds.remove(account.id) + } else { + selectedAccountIds.insert(account.id) + } + }) { + VStack(spacing: 1) { + Text(account.name).themeBody() + Text(account.type.detailedDescription).themeSubhead2() + } + + ZStack { + RoundedRectangle(cornerRadius: .cornerRadius4, style: .continuous) + .stroke(Color.themeGray, lineWidth: 1.5) + .frame(width: .margin24, height: .margin24) + + if selectedAccountIds.contains(account.id) { + Image("check_2_20").themeIcon(color: .themeJacob) + } + } + } + } + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeViewModel.swift new file mode 100644 index 0000000000..026e29ca52 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/DuressMode/DuressModeViewModel.swift @@ -0,0 +1,22 @@ +import Combine + +class DuressModeViewModel: ObservableObject { + private let biometryManager: BiometryManager + + let regularAccounts: [Account] + let watchAccounts: [Account] + + @Published var selectedAccountIds = Set() + + init(biometryManager: BiometryManager, accountManager: AccountManager) { + self.biometryManager = biometryManager + + let sortedAccounts = accountManager.accounts.sorted { $0.name.lowercased() < $1.name.lowercased() } + regularAccounts = sortedAccounts.filter { !$0.watchAccount } + watchAccounts = sortedAccounts.filter { $0.watchAccount } + } + + var biometryType: BiometryType? { + biometryManager.biometryType + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/CreateDuressPasscodeViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/CreateDuressPasscodeViewModel.swift index 536d22992b..35753bf0ce 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/CreateDuressPasscodeViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/CreateDuressPasscodeViewModel.swift @@ -1,21 +1,36 @@ import Combine class CreateDuressPasscodeViewModel: SetPasscodeViewModel { + private let accountIds: [String] + private let accountManager: AccountManager + + init(accountIds: [String], accountManager: AccountManager, passcodeManager: PasscodeManager) { + self.accountIds = accountIds + self.accountManager = accountManager + + super.init(passcodeManager: passcodeManager) + } + override var title: String { - "create_duress_passcode.title".localized + "enable_duress_mode.passcode.title".localized } override var passcodeDescription: String { - "create_duress_passcode.description".localized + "enable_duress_mode.passcode.description".localized } override var confirmDescription: String { - "create_duress_passcode.confirm_passcode".localized + "enable_duress_mode.passcode.confirm".localized } override func onEnter(passcode: String) { do { try passcodeManager.set(duressPasscode: passcode) + + if !accountIds.isEmpty { + accountManager.setDuress(accountIds: accountIds) + } + finishSubject.send() } catch { print("Create Duress Passcode Error: \(error)") diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/CreatePasscodeModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/CreatePasscodeModule.swift index 17948c52f9..351bd2e536 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/CreatePasscodeModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/CreatePasscodeModule.swift @@ -1,7 +1,7 @@ import SwiftUI struct CreatePasscodeModule { - static func createPasscodeView(reason: CreatePasscodeReason, onCreate: @escaping () -> Void, onCancel: @escaping () -> Void) -> some View { + static func createPasscodeView(reason: CreatePasscodeReason, showParentSheet: Binding, onCreate: @escaping () -> Void, onCancel: @escaping () -> Void) -> some View { let viewModel = CreatePasscodeViewModel( passcodeManager: App.shared.passcodeManager, reason: reason, @@ -9,15 +9,17 @@ struct CreatePasscodeModule { onCancel: onCancel ) - return SetPasscodeView(viewModel: viewModel) + return SetPasscodeView(viewModel: viewModel, showParentSheet: showParentSheet) } - static func createDuressPasscodeView() -> some View { + static func createDuressPasscodeView(accountIds: [String], showParentSheet: Binding) -> some View { let viewModel = CreateDuressPasscodeViewModel( + accountIds: accountIds, + accountManager: App.shared.accountManager, passcodeManager: App.shared.passcodeManager ) - return SetPasscodeView(viewModel: viewModel) + return SetPasscodeView(viewModel: viewModel, showParentSheet: showParentSheet) } enum CreatePasscodeReason: Hashable, Identifiable { @@ -28,7 +30,7 @@ struct CreatePasscodeModule { var description: String { switch self { case .regular: return "create_passcode.description".localized - case .biometry(let type): return "create_passcode.description.biometry".localized(type.title) + case let .biometry(type): return "create_passcode.description.biometry".localized(type.title) case .duress: return "create_passcode.description.duress_mode".localized } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/EditPasscodeModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/EditPasscodeModule.swift index cb3c46c339..8a5db69e06 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/EditPasscodeModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/EditPasscodeModule.swift @@ -1,13 +1,13 @@ import SwiftUI struct EditPasscodeModule { - static func editPasscodeView() -> some View { + static func editPasscodeView(showParentSheet: Binding) -> some View { let viewModel = EditPasscodeViewModel(passcodeManager: App.shared.passcodeManager) - return SetPasscodeView(viewModel: viewModel) + return SetPasscodeView(viewModel: viewModel, showParentSheet: showParentSheet) } - static func editDuressPasscodeView() -> some View { + static func editDuressPasscodeView(showParentSheet: Binding) -> some View { let viewModel = EditDuressPasscodeViewModel(passcodeManager: App.shared.passcodeManager) - return SetPasscodeView(viewModel: viewModel) + return SetPasscodeView(viewModel: viewModel, showParentSheet: showParentSheet) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/SetPasscodeView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/SetPasscodeView.swift index 29234649b7..7aaeb2c3b6 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/SetPasscodeView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Passcode/Manage/SetPasscodeView.swift @@ -2,34 +2,30 @@ import SwiftUI struct SetPasscodeView: View { @ObservedObject var viewModel: SetPasscodeViewModel - - @Environment(\.presentationMode) private var presentationMode + @Binding var showParentSheet: Bool var body: some View { - ThemeNavigationView { - ThemeView { - PasscodeView( - maxDigits: viewModel.passcodeLength, - description: $viewModel.description, - errorText: $viewModel.errorText, - passcode: $viewModel.passcode, - biometryType: Binding(get: { nil }, set: { _ in }), - lockoutState: Binding(get: { .unlocked(attemptsLeft: Int.max, maxAttempts: Int.max) }, set: { _ in }), - randomEnabled: false - ) - } - .navigationTitle(viewModel.title) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - Button("button.cancel".localized) { - viewModel.onCancel() - presentationMode.wrappedValue.dismiss() - } - } - .onReceive(viewModel.finishSubject) { - presentationMode.wrappedValue.dismiss() + ThemeView { + PasscodeView( + maxDigits: viewModel.passcodeLength, + description: $viewModel.description, + errorText: $viewModel.errorText, + passcode: $viewModel.passcode, + biometryType: Binding(get: { nil }, set: { _ in }), + lockoutState: Binding(get: { .unlocked(attemptsLeft: Int.max, maxAttempts: Int.max) }, set: { _ in }), + randomEnabled: false + ) + } + .navigationTitle(viewModel.title) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + Button("button.cancel".localized) { + viewModel.onCancel() + showParentSheet = false } } - .interactiveDismiss(canDismissSheet: false) + .onReceive(viewModel.finishSubject) { + showParentSheet = false + } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift index 39b0357e1f..2ada24f12f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift @@ -67,28 +67,65 @@ struct SecuritySettingsView: View { ListSectionFooter(text: "settings_security.balance_auto_hide.description".localized) } + + VStack(spacing: 0) { + ListSection { + if viewModel.isDuressPasscodeSet { + ClickableRow(action: { + unlockReason = .changeDuressPasscode + }) { + Image("switch_wallet_24").themeIcon(color: .themeJacob) + Text("settings_security.edit_duress_passcode".localized).themeBody(color: .themeJacob) + } + + ClickableRow(action: { + unlockReason = .disableDuressMode + }) { + Image("trash_24").themeIcon(color: .themeLucian) + Text("settings_security.disable_duress_mode".localized).themeBody(color: .themeLucian) + } + } else { + ClickableRow(action: { + if viewModel.isPasscodeSet { + unlockReason = .enableDuressMode + } else { + createPasscodeReason = .duress + } + }) { + Image("switch_wallet_24").themeIcon(color: .themeJacob) + Text("settings_security.enable_duress_mode".localized).themeBody(color: .themeJacob) + } + } + } + + ListSectionFooter(text: "settings_security.duress_mode.description".localized) + } } .sheet(item: $createPasscodeReason) { reason in - CreatePasscodeModule.createPasscodeView( - reason: reason, - onCreate: { - switch reason { - case .biometry: - viewModel.set(biometryEnabled: true) - case .duress: - DispatchQueue.main.async { - createDuressPasscodePresented = true + ThemeNavigationView { + CreatePasscodeModule.createPasscodeView( + reason: reason, + showParentSheet: Binding(get: { createPasscodeReason != nil }, set: { if !$0 { createPasscodeReason = nil } }), + onCreate: { + switch reason { + case .biometry: + viewModel.set(biometryEnabled: true) + case .duress: + DispatchQueue.main.async { + createDuressPasscodePresented = true + } + default: () + } + }, + onCancel: { + switch reason { + case .biometry: viewModel.isBiometryToggleOn = false + default: () } - default: () - } - }, - onCancel: { - switch reason { - case .biometry: viewModel.isBiometryToggleOn = false - default: () } - } - ) + ) + } + .interactiveDismiss(canDismissSheet: false) } .sheet(item: $unlockReason) { reason in ThemeNavigationView { @@ -115,16 +152,17 @@ struct SecuritySettingsView: View { } } .sheet(isPresented: $editPasscodePresented) { - EditPasscodeModule.editPasscodeView() + ThemeNavigationView { EditPasscodeModule.editPasscodeView(showParentSheet: $editPasscodePresented) } } .sheet(isPresented: $createDuressPasscodePresented) { - CreatePasscodeModule.createDuressPasscodeView() + ThemeNavigationView { DuressModeModule.view(showParentSheet: $createDuressPasscodePresented) } } .sheet(isPresented: $editDuressPasscodePresented) { - EditPasscodeModule.editDuressPasscodeView() + ThemeNavigationView { EditPasscodeModule.editDuressPasscodeView(showParentSheet: $editDuressPasscodePresented) } } .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) } + .navigationTitle("settings_security.title".localized) } enum UnlockReason: Identifiable { diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ClickableRow.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ClickableRow.swift index 827a58792a..cb37355e66 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ClickableRow.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ClickableRow.swift @@ -10,7 +10,7 @@ struct ClickableRow: View { content } }) - .buttonStyle(RowButton()) - .contentShape(Rectangle()) + .buttonStyle(RowButtonStyle()) + .contentShape(Rectangle()) } } diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/HorizontalDivider.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/HorizontalDivider.swift index c9d27d86a4..71abc234f2 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/HorizontalDivider.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/HorizontalDivider.swift @@ -4,7 +4,7 @@ struct HorizontalDivider: View { private let color: Color private let height: CGFloat - init(color: Color = .themeSteel10, height: CGFloat = 1) { + init(color: Color = .themeSteel10, height: CGFloat = .heightOneDp) { self.color = color self.height = height } @@ -12,5 +12,4 @@ struct HorizontalDivider: View { var body: some View { color.frame(height: height) } - } diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListRow.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListRow.swift index 19afb98790..0110886f3e 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListRow.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListRow.swift @@ -7,7 +7,7 @@ struct ListRow: View { HStack(spacing: .margin16) { content } - .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin12, trailing: .margin16)) - .frame(minHeight: .heightCell48) + .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin12, trailing: .margin16)) + .frame(minHeight: .heightCell48) } } diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListSection.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListSection.swift index a2116d81f9..e83bd90300 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListSection.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListSection.swift @@ -1,15 +1,18 @@ import SwiftUI struct ListSection: View { + @Environment(\.listStyle) var listStyle + @ViewBuilder let content: Content var body: some View { VStack(spacing: 0) { _VariadicView.Tree(Layout()) { - content - } - .background(RoundedRectangle(cornerRadius: .cornerRadius12, style: .continuous).fill(Color.themeLawrence)) - .clipShape(RoundedRectangle(cornerRadius: .cornerRadius12, style: .continuous)) + content + } + .background(RoundedRectangle(cornerRadius: .cornerRadius12, style: .continuous).fill(listStyle.backgroundColor)) + .clipShape(RoundedRectangle(cornerRadius: .cornerRadius12, style: .continuous)) + .overlay(RoundedRectangle(cornerRadius: .cornerRadius12).stroke(listStyle.borderColor, lineWidth: .heightOneDp)) } } @@ -29,5 +32,4 @@ struct ListSection: View { } } } - } diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListStyle.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListStyle.swift new file mode 100644 index 0000000000..113a9ecab3 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ListStyle.swift @@ -0,0 +1,37 @@ +import SwiftUI + +enum ListStyle { + case lawrence + case bordered + + var backgroundColor: Color { + switch self { + case .lawrence: return .themeLawrence + case .bordered: return .clear + } + } + + var borderColor: Color { + switch self { + case .lawrence: return .clear + case .bordered: return .themeSteel20 + } + } +} + +struct ListStyleKey: EnvironmentKey { + static let defaultValue = ListStyle.lawrence +} + +extension EnvironmentValues { + var listStyle: ListStyle { + get { self[ListStyleKey.self] } + set { self[ListStyleKey.self] = newValue } + } +} + +extension View { + func listStyle(_ listStyle: ListStyle) -> some View { + environment(\.listStyle, listStyle) + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/NavigationRow.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/NavigationRow.swift index fa9efa3195..deb5523386 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/NavigationRow.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/NavigationRow.swift @@ -10,6 +10,6 @@ struct NavigationRow: View { content } } - .buttonStyle(RowButton()) + .buttonStyle(RowButtonStyle()) } } diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/PageDescription.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/PageDescription.swift new file mode 100644 index 0000000000..a4e343fe8a --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/PageDescription.swift @@ -0,0 +1,11 @@ +import SwiftUI + +struct PageDescription: View { + let text: String + + var body: some View { + Text(text) + .themeSubhead2() + .padding(EdgeInsets(top: .margin12, leading: .margin32, bottom: .margin32, trailing: .margin32)) + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/RowButton.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/RowButton.swift deleted file mode 100644 index 82d26eaec6..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/RowButton.swift +++ /dev/null @@ -1,10 +0,0 @@ -import SwiftUI - -struct RowButton: ButtonStyle { - - func makeBody(configuration: Self.Configuration) -> some View { - configuration.label - .background(configuration.isPressed ? Color.themeLawrencePressed : Color.themeLawrence) - } - -} diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/RowButtonStyle.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/RowButtonStyle.swift new file mode 100644 index 0000000000..7e83f83c71 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/RowButtonStyle.swift @@ -0,0 +1,10 @@ +import SwiftUI + +struct RowButtonStyle: ButtonStyle { + @Environment(\.listStyle) var listStyle + + func makeBody(configuration: Self.Configuration) -> some View { + configuration.label + .background(configuration.isPressed ? Color.themeLawrencePressed : listStyle.backgroundColor) + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings index 4e1d9975fa..7e8f5f63f3 100644 --- a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings +++ b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings @@ -1084,6 +1084,10 @@ Go to Settings - > %@ and allow access to the camera."; "settings_security.disable_passcode" = "Disable Passcode"; "settings_security.balance_auto_hide" = "Balance Auto Hide"; "settings_security.balance_auto_hide.description" = "Automatically hides balance each time the app is opened, regardless of previous preferences."; +"settings_security.enable_duress_mode" = "Enable Duress Mode"; +"settings_security.edit_duress_passcode" = "Edit Duress Passcode"; +"settings_security.disable_duress_mode" = "Disable Duress Mode"; +"settings_security.duress_mode.description" = "A specialized mode designed to keep selected wallets safe under coercion."; // Create Passcode @@ -1093,11 +1097,25 @@ Go to Settings - > %@ and allow access to the camera."; "create_passcode.description.duress_mode" = "Set a passcode to enable Duress Mode"; "create_passcode.confirm_passcode" = "Confirm passcode"; -// Create Duress Passcode - -"create_duress_passcode.title" = "Duress Passcode"; -"create_duress_passcode.description" = "Set a passcode for Duress Mode"; -"create_duress_passcode.confirm_passcode" = "Confirm passcode"; +// Enable Duress Mode + +"enable_duress_mode.intro.title" = "Duress Passcode"; +"enable_duress_mode.intro.description" = "This mode allows user to setup multiple unlock app passcodes where a desired passcode shows only specified wallets. Designed to keep selected wallets safe under coercion or threats."; +"enable_duress_mode.intro.notes" = "Notes"; +"enable_duress_mode.intro.biometrics.description" = "The %@ feature will work to unlock the Duress Mode. You can disable %@ for convenience."; +"enable_duress_mode.intro.passcode_disabling" = "Passcode Disabling"; +"enable_duress_mode.intro.passcode_disabling.description" = "Disabling the passcode in the main mode will automatically reset the Duress Mode."; +"enable_duress_mode.intro.passcode_change" = "Passcode Change"; +"enable_duress_mode.intro.passcode_change.description" = "Changing the passcode in the Duress Mode will also change the current passcode code for that mode."; + +"enable_duress_mode.select.title" = "Select Wallets"; +"enable_duress_mode.select.description" = "Select the wallets that will be displayed in Duress Mode."; +"enable_duress_mode.select.wallets" = "Wallets"; +"enable_duress_mode.select.watch_wallets" = "Watch Wallets"; + +"enable_duress_mode.passcode.title" = "Duress Passcode"; +"enable_duress_mode.passcode.description" = "Set a passcode for Duress Mode"; +"enable_duress_mode.passcode.confirm" = "Confirm passcode"; // Edit Passcode