diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 4a0d21ac3c..7a9cae0963 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -2089,6 +2089,7 @@ ABC9A36297D869E49C152CAB /* SwapRevokeConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A1360FE305343B1049CF /* SwapRevokeConfirmationViewController.swift */; }; ABC9A36D3A4EEABF6EA6DBA0 /* Shake.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A104D916039D690E454E /* Shake.swift */; }; ABC9A372F53F1F1D59BF8969 /* RestoreFileConfigurationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9ADA345301F29B947F281 /* RestoreFileConfigurationModule.swift */; }; + ABC9A37B5FAB65E7AB66547E /* BackupManagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AEA4B072067A9F10BE36 /* BackupManagerViewController.swift */; }; ABC9A37FB71FA7DA14553EFC /* RawFullBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A819E6708797C571CA0B /* RawFullBackup.swift */; }; ABC9A395A96C1F7C30F21940 /* WalletConnectPendingRequestsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD3F677671FB57CCD886 /* WalletConnectPendingRequestsService.swift */; }; ABC9A3B155B3F6E7E0F2CB07 /* HudHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A381CB4C09FF7CB62A94 /* HudHelper.swift */; }; @@ -2129,7 +2130,6 @@ ABC9A4E323FF7AAD86FA8E75 /* MarketCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A37521CD6E2CC5BA4E68 /* MarketCardView.swift */; }; ABC9A4F4B7F17169DC240A98 /* WalletConnectUriHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A1C31F5343EB2BEA4540 /* WalletConnectUriHandler.swift */; }; ABC9A4FF1E1964FB77700C4E /* ChartIndicatorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A6DE5C760A5D0C90B70E /* ChartIndicatorFactory.swift */; }; - ABC9A50F1811EEABE61913F7 /* BackupManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A121EDBB1FF651D514D1 /* BackupManagerView.swift */; }; ABC9A51979D2047BEF45A2AE /* TokenSelectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A791A47F4F1E71B51B3B /* TokenSelectView.swift */; }; ABC9A51E36466E414AF24C67 /* WalletConnectMainPendingRequestViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A8D8072033A5AC7E4897 /* WalletConnectMainPendingRequestViewModel.swift */; }; ABC9A51F2E7EB0B477EBE708 /* SendZcashService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AE6D2CD14194802E7976 /* SendZcashService.swift */; }; @@ -2197,7 +2197,6 @@ ABC9A7655AE66379E42FE2A4 /* ContactBookSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A99184EE1D5D052C52E9 /* ContactBookSettingsViewController.swift */; }; ABC9A767A49B686B3A3AC154 /* UniswapV3Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A6363DB5DAE5B58AFDC0 /* UniswapV3Module.swift */; }; ABC9A774500F8D8D3D9E04DD /* SendBitcoinAdapterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A1BD3B1B53C72DDF923A /* SendBitcoinAdapterService.swift */; }; - ABC9A77A41517173B610E0FC /* BackupManagerModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD8AEA1E3B1198DDECCE /* BackupManagerModule.swift */; }; ABC9A78CFF8B232D330EC7B5 /* DiffLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A916C64B5EA9D96B8FDA /* DiffLabel.swift */; }; ABC9A78D3A4267CAC0F5D0E8 /* SendConfirmationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AFF7119B9AC0E32B2060 /* SendConfirmationModule.swift */; }; ABC9A794E47FC07ABFC32BBD /* FeePriceScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AF8E8DE67732371A00E0 /* FeePriceScale.swift */; }; @@ -2215,7 +2214,6 @@ ABC9A802418438F6BD1FC1E3 /* WalletTokenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A64A66778C137FA9642C /* WalletTokenViewController.swift */; }; ABC9A80BCDA72347C6619E6C /* SendTimeLockErrorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9ADF114FCFABEA148AF04 /* SendTimeLockErrorService.swift */; }; ABC9A819DDAEE683FCCA02EF /* NftAssetCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A30A8F78E9C9AEE861F1 /* NftAssetCellFactory.swift */; }; - ABC9A81D09F820C68D4F75FB /* BackupManagerModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD8AEA1E3B1198DDECCE /* BackupManagerModule.swift */; }; ABC9A82D771D920162551294 /* WalletConnectPendingRequestsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AE15C187118DE6F0CE7B /* WalletConnectPendingRequestsViewModel.swift */; }; ABC9A83233DA4C83AF83E483 /* WalletConnectPendingRequestsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD3F677671FB57CCD886 /* WalletConnectPendingRequestsService.swift */; }; ABC9A8451CEF02EA0A94CEAA /* ProFeaturesAuthorizationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A9628A708749A31EEA70 /* ProFeaturesAuthorizationManager.swift */; }; @@ -2234,7 +2232,6 @@ ABC9A8A74C527C4E01EBB8A5 /* RestoreCloudModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A45E29D1773EF27A0074 /* RestoreCloudModule.swift */; }; ABC9A8AC5E635D9CB1704568 /* BackupDisclaimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AFFD435E0C9FBE0E5E7C /* BackupDisclaimerView.swift */; }; ABC9A8AE39B8925B28B97F77 /* AppBackupProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AB61EA3B39D8BDB1EEDE /* AppBackupProvider.swift */; }; - ABC9A8CAD0A3AAB7398C2A30 /* BackupManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A121EDBB1FF651D514D1 /* BackupManagerView.swift */; }; ABC9A8CBDB7CF4E781896C49 /* RestoreTypeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AAC741F9A54293CD21B1 /* RestoreTypeModule.swift */; }; ABC9A8D215CC5D6A70736E84 /* SendBaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A48552CF0C90E22686A9 /* SendBaseService.swift */; }; ABC9A8D8709EC2B40D74A97A /* SwapRevokeConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A1360FE305343B1049CF /* SwapRevokeConfirmationViewController.swift */; }; @@ -2313,6 +2310,7 @@ ABC9ABFA7299ADDDFEE918F7 /* WalletConnectPairingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9ACE88105815BFC477D71 /* WalletConnectPairingViewController.swift */; }; ABC9AC011EA9D58866999D88 /* UniswapV3DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AE97D361FBF43F46F016 /* UniswapV3DataSource.swift */; }; ABC9AC10D815702B812CFFB7 /* NftAssetOverviewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9ABE473B354836327B3AC /* NftAssetOverviewService.swift */; }; + ABC9AC170807B409634706E6 /* BackupManagerModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD42C324F58B5EE00610 /* BackupManagerModule.swift */; }; ABC9AC1BD5C95957726F8AE8 /* MarketCardValueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A4FCDC5085002DF35C17 /* MarketCardValueView.swift */; }; ABC9AC50E2E966F009D78FD5 /* CheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A6B2EF46FF7EDA4728D3 /* CheckboxView.swift */; }; ABC9AC5552508D091D622027 /* SendZcashFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A6F55A2C6777D25F57D5 /* SendZcashFactory.swift */; }; @@ -2347,6 +2345,7 @@ ABC9ACE255480B2D6E340611 /* ChartIndicatorsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A2F3E5147E0E92258FBB /* ChartIndicatorsService.swift */; }; ABC9ACEB81BCB00435B35F64 /* RestoreFileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AB61774389A4773BE18C /* RestoreFileHelper.swift */; }; ABC9ACEE45E455BA098231EE /* SendMemoInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A3C103C1DE359184D944 /* SendMemoInputCell.swift */; }; + ABC9ACFCC63CDB6C7712E512 /* BackupManagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AEA4B072067A9F10BE36 /* BackupManagerViewController.swift */; }; ABC9AD05E7B986179310D6D7 /* SwapInputAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A56611CF5E7B3F25CD5C /* SwapInputAccessoryView.swift */; }; ABC9AD1C8D0CE88A604D5250 /* SendBinanceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD0DD32AB4B9BAB79F11 /* SendBinanceFactory.swift */; }; ABC9AD1F6A6A7C97E4120F2F /* BackupAppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AB001077F4001611DFFC /* BackupAppViewModel.swift */; }; @@ -2384,6 +2383,7 @@ ABC9AE1E60CABA0101D62738 /* FullCoin.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A9B35C58F6525F3B2D5C /* FullCoin.swift */; }; ABC9AE2131780654A7139081 /* RestorePassphraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A0547CBE2B5A3E38891E /* RestorePassphraseViewController.swift */; }; ABC9AE223619E13A296BED51 /* MarketCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A2B7FBA735A76083990C /* MarketCardCell.swift */; }; + ABC9AE262936C29D89DC61C8 /* BackupManagerModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD42C324F58B5EE00610 /* BackupManagerModule.swift */; }; ABC9AE2C026D04B679644279 /* IntegerAmountInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AA527E63E18179CB689A /* IntegerAmountInputCell.swift */; }; ABC9AE3D64AF3981A68D9913 /* SendConfirmationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A3AB799024C8FC2C7DD8 /* SendConfirmationViewModel.swift */; }; ABC9AE3F4B99ABE949945A3A /* ChartIndicatorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A0C131342CC764890C2B /* ChartIndicatorsViewController.swift */; }; @@ -3808,7 +3808,6 @@ ABC9A1057AD189DA1CE31BF5 /* PredefinedBlockchainService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredefinedBlockchainService.swift; sourceTree = ""; }; ABC9A10A83A43DCAFA709472 /* CoinDetailAdviceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinDetailAdviceViewController.swift; sourceTree = ""; }; ABC9A1136889E6976E17B347 /* WalletConnectService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectService.swift; sourceTree = ""; }; - ABC9A121EDBB1FF651D514D1 /* BackupManagerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupManagerView.swift; sourceTree = ""; }; ABC9A12529DC8DE5D46D9776 /* ContactBookAddressViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactBookAddressViewModel.swift; sourceTree = ""; }; ABC9A12E4155640075755699 /* IndicatorDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndicatorDataSource.swift; sourceTree = ""; }; ABC9A1360FE305343B1049CF /* SwapRevokeConfirmationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwapRevokeConfirmationViewController.swift; sourceTree = ""; }; @@ -3982,9 +3981,9 @@ ABC9AD2E1F25A5CED10DB81F /* MaIndicatorDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MaIndicatorDataSource.swift; sourceTree = ""; }; ABC9AD35D41AEEBD38AA08B5 /* NftAssetModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NftAssetModule.swift; sourceTree = ""; }; ABC9AD3F677671FB57CCD886 /* WalletConnectPendingRequestsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectPendingRequestsService.swift; sourceTree = ""; }; + ABC9AD42C324F58B5EE00610 /* BackupManagerModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupManagerModule.swift; sourceTree = ""; }; ABC9AD448DC071D8800C6B12 /* WalletTokenBalanceCustomAmountCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletTokenBalanceCustomAmountCell.swift; sourceTree = ""; }; ABC9AD5CB1911A698718213F /* BackupCryptoHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupCryptoHelper.swift; sourceTree = ""; }; - ABC9AD8AEA1E3B1198DDECCE /* BackupManagerModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupManagerModule.swift; sourceTree = ""; }; ABC9ADA345301F29B947F281 /* RestoreFileConfigurationModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreFileConfigurationModule.swift; sourceTree = ""; }; ABC9ADB77831DCB474B24C8A /* SendFeeService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendFeeService.swift; sourceTree = ""; }; ABC9ADC1A3B17225B6CC0869 /* MarketCategoryMarketCapFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketCategoryMarketCapFetcher.swift; sourceTree = ""; }; @@ -4003,6 +4002,7 @@ ABC9AE8D5944EB202A471C80 /* RestorePassphraseViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestorePassphraseViewModel.swift; sourceTree = ""; }; ABC9AE97D361FBF43F46F016 /* UniswapV3DataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniswapV3DataSource.swift; sourceTree = ""; }; ABC9AEA1D717D8CED8462AB0 /* WalletConnectMainViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectMainViewModel.swift; sourceTree = ""; }; + ABC9AEA4B072067A9F10BE36 /* BackupManagerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupManagerViewController.swift; sourceTree = ""; }; ABC9AEAD18F73D4FBE05783D /* Contact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; ABC9AEC034DE5784F55BD5F3 /* PseudoAccessoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PseudoAccessoryView.swift; sourceTree = ""; }; ABC9AECEEB35D57CB0965E79 /* WalletBackup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletBackup.swift; sourceTree = ""; }; @@ -7356,10 +7356,10 @@ ABC9A9C9D65EAD890AF617A4 /* BackupApp */ = { isa = PBXGroup; children = ( - ABC9AD5DC41717F92AC2151C /* BackupManager */, ABC9A37065F4A8459C416F0A /* BackupAppModule.swift */, ABC9A386552F4E2372850DBB /* Backup */, ABC9A1A8B6E734D38D55E1D2 /* Restore */, + ABC9AE7C2B9E59FFF7663BCD /* BackupManagerLegacy */, ); path = BackupApp; sourceTree = ""; @@ -7489,15 +7489,6 @@ path = Platforms; sourceTree = ""; }; - ABC9AD5DC41717F92AC2151C /* BackupManager */ = { - isa = PBXGroup; - children = ( - ABC9A121EDBB1FF651D514D1 /* BackupManagerView.swift */, - ABC9AD8AEA1E3B1198DDECCE /* BackupManagerModule.swift */, - ); - path = BackupManager; - sourceTree = ""; - }; ABC9ADC8373B45DF33E35DCA /* Binance */ = { isa = PBXGroup; children = ( @@ -7539,6 +7530,15 @@ path = PriceView; sourceTree = ""; }; + ABC9AE7C2B9E59FFF7663BCD /* BackupManagerLegacy */ = { + isa = PBXGroup; + children = ( + ABC9AD42C324F58B5EE00610 /* BackupManagerModule.swift */, + ABC9AEA4B072067A9F10BE36 /* BackupManagerViewController.swift */, + ); + path = BackupManagerLegacy; + sourceTree = ""; + }; ABC9AE7E6FB8D22E8697DBA9 /* Eip1155 */ = { isa = PBXGroup; children = ( @@ -9456,8 +9456,6 @@ 11B356A35A5981DD231E580C /* ListStyle.swift in Sources */, 11B357C425D633543FD109C3 /* DuressModeSelectView.swift in Sources */, 11B358D35D2270FD78C6EF82 /* AutoLockPeriod.swift in Sources */, - ABC9A50F1811EEABE61913F7 /* BackupManagerView.swift in Sources */, - ABC9A81D09F820C68D4F75FB /* BackupManagerModule.swift in Sources */, ABC9AF95141EA649524FBF88 /* CheckboxStyle.swift in Sources */, ABC9A79CFCEBAC442A1B791D /* BackupAppModule.swift in Sources */, ABC9A09E0B614E5B4E32B7F9 /* InputTextView.swift in Sources */, @@ -9489,6 +9487,8 @@ 11B35FA1970606C12E57C2EA /* ChartView.swift in Sources */, 11B3596AE38880C5899769D5 /* CoinOverviewView.swift in Sources */, 11B357BA09F0FA21477F0A59 /* CoinOverviewViewModelNew.swift in Sources */, + ABC9AE262936C29D89DC61C8 /* BackupManagerModule.swift in Sources */, + ABC9ACFCC63CDB6C7712E512 /* BackupManagerViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -10788,8 +10788,6 @@ 11B353577381981235B90A82 /* ListStyle.swift in Sources */, 11B354DC983042AD922339A6 /* DuressModeSelectView.swift in Sources */, 11B3511DAD3881FDE2419A64 /* AutoLockPeriod.swift in Sources */, - ABC9A8CAD0A3AAB7398C2A30 /* BackupManagerView.swift in Sources */, - ABC9A77A41517173B610E0FC /* BackupManagerModule.swift in Sources */, ABC9A2035980B70E1C0790A8 /* CheckboxStyle.swift in Sources */, ABC9A99A45187C36D48840F8 /* BackupAppModule.swift in Sources */, ABC9AE51262C09EABF5CCEEE /* InputTextView.swift in Sources */, @@ -10821,6 +10819,8 @@ 11B35FB362526C723329C9ED /* ChartView.swift in Sources */, 11B353E4793549B6A4F23997 /* CoinOverviewView.swift in Sources */, 11B35FDF03CD52FEC5B1745A /* CoinOverviewViewModelNew.swift in Sources */, + ABC9AC170807B409634706E6 /* BackupManagerModule.swift in Sources */, + ABC9A37B5FAB65E7AB66547E /* BackupManagerViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Storage/ContactBookManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Storage/ContactBookManager.swift index 26cb76fe8c..fb81da12d7 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Storage/ContactBookManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Storage/ContactBookManager.swift @@ -411,17 +411,24 @@ extension ContactBookManager { return contacts } - func restore(contacts: [BackupContact]) throws { + func restore(contacts: [BackupContact], mergePolitics: MergePolitics) throws { guard let localUrl else { state = .failed(ContactBookManager.StorageError.localUrlNotAvailable) return } - let newContactBook = helper.contactBook(contacts: contacts, lastVersion: state.data?.version) + let resolved: ContactBook - try save(url: localUrl, newContactBook) + switch mergePolitics { + case .replace: + resolved = helper.contactBook(contacts: contacts, lastVersion: state.data?.version) + case .insert: + resolved = helper.insert(contacts: contacts, book: state.data) + } + + try save(url: localUrl, resolved) if remoteSync { - try? saveToICloud(book: newContactBook) + try? saveToICloud(book: resolved) } } @@ -447,6 +454,10 @@ extension ContactBookManager { } extension ContactBookManager { + enum MergePolitics { + case replace, insert + } + enum StorageError: Error { case cloudUrlNotAvailable case localUrlNotAvailable diff --git a/UnstoppableWallet/UnstoppableWallet/Models/Contact.swift b/UnstoppableWallet/UnstoppableWallet/Models/Contact.swift index 19710a987e..89f6e0b1d6 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/Contact.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/Contact.swift @@ -7,7 +7,7 @@ class ContactAddress: Codable, ImmutableMappable, Hashable, Equatable { enum CodingKeys: String, CodingKey { case blockchainUid = "blockchain_uid" - case address = "address" + case address } init(blockchainUid: String, address: String) { @@ -22,7 +22,7 @@ class ContactAddress: Codable, ImmutableMappable, Hashable, Equatable { func mapping(map: Map) { blockchainUid >>> map[CodingKeys.blockchainUid.rawValue] - address >>> map[CodingKeys.address.rawValue] + address >>> map[CodingKeys.address.rawValue] } func hash(into hasher: inout Hasher) { @@ -30,19 +30,16 @@ class ContactAddress: Codable, ImmutableMappable, Hashable, Equatable { hasher.combine(address.lowercased()) } - static func ==(lhs: ContactAddress, rhs: ContactAddress) -> Bool { + static func == (lhs: ContactAddress, rhs: ContactAddress) -> Bool { lhs.address.lowercased() == rhs.address.lowercased() && - lhs.blockchainUid == rhs.blockchainUid + lhs.blockchainUid == rhs.blockchainUid } - } extension Array where Element == ContactAddress { - - static func ==(lhs: [ContactAddress], rhs: [ContactAddress]) -> Bool { + static func == (lhs: [ContactAddress], rhs: [ContactAddress]) -> Bool { Set(lhs) == Set(rhs) } - } class Contact: Codable, ImmutableMappable, Hashable, Equatable { @@ -66,13 +63,13 @@ class Contact: Codable, ImmutableMappable, Hashable, Equatable { } func mapping(map: Map) { - uid >>> map["uid"] - modifiedAt >>> map["modified_at"] - name >>> map["name"] - addresses >>> map["addresses"] + uid >>> map["uid"] + modifiedAt >>> map["modified_at"] + name >>> map["name"] + addresses >>> map["addresses"] } - static func ==(lhs: Contact, rhs: Contact) -> Bool { + static func == (lhs: Contact, rhs: Contact) -> Bool { lhs.uid == rhs.uid } @@ -81,9 +78,8 @@ class Contact: Codable, ImmutableMappable, Hashable, Equatable { } func address(blockchainUid: String) -> ContactAddress? { - addresses.first { $0.blockchainUid == blockchainUid } + addresses.first { $0.blockchainUid == blockchainUid } } - } class DeletedContact: Codable, ImmutableMappable, Hashable, Equatable { @@ -101,18 +97,17 @@ class DeletedContact: Codable, ImmutableMappable, Hashable, Equatable { } func mapping(map: Map) { - uid >>> map["uid"] - deletedAt >>> map["deleted_at"] + uid >>> map["uid"] + deletedAt >>> map["deleted_at"] } - static func ==(lhs: DeletedContact, rhs: DeletedContact) -> Bool { + static func == (lhs: DeletedContact, rhs: DeletedContact) -> Bool { lhs.uid == rhs.uid } func hash(into hasher: inout Hasher) { hasher.combine(uid) } - } class ContactBook: Codable, ImmutableMappable { @@ -134,9 +129,8 @@ class ContactBook: Codable, ImmutableMappable { } func mapping(map: Map) { - version >>> map["version"] - contacts >>> map["contacts"] - deleted >>> map["deleted"] + version >>> map["version"] + contacts >>> map["contacts"] + deleted >>> map["deleted"] } - } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Backup/ICloud/AppBackupProvider.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Backup/ICloud/AppBackupProvider.swift index ace803fe26..63a8bf4f64 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Backup/ICloud/AppBackupProvider.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Backup/ICloud/AppBackupProvider.swift @@ -186,7 +186,7 @@ extension AppBackupProvider { favoritesManager.add(coinUids: raw.watchlistIds) if !raw.contacts.isEmpty { - try? contactManager.restore(contacts: raw.contacts) + try? contactManager.restore(contacts: raw.contacts, mergePolitics: .replace) } evmSyncSourceManager.restore(selected: raw.settings.evmSyncSources.selected, custom: raw.customSyncSources) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/ContactBook/ContactBookHelper.swift b/UnstoppableWallet/UnstoppableWallet/Modules/ContactBook/ContactBookHelper.swift index 42d65284f2..e8bc5abb42 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/ContactBook/ContactBookHelper.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/ContactBook/ContactBookHelper.swift @@ -1,7 +1,6 @@ import Foundation class ContactBookHelper { - // Merge contacts with replace older contacts by newer func mergeNewer(lhs: [Contact], rhs: [Contact]) -> [Contact] { let left = Set(lhs) @@ -12,8 +11,8 @@ class ContactBookHelper { let mergedNewer = intersectionLeft.map { lContact in if let rContact = right.first(where: { $0.uid == lContact.uid }), - rContact.modifiedAt > lContact.modifiedAt { - + rContact.modifiedAt > lContact.modifiedAt + { return rContact } return lContact @@ -32,8 +31,8 @@ class ContactBookHelper { let mergedNewer = intersectionLeft.map { lContact in if let rContact = right.first(where: { $0.uid == lContact.uid }), - rContact.deletedAt > lContact.deletedAt { - + rContact.deletedAt > lContact.deletedAt + { return rContact } return lContact @@ -48,7 +47,6 @@ class ContactBookHelper { let contacts = contacts.filter { contact in if let deletedIndex = deleted.firstIndex(where: { contact.uid == $0.uid }) { - if deleted[deletedIndex].deletedAt > contact.modifiedAt { return false } else { @@ -71,10 +69,41 @@ class ContactBookHelper { return Set(lhsContacts) == Set(rhsContacts) && Set(lhsDeleted) == Set(rhsDeleted) } - } extension ContactBookHelper { + func insert(contacts: [BackupContact], book: ContactBook?) -> ContactBook { + var updatedContacts = book?.contacts ?? [] + + for contact in contacts { + var name = contact.name + if let index = updatedContacts.firstIndex(where: { $0.name == contact.name }) { + if updatedContacts[index].addresses == contact.addresses { + continue + } + + name = RestoreFileHelper.resolve( + name: contact.name, + elements: updatedContacts.map { $0.name }, + style: "(%d)" + ) + } + updatedContacts.append( + Contact( + uid: UUID().uuidString, + modifiedAt: Date().timeIntervalSince1970, + name: name, + addresses: contact.addresses + ) + ) + } + + return ContactBook( + version: (book?.version ?? 0) + 1, + contacts: updatedContacts, + deletedContacts: book?.deleted ?? [] + ) + } func update(contact: Contact, book: ContactBook) -> ContactBook { var contacts = book.contacts @@ -104,7 +133,6 @@ extension ContactBookHelper { // try resolve contact book. If one of them not changed - return it, else return new one func resolved(lhs: ContactBook, rhs: ContactBook) -> ResolveResult { - if lhs.version > rhs.version { return .left } @@ -138,34 +166,33 @@ extension ContactBookHelper { func backupContactBook(contactBook: ContactBook) -> BackupContactBook { BackupContactBook( - contacts: contactBook - .contacts - .map { BackupContact(uid: $0.uid, name: $0.name, addresses: $0.addresses) } + contacts: contactBook + .contacts + .map { BackupContact(uid: $0.uid, name: $0.name, addresses: $0.addresses) } ) } func contactBook(contacts: [BackupContact], lastVersion: Int?) -> ContactBook { // we need increase version and create new book with latest timestamps for all contacts ContactBook( - version: (lastVersion ?? 0) + 1, - contacts: contacts - .map { Contact(uid: $0.uid, - modifiedAt: Date().timeIntervalSince1970, - name: $0.name, - addresses: $0.addresses) - }, - deletedContacts: []) + version: (lastVersion ?? 0) + 1, + contacts: contacts + .map { Contact(uid: $0.uid, + modifiedAt: Date().timeIntervalSince1970, + name: $0.name, + addresses: $0.addresses) + }, + deletedContacts: [] + ) } - } extension ContactBookHelper { - private struct EqualContactData: Equatable, Hashable { let uid: String let timestamp: TimeInterval - static func ==(lhs: EqualContactData, rhs: EqualContactData) -> Bool { + static func == (lhs: EqualContactData, rhs: EqualContactData) -> Bool { lhs.uid == rhs.uid && lhs.timestamp == rhs.timestamp } @@ -181,5 +208,4 @@ extension ContactBookHelper { case right case merged(ContactBook) } - } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/ContactBook/ContactBookSettings/ContactBookSettingsService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/ContactBook/ContactBookSettings/ContactBookSettingsService.swift index 7e542c535a..e13ca5057a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/ContactBook/ContactBookSettings/ContactBookSettingsService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/ContactBook/ContactBookSettings/ContactBookSettingsService.swift @@ -97,7 +97,7 @@ extension ContactBookSettingsService { } func replace(contacts: [BackupContact]) throws { - try contactManager.restore(contacts: contacts) + try contactManager.restore(contacts: contacts, mergePolitics: .replace) } func createBackupFile() throws -> URL { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileConfiguration/RestoreFileConfigurationViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileConfiguration/RestoreFileConfigurationViewController.swift index 993903037a..c99e24a21e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileConfiguration/RestoreFileConfigurationViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileConfiguration/RestoreFileConfigurationViewController.swift @@ -82,7 +82,7 @@ class RestoreFileConfigurationViewController: KeyboardAwareViewController { .highlightedDescription(text: "backup_app.restore.notice.description".localized), ], buttons: [ - .init(style: .yellow, title: "backup_app.restore.notice.merge".localized, actionType: .afterClose) { [weak self] in + .init(style: .red, title: "backup_app.restore.notice.merge".localized, actionType: .afterClose) { [weak self] in self?.viewModel.onTapRestore() }, .init(style: .transparent, title: "button.cancel".localized, actionType: .afterClose), @@ -105,13 +105,23 @@ class RestoreFileConfigurationViewController: KeyboardAwareViewController { } private func row(item: BackupAppModule.Item, rowInfo: RowInfo) -> RowProtocol { - tableView.universalRow62( - id: item.title, - title: .body(item.title), - description: .subhead2(item.description), - isFirst: rowInfo.isFirst, - isLast: rowInfo.isLast - ) + if let description = item.description { + return tableView.universalRow62( + id: item.title, + title: .body(item.title), + description: .subhead2(description), + isFirst: rowInfo.isFirst, + isLast: rowInfo.isLast + ) + } else { + return tableView.universalRow48( + id: item.title, + title: .body(item.title), + value: .subhead1(item.value, color: .themeGray), + isFirst: rowInfo.isFirst, + isLast: rowInfo.isLast + ) + } } private var descriptionSection: SectionProtocol { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileConfiguration/RestoreFileConfigurationViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileConfiguration/RestoreFileConfigurationViewModel.swift index 1686634c30..abe5a7b13f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileConfiguration/RestoreFileConfigurationViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileConfiguration/RestoreFileConfigurationViewModel.swift @@ -46,7 +46,7 @@ extension RestoreFileConfigurationViewModel { } var otherItems: [BackupAppModule.Item] { - let contactAddressCount = rawBackup.contacts.reduce(into: 0) { $0 += $1.addresses.count } + let contactAddressCount = rawBackup.contacts.count let watchAccounts = rawBackup .accounts .filter { $0.account.watchAccount } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileHelper.swift b/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileHelper.swift index 8f3ea1cc79..dd7fe31cb6 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileHelper.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/RestoreAccount/RestoreFile/RestoreFileHelper.swift @@ -6,18 +6,33 @@ struct RestoreFileHelper { let filename = NSString(string: url.lastPathComponent).deletingPathExtension if let oneWallet = try? JSONDecoder().decode(WalletBackup.self, from: data) { - return .init(name: filename, source: .wallet(oneWallet)) } if let fullBackup = try? JSONDecoder().decode(FullBackup.self, from: data) { - return .init(name: filename, source: .full(fullBackup)) } throw ParseError.wrongFile } + static func resolve(name: String, elements: [String], checkRaw: Bool = false, style: String = "%d") -> String { + let name: (String?) -> String = { [name, $0].compactMap { $0 }.joined(separator: " ") } + + if checkRaw { + if !elements.contains(where: { $0.lowercased() == name(nil).lowercased() }) { + return name(nil) + } + } + + for i in 1 ..< elements.count + 1 { + let newName = name(style.localized(i)) + if !elements.contains(where: { $0.lowercased() == newName.lowercased() }) { + return newName + } + } + return name(style.localized(elements.count + 1)) + } } extension RestoreFileHelper { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupAppViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupAppViewModel.swift index 8e52d037ae..609f85b564 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupAppViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupAppViewModel.swift @@ -71,18 +71,17 @@ class BackupAppViewModel: ObservableObject { @Published var passwordCautionState: CautionState = .none @Published var password: String = AppConfig.defaultPassphrase { didSet { - validatePasswords() + clearCautions() } } @Published var confirmCautionState: CautionState = .none @Published var confirm: String = AppConfig.defaultPassphrase { didSet { - validatePasswords() + clearCautions() } } - @Published var passwordButtonDisabled = true @Published var passwordButtonProcessing = false private var dismissSubject = PassthroughSubject() @@ -111,8 +110,6 @@ class BackupAppViewModel: ObservableObject { otherItems = getOtherItems() selected = accountIds.reduce(into: [:]) { $0[$1] = true } name = nextName - - validatePasswords() } } @@ -153,15 +150,24 @@ extension BackupAppViewModel { private func getOtherItems() -> [BackupAppModule.Item] { let contacts = contactManager.all ?? [] - let contactAddressCount = contacts.reduce(into: 0) { $0 += $1.addresses.count } return BackupAppModule.items( watchAccountCount: accounts(watch: true).count, watchlistCount: favoritesManager.allCoinUids.count, - contactAddressCount: contactAddressCount, + contactAddressCount: contacts.count, blockchainSourcesCount: evmSyncSourceManager.customSyncSources(blockchainType: nil).count ) } + + private func clearCautions() { + if passwordCautionState != .none { + passwordCautionState = .none + } + + if confirmCautionState != .none { + confirmCautionState = .none + } + } } extension BackupAppViewModel { @@ -170,25 +176,17 @@ extension BackupAppViewModel { } } -// Backup Name VieeModel +// Backup Name ViewModel extension BackupAppViewModel { var nextName: String { - let name = { [Self.backupNamePrefix, $0].joined(separator: " ") } switch destination { case .cloud: - let exists = cloudBackupManager - .existFilenames - .filter { $0.hasPrefix(Self.backupNamePrefix) } - .sorted() - for i in 1 ..< exists.count + 1 { - let newName = name(i.description) - if !exists.contains(where: { $0.lowercased() == newName.lowercased() }) { - return newName - } - } - return name((exists.count + 1).description) + return RestoreFileHelper.resolve( + name: Self.backupNamePrefix, + elements: cloudBackupManager.existFilenames + ) default: - return name("1") + return Self.backupNamePrefix } } @@ -204,43 +202,30 @@ extension BackupAppViewModel { func validatePasswords() { var buttonDisabled = false - if password.isEmpty { - buttonDisabled = true - confirmCautionState = .none - } else { - do { - try BackupCrypto.validate(passphrase: password) - passwordCautionState = .none - } catch { - passwordCautionState = .caution(.init(text: error.localizedDescription, type: .error)) - buttonDisabled = true - } + clearCautions() + + do { + try BackupCrypto.validate(passphrase: password) + passwordCautionState = .none + } catch { + passwordCautionState = .caution(.init(text: error.localizedDescription, type: .error)) } - if confirm.isEmpty { - buttonDisabled = true - confirmCautionState = .none - } else { - do { - try BackupCrypto.validate(passphrase: confirm) - if password != confirm { - buttonDisabled = true - confirmCautionState = .caution( - .init( - text: "backup.cloud.password.confirm.error.doesnt_match".localized, - type: .error - ) + do { + try BackupCrypto.validate(passphrase: confirm) + if password != confirm { + confirmCautionState = .caution( + .init( + text: "backup.cloud.password.confirm.error.doesnt_match".localized, + type: .error ) - } else { - confirmCautionState = .none - } - } catch { - confirmCautionState = .caution(.init(text: error.localizedDescription, type: .error)) - buttonDisabled = true + ) + } else { + confirmCautionState = .none } + } catch { + confirmCautionState = .caution(.init(text: error.localizedDescription, type: .error)) } - - passwordButtonDisabled = buttonDisabled } @MainActor @@ -254,14 +239,19 @@ extension BackupAppViewModel { } func onTapSave() { + validatePasswords() + guard passwordCautionState == .none && confirmCautionState == .none else { + return + } passwordButtonProcessing = true + let selectedIds = accountIds.filter { (selected[$0] ?? false) } Task { switch destination { case .none: () case .cloud: do { - try cloudBackupManager.save(accountIds: accountIds, passphrase: password, name: name) + try cloudBackupManager.save(accountIds: selectedIds, passphrase: password, name: name) passwordButtonProcessing = false await showSuccess() dismissSubject.send() @@ -271,7 +261,7 @@ extension BackupAppViewModel { } case .local: do { - let url = try cloudBackupManager.file(accountIds: accountIds, passphrase: password, name: name) + let url = try cloudBackupManager.file(accountIds: selectedIds, passphrase: password, name: name) sharePresented = url passwordButtonProcessing = false } catch { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupDisclaimer/BackupDisclaimerView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupDisclaimer/BackupDisclaimerView.swift index 73125c8f6c..29e4c4e26b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupDisclaimer/BackupDisclaimerView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupDisclaimer/BackupDisclaimerView.swift @@ -4,7 +4,7 @@ import ThemeKit struct BackupDisclaimerView: View { @ObservedObject var viewModel: BackupAppViewModel - @Binding var backupPresented: Bool + var onDismiss: (() -> Void)? @State var isOn: Bool = true @@ -30,22 +30,23 @@ struct BackupDisclaimerView: View { .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) } bottomContent: { NavigationLink( - destination: BackupNameView(viewModel: viewModel, backupPresented: $backupPresented), + destination: BackupNameView(viewModel: viewModel, onDismiss: onDismiss), isActive: $viewModel.namePushed ) { Button(action: { viewModel.namePushed = true }) { Text("button.next".localized) } - .buttonStyle(PrimaryButtonStyle(style: .yellow)) - .disabled(!isOn) + .buttonStyle(PrimaryButtonStyle(style: .yellow)) + .disabled(!isOn) } } } .navigationBarTitle(backupDisclaimer.title) + .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("button.cancel".localized) { - backupPresented = false + onDismiss?() } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupList/BackupListView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupList/BackupListView.swift index d058571ca2..f4e194cbb7 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupList/BackupListView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupList/BackupListView.swift @@ -4,7 +4,7 @@ import ThemeKit struct BackupListView: View { @ObservedObject var viewModel: BackupAppViewModel - @Binding var backupPresented: Bool + var onDismiss: (() -> Void)? var body: some View { ThemeView { @@ -45,9 +45,17 @@ struct BackupListView: View { ForEach(viewModel.otherItems) { (item: BackupAppModule.Item) in ListRow { VStack(spacing: 1) { - Text(item.title).themeBody() + HStack { + Text(item.title).themeBody() + + if let value = item.value { + Text(value).themeSubhead1(alignment: .trailing) + } + } - Text(item.description).themeSubhead2() + if let description = item.description { + Text(description).themeSubhead2() + } } } } @@ -57,7 +65,7 @@ struct BackupListView: View { .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) } bottomContent: { NavigationLink( - destination: BackupDisclaimerView(viewModel: viewModel, backupPresented: $backupPresented), + destination: BackupDisclaimerView(viewModel: viewModel, onDismiss: onDismiss), isActive: $viewModel.disclaimerPushed ) { Button(action: { @@ -72,7 +80,7 @@ struct BackupListView: View { .navigationBarTitleDisplayMode(.inline) .toolbar { Button("button.cancel".localized) { - backupPresented = false + onDismiss?() } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupName/BackupNameView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupName/BackupNameView.swift index 56b9843663..dfeb9b3835 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupName/BackupNameView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupName/BackupNameView.swift @@ -3,7 +3,7 @@ import ThemeKit struct BackupNameView: View { @ObservedObject var viewModel: BackupAppViewModel - @Binding var backupPresented: Bool + var onDismiss: (() -> Void)? var body: some View { ThemeView { @@ -28,7 +28,7 @@ struct BackupNameView: View { .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) } bottomContent: { NavigationLink( - destination: BackupPasswordView(viewModel: viewModel, backupPresented: $backupPresented), + destination: BackupPasswordView(viewModel: viewModel, onDismiss: onDismiss), isActive: $viewModel.passwordPushed ) { Button(action: { @@ -40,11 +40,11 @@ struct BackupNameView: View { .disabled(viewModel.nameCautionState != .none) } } - .navigationBarTitle("backup_app.backup.name.title".localized) + .navigationTitle("backup_app.backup.name.title".localized) .navigationBarTitleDisplayMode(.inline) .toolbar { Button("button.cancel".localized) { - backupPresented = false + onDismiss?() } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupPassword/BackupPasswordView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupPassword/BackupPasswordView.swift index 0f15dbe967..3c585d3e44 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupPassword/BackupPasswordView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupPassword/BackupPasswordView.swift @@ -4,7 +4,7 @@ import ThemeKit struct BackupPasswordView: View { @ObservedObject var viewModel: BackupAppViewModel - @Binding var backupPresented: Bool + var onDismiss: (() -> ())? @State var secureLock = true @@ -63,14 +63,14 @@ struct BackupPasswordView: View { } } .buttonStyle(PrimaryButtonStyle(style: .yellow)) - .disabled(viewModel.passwordButtonDisabled || viewModel.passwordButtonProcessing) + .disabled(viewModel.passwordButtonProcessing) .animation(.default, value: viewModel.passwordButtonProcessing) } .sheet(item: $viewModel.sharePresented) { url in let completion: UIActivityViewController.CompletionWithItemsHandler = { _, success, _, error in if success { + onDismiss?() showDone() - backupPresented = false } if let error { show(error: error) @@ -83,23 +83,19 @@ struct BackupPasswordView: View { } } .onReceive(viewModel.dismissPublisher) { - backupPresented = false + onDismiss?() } .navigationBarTitle("backup_app.backup.password.title".localized) .navigationBarTitleDisplayMode(.inline) .toolbar { Button("button.cancel".localized) { - backupPresented = false + onDismiss?() } + .disabled(viewModel.passwordButtonProcessing) } } } - @MainActor - private func showSuccess() { - HudHelper.instance.show(banner: .savedToCloud) - } - @MainActor private func show(error: Error) { HudHelper.instance.show(banner: .error(string: error.localizedDescription)) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupType/BackupTypeView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupType/BackupTypeView.swift index 61d9e77c6f..2f05789cea 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupType/BackupTypeView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupType/BackupTypeView.swift @@ -3,7 +3,7 @@ import ThemeKit struct BackupTypeView: View { @ObservedObject var viewModel: BackupAppViewModel - @Binding var backupPresented: Bool + var onDismiss: (() -> Void)? @State var cloudNavigationPushed = false @State var localNavigationPushed = false @@ -11,19 +11,18 @@ struct BackupTypeView: View { var body: some View { ScrollableThemeView { - VStack(spacing: .margin24) { - Text("backup_app.backup_type.description".localized) - .themeSubhead2() - .padding(EdgeInsets(top: 0, leading: .margin16, bottom: .margin12, trailing: .margin16)) - + VStack(spacing: .margin12) { ListSection { - navigation(image: "icloud_24", text: "backup_app.backup_type.cloud".localized, isAvailable: $viewModel.cloudAvailable, isActive: $cloudNavigationPushed) { + navigation( + image: "icloud_24", + text: "backup_app.backup_type.cloud".localized, + description: "backup_app.backup_type.cloud.description".localized, + isAvailable: $viewModel.cloudAvailable, + isActive: $cloudNavigationPushed + ) { if viewModel.cloudAvailable { viewModel.destination = .cloud } else { cloudAlertPresented = true } } - - navigation(image: "file_24", text: "backup_app.backup_type.file".localized, isActive: $localNavigationPushed) { - viewModel.destination = .local - } + .frame(minHeight: 106) } .sheet(isPresented: $cloudAlertPresented) { if #available(iOS 16, *) { @@ -32,38 +31,53 @@ struct BackupTypeView: View { ViewWrapper(BottomSheetModule.cloudNotAvailableController()) } } - } - .navigationBarTitle("backup_app.backup_type.title".localized) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - Button("button.cancel".localized) { - backupPresented = false + + ListSection { + navigation( + image: "file_24", + text: "backup_app.backup_type.file".localized, + description: "backup_app.backup_type.file.description".localized, + isActive: $localNavigationPushed + ) { + viewModel.destination = .local + } + .frame(minHeight: 106) } } .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) } + .navigationTitle("backup_app.backup_type.title".localized) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + Button("button.cancel".localized) { + onDismiss?() + } + } } - @ViewBuilder func row(image: String, text: String) -> some View { + @ViewBuilder func row(image: String, text: String, description: String) -> some View { HStack(spacing: .margin16) { Image(image).themeIcon() - Text(text).themeBody() - Image.disclosureIcon + VStack(spacing: .margin4) { + Text(text).themeBody() + Text(description).themeSubhead2() + } } + .padding(EdgeInsets(top: .margin12, leading: 0, bottom: .margin12, trailing: 0)) } - @ViewBuilder func navigation(image: String, text: String, isAvailable: Binding = .constant(true), isActive: Binding, action: @escaping () -> Void = {}) -> some View { + @ViewBuilder func navigation(image: String, text: String, description: String, isAvailable: Binding = .constant(true), isActive: Binding, action: @escaping () -> Void = {}) -> some View { if isAvailable.wrappedValue { NavigationRow( - destination: { BackupListView(viewModel: viewModel, backupPresented: $backupPresented) }, + destination: { BackupListView(viewModel: viewModel, onDismiss: onDismiss) }, isActive: isActive ) { - row(image: image, text: text.localized) + row(image: image, text: text.localized, description: description) } .onChange(of: isActive.wrappedValue) { _ in action() } } else { ClickableRow(action: action) { - row(image: image, text: text.localized) + row(image: image, text: text.localized, description: description) } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupAppModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupAppModule.swift index 2285de7138..d474139e49 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupAppModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupAppModule.swift @@ -1,7 +1,7 @@ import SwiftUI struct BackupAppModule { - static func view(backupPresented: Binding) -> some View { + static func view(onDismiss: (() -> ())?) -> some View { let viewModel = BackupAppViewModel( accountManager: App.shared.accountManager, contactManager: App.shared.contactManager, @@ -10,7 +10,7 @@ struct BackupAppModule { evmSyncSourceManager: App.shared.evmSyncSourceManager ) - return BackupTypeView(viewModel: viewModel, backupPresented: backupPresented) + return BackupTypeView(viewModel: viewModel, onDismiss: onDismiss) } } @@ -58,28 +58,28 @@ extension BackupAppModule { if watchAccountCount != 0 { items.append(BackupAppModule.Item( title: "backup_app.backup_list.other.watch_account.title".localized, - description: "backup_app.backup_list.other.watch_account.description".localized(watchAccountCount) + value: watchAccountCount.description )) } if watchlistCount != 0 { items.append(BackupAppModule.Item( title: "backup_app.backup_list.other.watchlist.title".localized, - description: "backup_app.backup_list.other.watchlist.description".localized(watchlistCount) + value: watchlistCount.description )) } if contactAddressCount != 0 { items.append(BackupAppModule.Item( title: "backup_app.backup_list.other.contacts.title".localized, - description: "backup_app.backup_list.other.contacts.description".localized(contactAddressCount) + value: contactAddressCount.description )) } if blockchainSourcesCount != 0 { items.append(BackupAppModule.Item( title: "backup_app.backup_list.other.blockchain_settings.title".localized, - description: "backup_app.backup_list.other.blockchain_settings.description".localized(blockchainSourcesCount) + value: blockchainSourcesCount.description )) } items.append(BackupAppModule.Item( @@ -113,7 +113,14 @@ extension BackupAppModule { struct Item: Identifiable { let title: String - let description: String + let value: String? + let description: String? + + init(title: String, value: String? = nil, description: String? = nil) { + self.title = title + self.value = value + self.description = description + } var id: String { title diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManager/BackupManagerModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManager/BackupManagerModule.swift deleted file mode 100644 index 785b0ea0be..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManager/BackupManagerModule.swift +++ /dev/null @@ -1,7 +0,0 @@ -import SwiftUI - -struct BackupManagerModule { - static func view() -> some View { - BackupManagerView() - } -} \ No newline at end of file diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManager/BackupManagerView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManager/BackupManagerView.swift deleted file mode 100644 index 6c919ec1e1..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManager/BackupManagerView.swift +++ /dev/null @@ -1,35 +0,0 @@ -import SDWebImageSwiftUI -import SwiftUI -import ThemeKit - -struct BackupManagerView: View { - @State var restorePresented: Bool = false - @State var backupPresented: Bool = false - - var body: some View { - ScrollableThemeView { - ListSection { - ClickableRow(action: { - restorePresented = true - }) { - Image("download_24").themeIcon(color: .themeJacob) - Text("backup_app.backup_manager.restore".localized).themeBody(color: .themeJacob) - } - ClickableRow(action: { - backupPresented = true - }) { - Image("plus_24").themeIcon(color: .themeJacob) - Text("backup_app.backup_manager.create".localized).themeBody(color: .themeJacob) - } - } - .sheet(isPresented: $restorePresented) { - InfoModule.restoreSourceInfo - } - .sheet(isPresented: $backupPresented) { - ThemeNavigationView { BackupAppModule.view(backupPresented: $backupPresented) } - } - .navigationBarTitle("backup_app.backup_manager.title".localized) - .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) - } - } -} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManagerLegacy/BackupManagerModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManagerLegacy/BackupManagerModule.swift new file mode 100644 index 0000000000..1134aa2fb2 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManagerLegacy/BackupManagerModule.swift @@ -0,0 +1,7 @@ +import UIKit + +class BackupManagerModule { + static func viewController() -> UIViewController { + BackupManagerViewController() + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManagerLegacy/BackupManagerViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManagerLegacy/BackupManagerViewController.swift new file mode 100644 index 0000000000..4854618775 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/BackupManagerLegacy/BackupManagerViewController.swift @@ -0,0 +1,94 @@ +import Combine +import SectionsTableView +import SwiftUI +import ThemeKit +import UIKit + +class BackupManagerViewController: ThemeViewController { + private let tableView = SectionsTableView(style: .grouped) + + override init() { + super.init() + + hidesBottomBarWhenPushed = true + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "backup_app.backup_manager.title".localized + navigationItem.largeTitleDisplayMode = .never + + view.addSubview(tableView) + tableView.snp.makeConstraints { maker in + maker.edges.equalToSuperview() + } + + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + + tableView.sectionDataSource = self + + tableView.buildSections() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + tableView.deselectCell(withCoordinator: transitionCoordinator, animated: animated) + } + + private func onRestore() { + let viewController = RestoreTypeModule.viewController(type: .full, sourceViewController: self) + present(viewController, animated: true) + } + + private func onCreate() { + let viewController = BackupAppModule + .view { [weak self] in + self?.presentedViewController?.dismiss(animated: true) + }.toNavigationViewController() + + present(viewController, animated: true) + } +} + +extension BackupManagerViewController: SectionsDataSource { + func buildSections() -> [SectionProtocol] { + [ + Section( + id: "type-section", + headerState: .margin(height: .margin12), + footerState: .margin(height: .margin32), + rows: [ + tableView.universalRow48( + id: "restore", + image: .local(UIImage(named: "download_24")?.withTintColor(.themeJacob)), + title: .body("backup_app.backup_manager.restore".localized, color: .themeJacob), + backgroundStyle: .lawrence, + autoDeselect: true, + isFirst: true, + isLast: false + ) { [weak self] in + self?.onRestore() + }, + tableView.universalRow48( + id: "create", + image: .local(UIImage(named: "plus_24")?.withTintColor(.themeJacob)), + title: .body("backup_app.backup_manager.create".localized, color: .themeJacob), + backgroundStyle: .lawrence, + autoDeselect: true, + isFirst: false, + isLast: true + ) { [weak self] in + self?.onCreate() + }, + ] + ), + ] + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift index f300cafcd3..519d98edd2 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift @@ -234,7 +234,7 @@ class MainSettingsViewController: ThemeViewController { accessoryType: .disclosure, isLast: true, action: { [weak self] in - let viewController = BackupManagerModule.view().toViewController(title: "backup_manager.title".localized) + let viewController = BackupManagerModule.viewController() self?.navigationController?.pushViewController(viewController, animated: true) } ), diff --git a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings index 5825ab80c3..3bb301c758 100644 --- a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings +++ b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings @@ -138,7 +138,6 @@ Go to Settings - > %@ and allow access to the camera."; // Restore Type "restore_type.title" = "Import Wallet"; - "restore_type.recovery.title" = "from Recovery Phrase"; "restore_type.cloud.title" = "from iCloud"; "restore_type.file.title" = "from Files"; @@ -1097,23 +1096,20 @@ Go to Settings - > %@ and allow access to the camera."; "backup_app.backup_manager.restore" = "Restore Backup"; "backup_app.backup_manager.create" = "Create New Backup"; -"backup_app.backup_type.title" = "Backup Type"; -"backup_app.backup_type.description" = "Select where you want to save the backup file."; -"backup_app.backup_type.cloud" = "Backup to iCloud"; -"backup_app.backup_type.file" = "Backup to Files"; +"backup_app.backup_type.title" = "Save Backup"; +"backup_app.backup_type.cloud" = "to iCloud"; +"backup_app.backup_type.cloud.description" = "Saving a backup copy file in the your keychain."; +"backup_app.backup_type.file" = "to Files"; +"backup_app.backup_type.file.description" = "Saving a backup copy file to your local folder."; "backup_app.backup_list.title" = "Backup File"; "backup_app.backup_list.description.restore" = "List of contents in the backup file."; "backup_app.backup_list.header.wallets" = "Wallets"; "backup_app.backup_list.header.other" = "Other"; "backup_app.backup_list.other.watch_account.title" = "Watch Wallets"; -"backup_app.backup_list.other.watch_account.description" = "Addresses: %d"; "backup_app.backup_list.other.watchlist.title" = "Watchlist"; -"backup_app.backup_list.other.watchlist.description" = "Coins: %d"; "backup_app.backup_list.other.contacts.title" = "Contacts"; -"backup_app.backup_list.other.contacts.description" = "Addresses: %d"; "backup_app.backup_list.other.blockchain_settings.title" = "Blockchain Settings"; -"backup_app.backup_list.other.blockchain_settings.description" = "Custom RPCs: %d"; "backup_app.backup_list.other.app_settings.title" = "App Settings"; "backup_app.backup_list.other.app_settings.description" = "Language, Currency, Appearance ..."; @@ -1137,8 +1133,10 @@ Go to Settings - > %@ and allow access to the camera."; "backup_app.backup.password.description" = "Set unlock password for your backup. It must consist of at least 8 symbols and include at least one lowercase letter, uppercase letter, number and a special character."; "backup_app.backup.password.highlighted_description" = "This password is used to encrypt backup file of your wallet. It can't be recovered or reset if lost or forgotten."; -"backup_app.restore.notice.description" = "As a result of this action, wallets and contacts from a backup file will be added to the existing wallets and contacts in the app."; -"backup_app.restore.notice.merge" = "Merge"; +"backup_app.restore_type.title" = "Restore"; + +"backup_app.restore.notice.description" = "This action will overwrite your local payment contacts as well as its iCloud copy (if there is one)"; +"backup_app.restore.notice.merge" = "Replace"; "backup.password.title" = "Backup Password"; "backup.password.description" = "Set unlock password for your backup. It must consist of at least 8 symbols and include at least one lowercase letter, uppercase letter, number and a special character.";