diff --git a/.swiftlint.yml b/.swiftlint.yml index b3a5d195..ebe08d57 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -19,6 +19,7 @@ disabled_rules: - multiple_closures_with_trailing_closure - function_parameter_count - todo + - opening_brace # False positives on regex excluded: - Pods diff --git a/MobileWallet.xcodeproj/project.pbxproj b/MobileWallet.xcodeproj/project.pbxproj index 1c3dbff6..979b0d49 100644 --- a/MobileWallet.xcodeproj/project.pbxproj +++ b/MobileWallet.xcodeproj/project.pbxproj @@ -105,7 +105,6 @@ 3A1D8E4B28400AB8004129D5 /* AboutViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1D8E4A28400AB8004129D5 /* AboutViewHeader.swift */; }; 3A20A6B82722EE00002B15DB /* VideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A20A6B72722EDFF002B15DB /* VideoView.swift */; }; 3A21FC5128FFD7B8004B09A0 /* PopUpTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A21FC5028FFD7B8004B09A0 /* PopUpTableView.swift */; }; - 3A21FC5328FFDD10004B09A0 /* UITableViewDiffableDataSource+Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A21FC5228FFDD10004B09A0 /* UITableViewDiffableDataSource+Update.swift */; }; 3A237C7B27D8D0F7005FA6AB /* SeedWordsListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A237C7A27D8D0F7005FA6AB /* SeedWordsListModel.swift */; }; 3A237C7D27D8E141005FA6AB /* SeedWordsListConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A237C7C27D8E140005FA6AB /* SeedWordsListConstructor.swift */; }; 3A2B95682864801D0085BE21 /* UTXOsWalletTileListLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2B95672864801D0085BE21 /* UTXOsWalletTileListLayout.swift */; }; @@ -293,9 +292,6 @@ 3ADA73DD270A02B900D50E3B /* WalletSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADA73DC270A02B900D50E3B /* WalletSettings.swift */; }; 3ADA73DF270A02DF00D50E3B /* WalletSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADA73DE270A02DF00D50E3B /* WalletSettingsManager.swift */; }; 3ADEBF5D26A54FA500E87C84 /* SelectBaseNodeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADEBF5C26A54FA500E87C84 /* SelectBaseNodeModel.swift */; }; - 3ADEBF6226A59A5900E87C84 /* AddBaseNodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADEBF6126A59A5900E87C84 /* AddBaseNodeViewController.swift */; }; - 3ADEBF6626A5B5A500E87C84 /* AddBaseNodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADEBF6526A5B5A500E87C84 /* AddBaseNodeView.swift */; }; - 3ADEBF6A26A5C10E00E87C84 /* AddBaseNodeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADEBF6926A5C10E00E87C84 /* AddBaseNodeModel.swift */; }; 3ADF96CC28585E2400A3C888 /* ContextualButtonsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF96CB28585E2400A3C888 /* ContextualButtonsOverlay.swift */; }; 3ADF96CE2858A93000A3C888 /* ContextualButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF96CD2858A93000A3C888 /* ContextualButton.swift */; }; 3AE138C928044A1800443D34 /* PopUpComponentsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE138C828044A1800443D34 /* PopUpComponentsFactory.swift */; }; @@ -374,6 +370,9 @@ 5421551029A4AE37000A3F49 /* ContactBookContactListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5421550F29A4AE37000A3F49 /* ContactBookContactListViewController.swift */; }; 5421551229A4AE4B000A3F49 /* ContactBookContactListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5421551129A4AE4B000A3F49 /* ContactBookContactListView.swift */; }; 5421551429A4AE92000A3F49 /* SearchTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5421551329A4AE92000A3F49 /* SearchTextField.swift */; }; + 5422CBF82B88B1CA00428394 /* ScreenRecordingSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5422CBF72B88B1CA00428394 /* ScreenRecordingSettingsViewController.swift */; }; + 5422CBFA2B88B5C900428394 /* ScreenRecordingSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5422CBF92B88B5C800428394 /* ScreenRecordingSettingsModel.swift */; }; + 5422CBFC2B88C79200428394 /* ScreenRecordingSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5422CBFB2B88C79200428394 /* ScreenRecordingSettingsView.swift */; }; 542585C32A332F08009D12CD /* RotaryMenuOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542585C22A332F08009D12CD /* RotaryMenuOverlay.swift */; }; 542585C52A332F32009D12CD /* RotaryMenuOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542585C42A332F32009D12CD /* RotaryMenuOverlayView.swift */; }; 542585C82A334697009D12CD /* RotaryMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542585C72A334697009D12CD /* RotaryMenuView.swift */; }; @@ -385,6 +384,7 @@ 5430B97D29B7402600C80AA2 /* LinkContactsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5430B97C29B7402600C80AA2 /* LinkContactsConstructor.swift */; }; 54394EDE29CA133400E7CAEA /* LoadingImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */; }; 543DF19C293F3DA90031EA70 /* TorBridgesFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543DF19B293F3DA90031EA70 /* TorBridgesFooterView.swift */; }; + 543F89D02B98811F00B9821C /* ScreenRecordingSettingsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543F89CF2B98811F00B9821C /* ScreenRecordingSettingsConstructor.swift */; }; 544013A429E0003100B5DD6D /* ContactListDeeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544013A329E0003100B5DD6D /* ContactListDeeplink.swift */; }; 5443954629800F1600786682 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5443954529800F1600786682 /* OnboardingViewController.swift */; }; 5444C80329F14C2900BF3875 /* PopUpCircleImageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5444C80229F14C2900BF3875 /* PopUpCircleImageHeaderView.swift */; }; @@ -419,6 +419,7 @@ 545A9D1F294F9323008D24A6 /* RadioButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545A9D1E294F9323008D24A6 /* RadioButtonView.swift */; }; 545A9D21294F9CCE008D24A6 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545A9D20294F9CCE008D24A6 /* UserSettings.swift */; }; 545A9D24294F9E77008D24A6 /* UserSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545A9D23294F9E77008D24A6 /* UserSettingsManager.swift */; }; + 545C92B32B5EDA4000FCB3ED /* StringSimilarityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545C92B22B5EDA4000FCB3ED /* StringSimilarityTests.swift */; }; 5460258629A74D8200CF5764 /* ContactDetailsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5460258529A74D8200CF5764 /* ContactDetailsModel.swift */; }; 5460258829A74DA200CF5764 /* ContactDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5460258729A74DA200CF5764 /* ContactDetailsViewController.swift */; }; 5460258A29A74DAB00CF5764 /* ContactDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5460258929A74DAB00CF5764 /* ContactDetailsView.swift */; }; @@ -449,6 +450,7 @@ 5482C8D22A714A7300C2C80A /* BaseToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5482C8D12A714A7300C2C80A /* BaseToolbar.swift */; }; 5488068729D4230500C2A0F9 /* UIStackView+Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5488068629D4230500C2A0F9 /* UIStackView+Common.swift */; }; 5488068929D4244800C2A0F9 /* ContactBookShareBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5488068829D4244800C2A0F9 /* ContactBookShareBar.swift */; }; + 54884FBC2B596E640043E2B4 /* AddressPoisoningDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54884FBB2B596E640043E2B4 /* AddressPoisoningDataHandler.swift */; }; 54885B1929E7FEA8009175AC /* BLECentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54885B1829E7FEA8009175AC /* BLECentralManager.swift */; }; 54885B1B29E802D7009175AC /* BLEConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54885B1A29E802D7009175AC /* BLEConstants.swift */; }; 548C137B29BF64AD00ACDF0C /* UITableView+Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548C137A29BF64AD00ACDF0C /* UITableView+Common.swift */; }; @@ -459,6 +461,8 @@ 5494BFB22AF0E9B500791A52 /* TorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5494BFB12AF0E9B500791A52 /* TorError.swift */; }; 5494BFB32AF122A600791A52 /* RoundedLabeledButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A9C88929F6A54B009B0653 /* RoundedLabeledButton.swift */; }; 549565E02AA1D21E0092A10F /* Task+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549565DF2AA1D21E0092A10F /* Task+Utils.swift */; }; + 5495C4792B7B718E00BB9051 /* SecureWrapperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5495C4782B7B718E00BB9051 /* SecureWrapperView.swift */; }; + 549B033C2B84DA8D0059983C /* SecureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549B033B2B84DA8D0059983C /* SecureViewController.swift */; }; 549E0E0D29754E9C00828743 /* PartialBackupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E0E0C29754E9B00828743 /* PartialBackupModel.swift */; }; 549E1A012A5EB7B00063022C /* QRCodeScannerBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E1A002A5EB7B00063022C /* QRCodeScannerBoxView.swift */; }; 549E1A032A5EC2260063022C /* VideoCaptureManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E1A022A5EC2260063022C /* VideoCaptureManager.swift */; }; @@ -471,15 +475,19 @@ 54A783DB29E96FF600A30594 /* BLEPeripheralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A783DA29E96FF600A30594 /* BLEPeripheralManager.swift */; }; 54A95B3C29747C19003CCE5C /* TariUnspentOutputsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A95B3B29747C19003CCE5C /* TariUnspentOutputsService.swift */; }; 54A9C88829F6A06F009B0653 /* DataFlowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A9C88729F6A06F009B0653 /* DataFlowManager.swift */; }; + 54AA2DF92B55596600D38184 /* PopUpAddressPoisoningContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AA2DF82B55596600D38184 /* PopUpAddressPoisoningContentView.swift */; }; 54AA5D5A298122B70031A396 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AA5D59298122B70031A396 /* OnboardingView.swift */; }; 54AA5D5C2981267C0031A396 /* OnboardingPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AA5D5B2981267C0031A396 /* OnboardingPageViewController.swift */; }; 54AA5D5E298126CA0031A396 /* OnboardingPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AA5D5D298126CA0031A396 /* OnboardingPageView.swift */; }; + 54AC12922BCD1C1E0007FFEA /* StringBaseNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AC12912BCD1C1E0007FFEA /* StringBaseNodeTests.swift */; }; 54AD5F662A420A9F00D223B8 /* Data+Utlis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AD5F652A420A9F00D223B8 /* Data+Utlis.swift */; }; 54ADC0932A3858C40082C455 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ADC0922A3858C40082C455 /* ErrorView.swift */; }; 54B7F4452996583800BB484B /* ContactBookCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7F4442996583800BB484B /* ContactBookCell.swift */; }; 54B854B929BF993600A2367A /* ContactBookListPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B854B829BF993600A2367A /* ContactBookListPlaceholder.swift */; }; 54BB5E052A2F081300E239C7 /* UserProfileDeeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BB5E042A2F081300E239C7 /* UserProfileDeeplink.swift */; }; 54BBAF032A37238A002BC64B /* RotaryMenuCircleBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BBAF022A37238A002BC64B /* RotaryMenuCircleBackgroundView.swift */; }; + 54BFE3F62B99C74800273272 /* PublicKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BFE3F52B99C74800273272 /* PublicKeys.swift */; }; + 54C02B912BA075120057301A /* BaseNodeConnectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C02B902BA075120057301A /* BaseNodeConnectionError.swift */; }; 54C36D0B2A5D3E9800BD973A /* QRCodeScannerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C36D0A2A5D3E9800BD973A /* QRCodeScannerModel.swift */; }; 54C36D0D2A5D3EA400BD973A /* QRCodeScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C36D0C2A5D3EA400BD973A /* QRCodeScannerViewController.swift */; }; 54C36D0F2A5D3EAC00BD973A /* QRCodeScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C36D0E2A5D3EAC00BD973A /* QRCodeScannerView.swift */; }; @@ -490,6 +498,7 @@ 54CB24F829FA65230008632D /* PaymentInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CB24F729FA65230008632D /* PaymentInfo.swift */; }; 54CB354429B905AA00551D5A /* PopPresenter+ContactBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CB354329B905AA00551D5A /* PopPresenter+ContactBook.swift */; }; 54CF5E012A27460700B01F21 /* ContactBookBluetoothCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CF5E002A27460700B01F21 /* ContactBookBluetoothCell.swift */; }; + 54D1CCC02B4D4C7200808535 /* AddressPoisoningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D1CCBF2B4D4C7200808535 /* AddressPoisoningManager.swift */; }; 54D1E1A32ABD92290021A365 /* DataCollectionSettingsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D1E1A22ABD92290021A365 /* DataCollectionSettingsConstructor.swift */; }; 54D1E1A52ABD92380021A365 /* DataCollectionSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D1E1A42ABD92380021A365 /* DataCollectionSettingsModel.swift */; }; 54D1E1A72ABD92420021A365 /* DataCollectionSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D1E1A62ABD92420021A365 /* DataCollectionSettingsViewController.swift */; }; @@ -499,6 +508,7 @@ 54D419B72995094100D496B4 /* ContactBookView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D419B62995094100D496B4 /* ContactBookView.swift */; }; 54D419B92995094B00D496B4 /* ContactBookConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D419B82995094B00D496B4 /* ContactBookConstructor.swift */; }; 54D4747929EDC21D003D14E6 /* FormOverlayPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D4747829EDC21D003D14E6 /* FormOverlayPresenter.swift */; }; + 54D782F62BA8616F00DD3BFE /* BaseNodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D782F52BA8616F00DD3BFE /* BaseNodeState.swift */; }; 54D835582AC6C99D00D74FE8 /* AccessoryImageMenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D835572AC6C99D00D74FE8 /* AccessoryImageMenuCell.swift */; }; 54D88EA22A5ADBFA0075DBC1 /* TransactionFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D88EA12A5ADBFA0075DBC1 /* TransactionFormatter.swift */; }; 54D88EA42A5AEAEC0075DBC1 /* TransactionDynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D88EA32A5AEAEC0075DBC1 /* TransactionDynamicModel.swift */; }; @@ -510,6 +520,7 @@ 54DF83C12A4C22C30040E3F4 /* HomeViewTransactionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DF83C02A4C22C30040E3F4 /* HomeViewTransactionCell.swift */; }; 54DF83C32A4C59380040E3F4 /* AmountBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DF83C22A4C59380040E3F4 /* AmountBadge.swift */; }; 54DF83C52A4C5A220040E3F4 /* RoundedGlassContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DF83C42A4C5A220040E3F4 /* RoundedGlassContentView.swift */; }; + 54E007B62B8DF55600AFCD7C /* SecurityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E007B52B8DF55600AFCD7C /* SecurityManager.swift */; }; 54E1CB1E29C7292700E6777C /* EmojiTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E1CB1D29C7292700E6777C /* EmojiTextField.swift */; }; 54ED0FBE2A5D315400ED1F7E /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ED0FBD2A5D315400ED1F7E /* SearchField.swift */; }; 54F23DE729BBA0C3001E39A2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 54F23DE629BBA0C3001E39A2 /* InfoPlist.strings */; }; @@ -684,7 +695,6 @@ 3A1D8E4A28400AB8004129D5 /* AboutViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewHeader.swift; sourceTree = ""; }; 3A20A6B72722EDFF002B15DB /* VideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoView.swift; sourceTree = ""; }; 3A21FC5028FFD7B8004B09A0 /* PopUpTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpTableView.swift; sourceTree = ""; }; - 3A21FC5228FFDD10004B09A0 /* UITableViewDiffableDataSource+Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewDiffableDataSource+Update.swift"; sourceTree = ""; }; 3A237C7A27D8D0F7005FA6AB /* SeedWordsListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedWordsListModel.swift; sourceTree = ""; }; 3A237C7C27D8E140005FA6AB /* SeedWordsListConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedWordsListConstructor.swift; sourceTree = ""; }; 3A2B95672864801D0085BE21 /* UTXOsWalletTileListLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletTileListLayout.swift; sourceTree = ""; }; @@ -872,9 +882,6 @@ 3ADA73DC270A02B900D50E3B /* WalletSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSettings.swift; sourceTree = ""; }; 3ADA73DE270A02DF00D50E3B /* WalletSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSettingsManager.swift; sourceTree = ""; }; 3ADEBF5C26A54FA500E87C84 /* SelectBaseNodeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectBaseNodeModel.swift; sourceTree = ""; }; - 3ADEBF6126A59A5900E87C84 /* AddBaseNodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBaseNodeViewController.swift; sourceTree = ""; }; - 3ADEBF6526A5B5A500E87C84 /* AddBaseNodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBaseNodeView.swift; sourceTree = ""; }; - 3ADEBF6926A5C10E00E87C84 /* AddBaseNodeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBaseNodeModel.swift; sourceTree = ""; }; 3ADF96CB28585E2400A3C888 /* ContextualButtonsOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualButtonsOverlay.swift; sourceTree = ""; }; 3ADF96CD2858A93000A3C888 /* ContextualButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualButton.swift; sourceTree = ""; }; 3AE138C828044A1800443D34 /* PopUpComponentsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpComponentsFactory.swift; sourceTree = ""; }; @@ -953,6 +960,9 @@ 5421550F29A4AE37000A3F49 /* ContactBookContactListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookContactListViewController.swift; sourceTree = ""; }; 5421551129A4AE4B000A3F49 /* ContactBookContactListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookContactListView.swift; sourceTree = ""; }; 5421551329A4AE92000A3F49 /* SearchTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTextField.swift; sourceTree = ""; }; + 5422CBF72B88B1CA00428394 /* ScreenRecordingSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecordingSettingsViewController.swift; sourceTree = ""; }; + 5422CBF92B88B5C800428394 /* ScreenRecordingSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecordingSettingsModel.swift; sourceTree = ""; }; + 5422CBFB2B88C79200428394 /* ScreenRecordingSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecordingSettingsView.swift; sourceTree = ""; }; 542585C22A332F08009D12CD /* RotaryMenuOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuOverlay.swift; sourceTree = ""; }; 542585C42A332F32009D12CD /* RotaryMenuOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuOverlayView.swift; sourceTree = ""; }; 542585C72A334697009D12CD /* RotaryMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuView.swift; sourceTree = ""; }; @@ -964,6 +974,7 @@ 5430B97C29B7402600C80AA2 /* LinkContactsConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkContactsConstructor.swift; sourceTree = ""; }; 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingImageView.swift; sourceTree = ""; }; 543DF19B293F3DA90031EA70 /* TorBridgesFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBridgesFooterView.swift; sourceTree = ""; }; + 543F89CF2B98811F00B9821C /* ScreenRecordingSettingsConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecordingSettingsConstructor.swift; sourceTree = ""; }; 544013A329E0003100B5DD6D /* ContactListDeeplink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListDeeplink.swift; sourceTree = ""; }; 5443954529800F1600786682 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; 5444C80229F14C2900BF3875 /* PopUpCircleImageHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpCircleImageHeaderView.swift; sourceTree = ""; }; @@ -998,6 +1009,7 @@ 545A9D1E294F9323008D24A6 /* RadioButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonView.swift; sourceTree = ""; }; 545A9D20294F9CCE008D24A6 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; 545A9D23294F9E77008D24A6 /* UserSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsManager.swift; sourceTree = ""; }; + 545C92B22B5EDA4000FCB3ED /* StringSimilarityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringSimilarityTests.swift; sourceTree = ""; }; 5460258529A74D8200CF5764 /* ContactDetailsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailsModel.swift; sourceTree = ""; }; 5460258729A74DA200CF5764 /* ContactDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailsViewController.swift; sourceTree = ""; }; 5460258929A74DAB00CF5764 /* ContactDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailsView.swift; sourceTree = ""; }; @@ -1028,6 +1040,7 @@ 5482C8D12A714A7300C2C80A /* BaseToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseToolbar.swift; sourceTree = ""; }; 5488068629D4230500C2A0F9 /* UIStackView+Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+Common.swift"; sourceTree = ""; }; 5488068829D4244800C2A0F9 /* ContactBookShareBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookShareBar.swift; sourceTree = ""; }; + 54884FBB2B596E640043E2B4 /* AddressPoisoningDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressPoisoningDataHandler.swift; sourceTree = ""; }; 54885B1829E7FEA8009175AC /* BLECentralManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLECentralManager.swift; sourceTree = ""; }; 54885B1A29E802D7009175AC /* BLEConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEConstants.swift; sourceTree = ""; }; 548C137A29BF64AD00ACDF0C /* UITableView+Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Common.swift"; sourceTree = ""; }; @@ -1037,6 +1050,8 @@ 5494BFAF2AF0E98E00791A52 /* TorConnectionStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorConnectionStatus.swift; sourceTree = ""; }; 5494BFB12AF0E9B500791A52 /* TorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorError.swift; sourceTree = ""; }; 549565DF2AA1D21E0092A10F /* Task+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Task+Utils.swift"; sourceTree = ""; }; + 5495C4782B7B718E00BB9051 /* SecureWrapperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureWrapperView.swift; sourceTree = ""; }; + 549B033B2B84DA8D0059983C /* SecureViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureViewController.swift; sourceTree = ""; }; 549E0E0C29754E9B00828743 /* PartialBackupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialBackupModel.swift; sourceTree = ""; }; 549E1A002A5EB7B00063022C /* QRCodeScannerBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerBoxView.swift; sourceTree = ""; }; 549E1A022A5EC2260063022C /* VideoCaptureManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCaptureManager.swift; sourceTree = ""; }; @@ -1050,15 +1065,19 @@ 54A95B3B29747C19003CCE5C /* TariUnspentOutputsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariUnspentOutputsService.swift; sourceTree = ""; }; 54A9C88729F6A06F009B0653 /* DataFlowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataFlowManager.swift; sourceTree = ""; }; 54A9C88929F6A54B009B0653 /* RoundedLabeledButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabeledButton.swift; sourceTree = ""; }; + 54AA2DF82B55596600D38184 /* PopUpAddressPoisoningContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpAddressPoisoningContentView.swift; sourceTree = ""; }; 54AA5D59298122B70031A396 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; 54AA5D5B2981267C0031A396 /* OnboardingPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPageViewController.swift; sourceTree = ""; }; 54AA5D5D298126CA0031A396 /* OnboardingPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPageView.swift; sourceTree = ""; }; + 54AC12912BCD1C1E0007FFEA /* StringBaseNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringBaseNodeTests.swift; sourceTree = ""; }; 54AD5F652A420A9F00D223B8 /* Data+Utlis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utlis.swift"; sourceTree = ""; }; 54ADC0922A3858C40082C455 /* ErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; 54B7F4442996583800BB484B /* ContactBookCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookCell.swift; sourceTree = ""; }; 54B854B829BF993600A2367A /* ContactBookListPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookListPlaceholder.swift; sourceTree = ""; }; 54BB5E042A2F081300E239C7 /* UserProfileDeeplink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileDeeplink.swift; sourceTree = ""; }; 54BBAF022A37238A002BC64B /* RotaryMenuCircleBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuCircleBackgroundView.swift; sourceTree = ""; }; + 54BFE3F52B99C74800273272 /* PublicKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKeys.swift; sourceTree = ""; }; + 54C02B902BA075120057301A /* BaseNodeConnectionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNodeConnectionError.swift; sourceTree = ""; }; 54C36D0A2A5D3E9800BD973A /* QRCodeScannerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerModel.swift; sourceTree = ""; }; 54C36D0C2A5D3EA400BD973A /* QRCodeScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerViewController.swift; sourceTree = ""; }; 54C36D0E2A5D3EAC00BD973A /* QRCodeScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerView.swift; sourceTree = ""; }; @@ -1069,6 +1088,7 @@ 54CB24F729FA65230008632D /* PaymentInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentInfo.swift; sourceTree = ""; }; 54CB354329B905AA00551D5A /* PopPresenter+ContactBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PopPresenter+ContactBook.swift"; sourceTree = ""; }; 54CF5E002A27460700B01F21 /* ContactBookBluetoothCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookBluetoothCell.swift; sourceTree = ""; }; + 54D1CCBF2B4D4C7200808535 /* AddressPoisoningManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressPoisoningManager.swift; sourceTree = ""; }; 54D1E1A22ABD92290021A365 /* DataCollectionSettingsConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCollectionSettingsConstructor.swift; sourceTree = ""; }; 54D1E1A42ABD92380021A365 /* DataCollectionSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCollectionSettingsModel.swift; sourceTree = ""; }; 54D1E1A62ABD92420021A365 /* DataCollectionSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCollectionSettingsViewController.swift; sourceTree = ""; }; @@ -1078,6 +1098,7 @@ 54D419B62995094100D496B4 /* ContactBookView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookView.swift; sourceTree = ""; }; 54D419B82995094B00D496B4 /* ContactBookConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookConstructor.swift; sourceTree = ""; }; 54D4747829EDC21D003D14E6 /* FormOverlayPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormOverlayPresenter.swift; sourceTree = ""; }; + 54D782F52BA8616F00DD3BFE /* BaseNodeState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNodeState.swift; sourceTree = ""; }; 54D835572AC6C99D00D74FE8 /* AccessoryImageMenuCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryImageMenuCell.swift; sourceTree = ""; }; 54D88EA12A5ADBFA0075DBC1 /* TransactionFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionFormatter.swift; sourceTree = ""; }; 54D88EA32A5AEAEC0075DBC1 /* TransactionDynamicModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDynamicModel.swift; sourceTree = ""; }; @@ -1089,6 +1110,7 @@ 54DF83C02A4C22C30040E3F4 /* HomeViewTransactionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewTransactionCell.swift; sourceTree = ""; }; 54DF83C22A4C59380040E3F4 /* AmountBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountBadge.swift; sourceTree = ""; }; 54DF83C42A4C5A220040E3F4 /* RoundedGlassContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedGlassContentView.swift; sourceTree = ""; }; + 54E007B52B8DF55600AFCD7C /* SecurityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityManager.swift; sourceTree = ""; }; 54E1CB1D29C7292700E6777C /* EmojiTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiTextField.swift; sourceTree = ""; }; 54ED0FBD2A5D315400ED1F7E /* SearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchField.swift; sourceTree = ""; }; 54F23DE629BBA0C3001E39A2 /* InfoPlist.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = InfoPlist.strings; sourceTree = ""; }; @@ -1222,7 +1244,6 @@ 37C8BA46248126B4005BBC05 /* UIScrollView+RefreshControl.swift */, 5488068629D4230500C2A0F9 /* UIStackView+Common.swift */, 548C137A29BF64AD00ACDF0C /* UITableView+Common.swift */, - 3A21FC5228FFDD10004B09A0 /* UITableViewDiffableDataSource+Update.swift */, 3AFAC361271C6F9E008AA842 /* UITextField+Combine.swift */, BFC5532423D9B8E4009130A8 /* UIView+Content.swift */, 37547D5524601BF600EB59CC /* UIView+GlobalFrame.swift */, @@ -1238,35 +1259,35 @@ 00262EB2236F022000A6C8A0 /* Common */ = { isa = PBXGroup; children = ( + 37AFE263245193CA006EA270 /* AlwaysPoppableNavigationController */, + 00B4D6F4241B777500ED8318 /* BackgroundTasks */, 54CB24F629FA64F50008632D /* Data Models */, + 3AEDBE4B27CE454E006B0166 /* Deep Links */, + 3A87C3B327A9406F007A553F /* Errors */, + 00262EAD236F013500A6C8A0 /* Extensions */, + 3A420598279804A700A8D49C /* Factories */, + 3A42059B279804E900A8D49C /* Formatters */, + 3A386F3527A7FC580027FED4 /* Managers */, 5443954429800F0500786682 /* Onboarding */, - 54DE32CE2930BE170060108A /* Theme */, - 3A70C4F5292E51CF00212026 /* Tools */, + 3A4CE33326A18E3A00ECF460 /* Persistant Data */, 3A6A03312802DB64000432B4 /* Pop-up */, + 3A4CE32A26A18D5600ECF460 /* Property Wrappers */, + 3ACDA6FD26AFD6B800F138B8 /* Protocols */, + 54DE32CE2930BE170060108A /* Theme */, 3AE138CD2804A86A00443D34 /* Toasts */, - 3AEDBE4B27CE454E006B0166 /* Deep Links */, - 3A87C3B327A9406F007A553F /* Errors */, + 3A70C4F5292E51CF00212026 /* Tools */, 3ACDA6FE26AFF80600F138B8 /* View Controllers */, 3A35413426A738C8002AB5A8 /* Views */, 0048AF42245ADCA300E1A359 /* env.json */, 37765D2524A35BA20091AE2A /* AESEncryption.swift */, + 3A9A08F426E0A83D00D2E75C /* AppRouter.swift */, + 3A90653929001D830084EE66 /* AppValues.swift */, 00BBD800237C5B5200EBF5E6 /* CommandLineArgs.swift */, A01F54A7255EAB7E00F49AFA /* Localization.swift */, 00B4D6F9241B910200ED8318 /* NotificationManager.swift */, - 00262EB3236F024400A6C8A0 /* Theme.swift */, - 37AFE263245193CA006EA270 /* AlwaysPoppableNavigationController */, - 00B4D6F4241B777500ED8318 /* BackgroundTasks */, - 3ACDA6FD26AFD6B800F138B8 /* Protocols */, - 00262EAD236F013500A6C8A0 /* Extensions */, - 3A4CE33326A18E3A00ECF460 /* Persistant Data */, - 3A4CE32A26A18D5600ECF460 /* Property Wrappers */, - 3A42059B279804E900A8D49C /* Formatters */, - 3A420598279804A700A8D49C /* Factories */, - 3A386F3527A7FC580027FED4 /* Managers */, - 3A9A08F426E0A83D00D2E75C /* AppRouter.swift */, 3ADA05F027A41D44007F5677 /* PointerHandler.swift */, + 00262EB3236F024400A6C8A0 /* Theme.swift */, 3AE138D42804ACE100443D34 /* WebBrowserPresenter.swift */, - 3A90653929001D830084EE66 /* AppValues.swift */, ); path = Common; sourceTree = ""; @@ -1549,11 +1570,11 @@ 370E885D24FE6E9A00576F61 /* AdvancedSettings */ = { isa = PBXGroup; children = ( + 5422CBF62B88B1A200428394 /* Screen Recoding Settings */, 543DF198293F37DB0031EA70 /* Bridges */, 3AFC6E012745050500A20287 /* Views */, 3A62674426D76E52007F9895 /* Select Network */, 3A4CE33426A1A02700ECF460 /* SelectBaseNode */, - 3ADEBF6026A59A4700E87C84 /* AddBaseNode */, ); path = AdvancedSettings; sourceTree = ""; @@ -1710,6 +1731,8 @@ 3A87C3B827A96423007A553F /* ErrorMessageManagerTests.swift */, 3AEDBE5827CE4F80006B0166 /* DeepLinkFormatterTests.swift */, 3A70C4F3292E4C1700212026 /* VersionValidatorTests.swift */, + 545C92B22B5EDA4000FCB3ED /* StringSimilarityTests.swift */, + 54AC12912BCD1C1E0007FFEA /* StringBaseNodeTests.swift */, ); path = UnitTests; sourceTree = ""; @@ -1756,14 +1779,15 @@ 3A312FEB28B6314A00A290D3 /* FFI */ = { isa = PBXGroup; children = ( + 54D782F42BA8615D00DD3BFE /* Base Node */, 3AF335DD28D7905C00B48F33 /* Configs */, 3AF335DA28D754EC00B48F33 /* Contacts */, + 54C02B8F2BA075040057301A /* Errors */, 3AF335DC28D78D1F00B48F33 /* Keys */, 3AF335DB28D789F000B48F33 /* Seed Words */, 3A312FEC28B6315A00A290D3 /* Transactions */, 544D9D4A296D9B0000D8ECEF /* Unblinded Outputs */, 4CDEC322273A5E3500999DCB /* Balance.swift */, - 3A8005CE28EAF9380022A38A /* BaseNodeConnectivityStatus.swift */, 00BBD80D237DA07400EBF5E6 /* ByteVector.swift */, 3A8005C728EAF1AB0022A38A /* RestoreWalletStatus.swift */, 3A6F3FF7283BF980005D1793 /* TariFeePerGramStats.swift */, @@ -1798,6 +1822,7 @@ 3A35413426A738C8002AB5A8 /* Views */ = { isa = PBXGroup; children = ( + 549B033A2B84DA790059983C /* Secure View */, 54DF83C22A4C59380040E3F4 /* AmountBadge.swift */, 3AF79D5F2727206200613C24 /* ContactSearchView.swift */, 3A35413926A738F5002AB5A8 /* ContentScrollView.swift */, @@ -1821,19 +1846,21 @@ isa = PBXGroup; children = ( 54885B1729E7FE91009175AC /* BLE */, - 3A386F3627A7FC6C0027FED4 /* ErrorMessageManager.swift */, - 3A052BF927B2940900C93671 /* WalletTransactionsManager.swift */, - 3AF97E6F27CFCA2000FF6A3F /* ShortcutsManager.swift */, + 5482C8CF2A71105700C2C80A /* AnimationHandler.swift */, 3A8CC6C1290BDC70008161DC /* BugReportService.swift */, - 3A3E8BF22924FB4C00490E57 /* MigrationManager.swift */, - 5491696D2940F78000783E54 /* LogFilesManager.swift */, - 54A6E9EF297F1F9900A60853 /* StagedWalletSecurityManager.swift */, 54A9C88729F6A06F009B0653 /* DataFlowManager.swift */, + 3A386F3627A7FC6C0027FED4 /* ErrorMessageManager.swift */, 54C416222A1C914200454096 /* LocalNotificationsManager.swift */, + 5491696D2940F78000783E54 /* LogFilesManager.swift */, + 3A3E8BF22924FB4C00490E57 /* MigrationManager.swift */, 54C416242A1CA81700454096 /* PendingDataManager.swift */, - 549E1A022A5EC2260063022C /* VideoCaptureManager.swift */, - 5482C8CF2A71105700C2C80A /* AnimationHandler.swift */, + 54E007B52B8DF55600AFCD7C /* SecurityManager.swift */, + 3AF97E6F27CFCA2000FF6A3F /* ShortcutsManager.swift */, + 54A6E9EF297F1F9900A60853 /* StagedWalletSecurityManager.swift */, 54C7EB5F2AC6B22C00F387DF /* TrackingConsentManager.swift */, + 549E1A022A5EC2260063022C /* VideoCaptureManager.swift */, + 3A052BF927B2940900C93671 /* WalletTransactionsManager.swift */, + 54D1CCBF2B4D4C7200808535 /* AddressPoisoningManager.swift */, ); path = Managers; sourceTree = ""; @@ -1900,6 +1927,7 @@ children = ( 3A4376EC269C0CB5006107B0 /* Views */, 3A4376E4269C0C10006107B0 /* RestoreWalletFromSeedsViewController.swift */, + 3A4376ED269C0CC1006107B0 /* RestoreWalletFromSeedsView.swift */, 3A4376E8269C0C58006107B0 /* RestoreWalletFromSeedsModel.swift */, ); path = RestoreWalletFromSeeds; @@ -1908,7 +1936,6 @@ 3A4376EC269C0CB5006107B0 /* Views */ = { isa = PBXGroup; children = ( - 3A4376ED269C0CC1006107B0 /* RestoreWalletFromSeedsView.swift */, 3A0EA97926AA8E1C002612D4 /* TokenCollectionView.swift */, 3A0EA97D26AA8E3C002612D4 /* TokenView.swift */, 3A0EA98126AA8E64002612D4 /* TokenInputView.swift */, @@ -2035,6 +2062,7 @@ 54A6E9F1297F1FBA00A60853 /* PopUpStagedWalletSecurityHeaderView.swift */, 54A0903729E553A900D1CDDE /* PopUpQRContentView.swift */, 5444C80229F14C2900BF3875 /* PopUpCircleImageHeaderView.swift */, + 54AA2DF82B55596600D38184 /* PopUpAddressPoisoningContentView.swift */, ); path = Components; sourceTree = ""; @@ -2325,6 +2353,7 @@ isa = PBXGroup; children = ( 3A35412B26A72D9F002AB5A8 /* ViewIdentifiable.swift */, + 54884FBB2B596E640043E2B4 /* AddressPoisoningDataHandler.swift */, ); path = Protocols; sourceTree = ""; @@ -2398,16 +2427,6 @@ path = Wallet; sourceTree = ""; }; - 3ADEBF6026A59A4700E87C84 /* AddBaseNode */ = { - isa = PBXGroup; - children = ( - 3ADEBF6126A59A5900E87C84 /* AddBaseNodeViewController.swift */, - 3ADEBF6526A5B5A500E87C84 /* AddBaseNodeView.swift */, - 3ADEBF6926A5C10E00E87C84 /* AddBaseNodeModel.swift */, - ); - path = AddBaseNode; - sourceTree = ""; - }; 3AE138CD2804A86A00443D34 /* Toasts */ = { isa = PBXGroup; children = ( @@ -2462,6 +2481,7 @@ isa = PBXGroup; children = ( 3A890EC829262744000F5DA6 /* TariAddress.swift */, + 54BFE3F52B99C74800273272 /* PublicKeys.swift */, 001F6CDB238011CA00FA7002 /* PublicKey.swift */, ); path = Keys; @@ -2582,6 +2602,17 @@ path = "Contact List"; sourceTree = ""; }; + 5422CBF62B88B1A200428394 /* Screen Recoding Settings */ = { + isa = PBXGroup; + children = ( + 5422CBF72B88B1CA00428394 /* ScreenRecordingSettingsViewController.swift */, + 5422CBF92B88B5C800428394 /* ScreenRecordingSettingsModel.swift */, + 5422CBFB2B88C79200428394 /* ScreenRecordingSettingsView.swift */, + 543F89CF2B98811F00B9821C /* ScreenRecordingSettingsConstructor.swift */, + ); + path = "Screen Recoding Settings"; + sourceTree = ""; + }; 542585C12A332EE7009D12CD /* Rotary Menu */ = { isa = PBXGroup; children = ( @@ -2839,6 +2870,15 @@ path = Extensions; sourceTree = ""; }; + 549B033A2B84DA790059983C /* Secure View */ = { + isa = PBXGroup; + children = ( + 549B033B2B84DA8D0059983C /* SecureViewController.swift */, + 5495C4782B7B718E00BB9051 /* SecureWrapperView.swift */, + ); + path = "Secure View"; + sourceTree = ""; + }; 549E19FF2A5EB7A50063022C /* Views */ = { isa = PBXGroup; children = ( @@ -2865,6 +2905,14 @@ path = Views; sourceTree = ""; }; + 54C02B8F2BA075040057301A /* Errors */ = { + isa = PBXGroup; + children = ( + 54C02B902BA075120057301A /* BaseNodeConnectionError.swift */, + ); + path = Errors; + sourceTree = ""; + }; 54C36D092A5D3E8700BD973A /* QR Code Scanner */ = { isa = PBXGroup; children = ( @@ -2912,6 +2960,15 @@ path = "Contact Book"; sourceTree = ""; }; + 54D782F42BA8615D00DD3BFE /* Base Node */ = { + isa = PBXGroup; + children = ( + 3A8005CE28EAF9380022A38A /* BaseNodeConnectivityStatus.swift */, + 54D782F52BA8616F00DD3BFE /* BaseNodeState.swift */, + ); + path = "Base Node"; + sourceTree = ""; + }; 54DE32CE2930BE170060108A /* Theme */ = { isa = PBXGroup; children = ( @@ -3167,7 +3224,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1310; - LastUpgradeCheck = 1500; + LastUpgradeCheck = 1530; ORGANIZATIONNAME = "Jason van den Berg"; TargetAttributes = { 00E491952366E08B007B332D = { @@ -3353,6 +3410,7 @@ 3A7DAB9128FDAFD5002CC013 /* LogsListView.swift in Sources */, 3A70C4F7292E51D800212026 /* VersionValidator.swift in Sources */, 3AE138D12804A8B300443D34 /* SuccessToast.swift in Sources */, + 54C02B912BA075120057301A /* BaseNodeConnectionError.swift in Sources */, 5410F5D12951F04B006976DC /* TariWindow.swift in Sources */, 54D1E1A32ABD92290021A365 /* DataCollectionSettingsConstructor.swift in Sources */, 370E887824FEA54100576F61 /* NetworkTools.m in Sources */, @@ -3382,7 +3440,6 @@ 00B4D6FA241B910200ED8318 /* NotificationManager.swift in Sources */, 3AE88506283785100070D1AC /* PopUpModifyFeeContentView.swift in Sources */, 3ACDA85E2721794800F08C70 /* YatTransactionModel.swift in Sources */, - 3A21FC5328FFDD10004B09A0 /* UITableViewDiffableDataSource+Update.swift in Sources */, 5453242429A68D14009281A7 /* TariGradientView.swift in Sources */, 37B48A8324B3968F00F8A8D2 /* AppKeychainWrapper.swift in Sources */, 54DEF9F32987DA8700C4B749 /* UIScreen+Tools.swift in Sources */, @@ -3425,6 +3482,7 @@ 3A8473D228EC568A0015E63A /* TariMessageSignService.swift in Sources */, 543DF19C293F3DA90031EA70 /* TorBridgesFooterView.swift in Sources */, 54103CDA2A555A1A00C456B4 /* TransactionHistoryConstructor.swift in Sources */, + 54D782F62BA8616F00DD3BFE /* BaseNodeState.swift in Sources */, 37765D2624A35BA20091AE2A /* AESEncryption.swift in Sources */, 3A82455627E1EE0D003B6B59 /* TransactionDetailsSectionView.swift in Sources */, 54AD5F662A420A9F00D223B8 /* Data+Utlis.swift in Sources */, @@ -3505,7 +3563,6 @@ 3ACDA70E26B0320900F138B8 /* SeedWordsRecoveryProgressModel.swift in Sources */, 3AE138CF2804A88500443D34 /* ToastPresenter.swift in Sources */, 3A0EA98226AA8E64002612D4 /* TokenInputView.swift in Sources */, - 3ADEBF6226A59A5900E87C84 /* AddBaseNodeViewController.swift in Sources */, 3A530BD2290AF46500C423C7 /* BackupWalletSettingsHeaderView.swift in Sources */, 5419AA7F2A44744A0079E745 /* HomeConstructor.swift in Sources */, 3A4BF7CE27B5712900CA499D /* Result+Void.swift in Sources */, @@ -3517,6 +3574,7 @@ 3AE5E5682874696E00D3AF85 /* ValuePickerView.swift in Sources */, 3A0391E6290BA40E00352D73 /* BugReportingView.swift in Sources */, 546B032A2983F33600DBED8E /* OnboardingPagerView.swift in Sources */, + 54E007B62B8DF55600AFCD7C /* SecurityManager.swift in Sources */, 001F6CDC238011CA00FA7002 /* PublicKey.swift in Sources */, 5453242029A68310009281A7 /* PageToolbarView.swift in Sources */, 545A9D16294F6EEA008D24A6 /* ThemeSettingsView.swift in Sources */, @@ -3543,6 +3601,7 @@ 3A66109627AD07F10038EB5B /* SendingTariView.swift in Sources */, 3A4376E9269C0C58006107B0 /* RestoreWalletFromSeedsModel.swift in Sources */, 54FCC3732AA5FF1B00CA0025 /* CustomTorBridgesModel.swift in Sources */, + 5495C4792B7B718E00BB9051 /* SecureWrapperView.swift in Sources */, 54621F8929E00E38000E9659 /* Dictionary+Tools.swift in Sources */, 3ADF96CE2858A93000A3C888 /* ContextualButton.swift in Sources */, 546CE7F929AF523F00264699 /* ContactBookFormView.swift in Sources */, @@ -3595,7 +3654,6 @@ 545A9D14294F6ED3008D24A6 /* ThemeSettingsViewController.swift in Sources */, 3ABBC5DE2726B3A1001BB864 /* YatCacheManager.swift in Sources */, 5444C80329F14C2900BF3875 /* PopUpCircleImageHeaderView.swift in Sources */, - 3ADEBF6A26A5C10E00E87C84 /* AddBaseNodeModel.swift in Sources */, 3A420597279803DF00A8D49C /* CharacterSet+CustomSets.swift in Sources */, 3ACDA70026AFF83D00F138B8 /* OverlayPresentable.swift in Sources */, 54621F8E29E01362000E9659 /* DeepLinkKeyedDecodingContainter.swift in Sources */, @@ -3619,6 +3677,7 @@ 3ABC7E9328FE864100EAC852 /* PopUpButtonsTableView.swift in Sources */, 540CB6F129C1DDCC003FACEF /* AddContactModel.swift in Sources */, 3AC877E628741680006F327B /* UTXOsWalletLoadingView.swift in Sources */, + 5422CBFC2B88C79200428394 /* ScreenRecordingSettingsView.swift in Sources */, 37547D5624601BF600EB59CC /* UIView+GlobalFrame.swift in Sources */, BF8316FD23EF7EAA00235403 /* LAContext.swift in Sources */, 54D835582AC6C99D00D74FE8 /* AccessoryImageMenuCell.swift in Sources */, @@ -3635,6 +3694,7 @@ 5453E43E2AC9D95900C7F40D /* TorBridgesConstructor.swift in Sources */, 3AE9F4E42795A260006101D1 /* RequestTariAmountModel.swift in Sources */, 3AA2E2212885755A00D30B62 /* NetworkMonitor.swift in Sources */, + 5422CBFA2B88B5C900428394 /* ScreenRecordingSettingsModel.swift in Sources */, 3A03A600288932DE00788A02 /* SplashView.swift in Sources */, 3A052BFA27B2940900C93671 /* WalletTransactionsManager.swift in Sources */, 540CEA862A540D6500BD26C7 /* PulseLayer.swift in Sources */, @@ -3650,6 +3710,7 @@ 37E0B008249B700F00DFE315 /* UIFont+FontStyle.swift in Sources */, 5444C80629F17C1800BF3875 /* BluetoothSettingsViewController.swift in Sources */, 3A8473C228EC556C0015E63A /* CoreTariService.swift in Sources */, + 54884FBC2B596E640043E2B4 /* AddressPoisoningDataHandler.swift in Sources */, 3A2B956A28648D860085BE21 /* CChar+Helpers.swift in Sources */, 54F2E34C29EE9FFC00A7A15A /* ContactTransactionListHeaderView.swift in Sources */, 3A0EA97A26AA8E1C002612D4 /* TokenCollectionView.swift in Sources */, @@ -3680,15 +3741,16 @@ 5453242229A68CA0009281A7 /* RoundedButton.swift in Sources */, 3A2C0B8727DA22970018C5A8 /* SeedWordListElementView.swift in Sources */, 54621F8B29E00E78000E9659 /* String+Tools.swift in Sources */, + 543F89D02B98811F00B9821C /* ScreenRecordingSettingsConstructor.swift in Sources */, 3A8473D628EC56C90015E63A /* TariBalanceService.swift in Sources */, 3A6F3FF8283BF980005D1793 /* TariFeePerGramStats.swift in Sources */, 3A5A3D5829016DE300B689C6 /* BackupWalletSettingsViewController.swift in Sources */, 3A94DB21283E7CEF00B0A740 /* TransactionFeesManager.swift in Sources */, 54D1E1A52ABD92380021A365 /* DataCollectionSettingsModel.swift in Sources */, + 54AA2DF92B55596600D38184 /* PopUpAddressPoisoningContentView.swift in Sources */, 3ACC4F602876D2F100632F64 /* NSAttributedString+Format.swift in Sources */, 3A72DC4126DFA1E100E0BC43 /* NetworkSettings.swift in Sources */, 3ADA73DD270A02B900D50E3B /* WalletSettings.swift in Sources */, - 3ADEBF6626A5B5A500E87C84 /* AddBaseNodeView.swift in Sources */, 3AF985FA286B233100290387 /* PopUpSelectionView.swift in Sources */, 375DB1E0246E90D100B2BEF4 /* NavigationBar.swift in Sources */, 5455969D29B0A73300D6719E /* ExternalContactsManager.swift in Sources */, @@ -3762,6 +3824,7 @@ 3A6A033A2802DCE7000432B4 /* TariPopUp.swift in Sources */, 371A0818247290C000F97713 /* TransitionLabel.swift in Sources */, 3A4205922798001A00A8D49C /* AmountKeyboardView.swift in Sources */, + 54BFE3F62B99C74800273272 /* PublicKeys.swift in Sources */, 3A03A602288962FA00788A02 /* SplashViewModel.swift in Sources */, 3A312FEE28B6316D00A290D3 /* CompletedTransactions.swift in Sources */, 3ADF96CC28585E2400A3C888 /* ContextualButtonsOverlay.swift in Sources */, @@ -3777,11 +3840,13 @@ 54F2E34729EE6BA100A7A15A /* ContactTransactionListModel.swift in Sources */, 376C62AE247574850091BB28 /* Animation+EnumInit.swift in Sources */, 3ABBBA7C2850771C00A3108D /* UTXOsWalletTextListView.swift in Sources */, + 5422CBF82B88B1CA00428394 /* ScreenRecordingSettingsViewController.swift in Sources */, 5430B97B29B7401C00C80AA2 /* LinkContactsView.swift in Sources */, 3ACDA70526B031A400F138B8 /* SeedWordsRecoveryProgressViewController.swift in Sources */, 545A9D24294F9E77008D24A6 /* UserSettingsManager.swift in Sources */, 546CE7F429AF514A00264699 /* FormOverlay.swift in Sources */, 542585C32A332F08009D12CD /* RotaryMenuOverlay.swift in Sources */, + 549B033C2B84DA8D0059983C /* SecureViewController.swift in Sources */, 3A3D70BB291A637100A8CD7A /* BaseMenuTableView.swift in Sources */, 54FCC3712AA5FF1100CA0025 /* CustomTorBridgesView.swift in Sources */, 3A21FC5128FFD7B8004B09A0 /* PopUpTableView.swift in Sources */, @@ -3806,6 +3871,7 @@ 3A420594279802E500A8D49C /* BaseButton.swift in Sources */, 3ABBBA7E2850815C00A3108D /* UTXOsWalletTileListView.swift in Sources */, 546B03232983B49400DBED8E /* StylizedLabel.swift in Sources */, + 54D1CCC02B4D4C7200808535 /* AddressPoisoningManager.swift in Sources */, 549565E02AA1D21E0092A10F /* Task+Utils.swift in Sources */, 54DE32D02930BE3D0060108A /* ColorTheme.swift in Sources */, 54621F9929E01465000E9659 /* DeepLinkUnkeyedEncodingContainer.swift in Sources */, @@ -3829,6 +3895,8 @@ 3A70C4F4292E4C1700212026 /* VersionValidatorTests.swift in Sources */, 3A87C3B927A96423007A553F /* ErrorMessageManagerTests.swift in Sources */, 3AEDBE5927CE4F80006B0166 /* DeepLinkFormatterTests.swift in Sources */, + 545C92B32B5EDA4000FCB3ED /* StringSimilarityTests.swift in Sources */, + 54AC12922BCD1C1E0007FFEA /* StringBaseNodeTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3866,6 +3934,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -3900,6 +3969,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -3929,6 +3999,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -3963,6 +4034,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -4005,7 +4077,7 @@ "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = 0.25.2; + MARKETING_VERSION = 0.26.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4038,7 +4110,7 @@ "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = 0.25.2; + MARKETING_VERSION = 0.26.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme b/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme index 46cd561f..910011ef 100644 --- a/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme +++ b/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme @@ -1,6 +1,6 @@ Void)?) { diff --git a/MobileWallet/Common/AppValues.swift b/MobileWallet/Common/AppValues.swift index b36d8eec..6367edd3 100644 --- a/MobileWallet/Common/AppValues.swift +++ b/MobileWallet/Common/AppValues.swift @@ -39,6 +39,10 @@ */ enum AppValues { + static let general = GeneralValues.self +} + +enum GeneralValues { static var isSimulator: Bool { #if targetEnvironment(simulator) return true diff --git a/MobileWallet/Common/Deep Links/DeepLinkDefaultActionsHandler.swift b/MobileWallet/Common/Deep Links/DeepLinkDefaultActionsHandler.swift index 500ff7f2..45e957c9 100644 --- a/MobileWallet/Common/Deep Links/DeepLinkDefaultActionsHandler.swift +++ b/MobileWallet/Common/Deep Links/DeepLinkDefaultActionsHandler.swift @@ -78,7 +78,10 @@ enum DeepLinkDefaultActionsHandler { } let paymentInfo = PaymentInfo(address: transactionSendDeepLink.receiverAddress, alias: nil, yatID: nil, amount: amount, feePerGram: nil, note: transactionSendDeepLink.note) - AppRouter.presentSendTransaction(paymentInfo: paymentInfo) + + Task { @MainActor in + AppRouter.presentSendTransaction(paymentInfo: paymentInfo) + } } private static func handle(deeplink: DeepLinkable, contacts: [ContactData], actionType: ActionType) throws { diff --git a/MobileWallet/Common/Extensions/LAContext.swift b/MobileWallet/Common/Extensions/LAContext.swift index c745bc87..dada4076 100644 --- a/MobileWallet/Common/Extensions/LAContext.swift +++ b/MobileWallet/Common/Extensions/LAContext.swift @@ -79,7 +79,7 @@ extension LAContext { func authenticateUser(reason: AuthenticateUserReason = .logIn, showFailedDialog: Bool = true, onSuccess: @escaping () -> Void) { // Skip auth on simulator, quicker for development - guard !AppValues.isSimulator else { + guard !AppValues.general.isSimulator else { onSuccess() return } diff --git a/MobileWallet/Common/Extensions/String+Emoji.swift b/MobileWallet/Common/Extensions/String+Emoji.swift index 2fbd70d6..d89db9e1 100644 --- a/MobileWallet/Common/Extensions/String+Emoji.swift +++ b/MobileWallet/Common/Extensions/String+Emoji.swift @@ -54,5 +54,22 @@ extension Character { } extension String { - var containsOnlyEmoji: Bool { !isEmpty && !contains { !$0.isEmoji } } + + var containsOnlyEmoji: Bool { !isEmpty && !contains { !$0.isEmoji }} + + func isSimilar(to text: String, minSameCharacters: Int, usedPrefixSuffixCharacters: Int) -> Bool { + + guard self != text, count == text.count, count >= (usedPrefixSuffixCharacters * 2) else { return false } + + let lShortText = prefix(usedPrefixSuffixCharacters) + suffix(usedPrefixSuffixCharacters) + let rShortText = text.prefix(usedPrefixSuffixCharacters) + text.suffix(usedPrefixSuffixCharacters) + + let result = zip(lShortText, rShortText) + .reduce(into: 0) { result, elements in + guard elements.0 == elements.1 else { return } + result += 1 + } + + return result >= minSameCharacters + } } diff --git a/MobileWallet/Common/Extensions/String+Tools.swift b/MobileWallet/Common/Extensions/String+Tools.swift index e4b597d7..957f9276 100644 --- a/MobileWallet/Common/Extensions/String+Tools.swift +++ b/MobileWallet/Common/Extensions/String+Tools.swift @@ -40,6 +40,27 @@ extension String { + static func isBaseNodeAddress(hex: String, address: String?) -> Bool { + + if #available(iOS 16.0, *) { + guard hex.ranges(of: /^[a-f0-9]{64}$/).count == 1 else { return false } + guard let address else { return true } + return address.ranges(of: /^\/onion3\/[a-z0-9]{56}:[0-9]{2,6}$/).count == 1 || address.ranges(of: /^\/ip4\/[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}\/tcp\/[0-9]{2,6}$/).count == 1 + } else { + guard let hexRegex = try? NSRegularExpression(pattern: "^[a-f0-9]{64}$") else { return false } + guard hexRegex.matches(in: hex, range: NSRange(location: 0, length: hex.count)).count == 1 else { return false } + guard let address else { return true } + + guard let onionRegex = try? NSRegularExpression(pattern: "^\\/onion3\\/[a-z0-9]{56}:[0-9]{2,6}$"), + let ip4Regex = try? NSRegularExpression(pattern: "^\\/ip4\\/[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}\\/tcp\\/[0-9]{2,6}$") else { + return false + } + + let range = NSRange(location: 0, length: address.count) + return onionRegex.matches(in: address, options: [], range: range).count == 1 || ip4Regex.matches(in: address, range: range).count == 1 + } + } + func splitElementsInBrackets() -> [String] { guard #available(iOS 16.0, *) else { return components(separatedBy: CharacterSet(charactersIn: "[]")).map { String($0) }} return split(separator: /\[|\]/).map { String($0) } diff --git a/MobileWallet/Common/Formatters/AmountNumberFormatter.swift b/MobileWallet/Common/Formatters/AmountNumberFormatter.swift index aae0f4d8..3f7b1fd8 100644 --- a/MobileWallet/Common/Formatters/AmountNumberFormatter.swift +++ b/MobileWallet/Common/Formatters/AmountNumberFormatter.swift @@ -60,8 +60,6 @@ final class AmountNumberFormatter { return formatter }() - private var cancellables = Set() - init() { setupBindings() } @@ -71,23 +69,12 @@ final class AmountNumberFormatter { let inputStream = $rawAmount .filter { [unowned self] in self.isValidNumber(string: $0) } - if #available(iOS 14.0, *) { - inputStream - .compactMap { [weak self] in self?.format(string: $0) } - .assign(to: &$amount) - inputStream - .compactMap { [weak self] in self?.formatter.number(from: $0)?.doubleValue } - .assign(to: &$amountValue) - } else { - inputStream - .compactMap { [weak self] in self?.format(string: $0) } - .assign(to: \.amount, on: self) - .store(in: &cancellables) - inputStream - .compactMap { [weak self] in self?.formatter.number(from: $0)?.doubleValue } - .assign(to: \.amountValue, on: self) - .store(in: &cancellables) - } + inputStream + .compactMap { [weak self] in self?.format(string: $0) } + .assign(to: &$amount) + inputStream + .compactMap { [weak self] in self?.formatter.number(from: $0)?.doubleValue } + .assign(to: &$amountValue) } func append(string: String) { diff --git a/MobileWallet/Common/Formatters/TransactionFormatter.swift b/MobileWallet/Common/Formatters/TransactionFormatter.swift index ebb5c3a0..5af24dd6 100644 --- a/MobileWallet/Common/Formatters/TransactionFormatter.swift +++ b/MobileWallet/Common/Formatters/TransactionFormatter.swift @@ -104,6 +104,7 @@ final class TransactionFormatter { } private func contactName(transaction: Transaction) throws -> String { + guard try !transaction.isCoinbase else { return localized("transaction.coinbase.user_placeholder") } let contact = try contact(transaction: transaction) return contact?.name ?? localized("transaction.one_sided_payment.inbound_user_placeholder") } @@ -129,7 +130,8 @@ final class TransactionFormatter { private func amountViewModel(transaction: Transaction) throws -> AmountBadge.ViewModel { - let amount = try MicroTari(transaction.amount).formattedWithNegativeOperator + let tariAmount = try MicroTari(transaction.amount) + let amount = try transaction.isOutboundTransaction ? tariAmount.formattedWithNegativeOperator : tariAmount.formattedWithOperator let valueType: AmountBadge.ValueType @@ -165,7 +167,7 @@ final class TransactionFormatter { return localized("refresh_view.final_processing") } return localized("refresh_view.final_processing_with_param", arguments: confirmationCount + 1, requiredConfirmationCount + 1) - case .imported, .coinbase, .minedConfirmed, .rejected, .fauxUnconfirmed, .fauxConfirmed, .queued, .txNullError, .unknown: + case .imported, .coinbase, .minedConfirmed, .rejected, .oneSidedUnconfirmed, .oneSidedConfirmed, .queued, .coinbaseUnconfirmed, .coinbaseConfirmed, .coinbaseNotInBlockChain, .txNullError, .unknown: return nil } } diff --git a/MobileWallet/Common/Managers/AddressPoisoningManager.swift b/MobileWallet/Common/Managers/AddressPoisoningManager.swift new file mode 100644 index 00000000..17829b18 --- /dev/null +++ b/MobileWallet/Common/Managers/AddressPoisoningManager.swift @@ -0,0 +1,120 @@ +// AddressPoisoningManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 09/01/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class AddressPoisoningManager { + + struct SimilarAddressData { + let address: String + let emojiID: String + let alias: String? + let transactionsCount: Int + let lastTransaction: String? + } + + // MARK: - Constants + + private let minSameCharacters = 3 + private let usedPrefixSuffixCharacters = 3 + + // MARK: - Properties + + static let shared: AddressPoisoningManager = AddressPoisoningManager() + private let contactsManager = ContactsManager() + + // MARK: - Initialisers + + private init() {} + + // MARK: - Actions + + func similarAddresses(address: TariAddress, includeInputAddress: Bool) async throws -> [SimilarAddressData] { + + try await contactsManager.fetchModels() + + let emojiID = try address.emojis + var result: [SimilarAddressData] = [] + + if includeInputAddress { + try result.append(inputAddressData(address: address)) + } + + result += try similarContacts(toEmojiID: emojiID) + return result + } + + private func similarContacts(toEmojiID emojiID: String) throws -> [SimilarAddressData] { + try (contactsManager.tariContactModels + contactsManager.externalModels) + .filter { + guard let internalModel = $0.internalModel else { return false } + return emojiID.isSimilar(to: internalModel.emojiID, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + } + .compactMap { try data(contact: $0) } + } + + private func inputAddressData(address: TariAddress) throws -> SimilarAddressData { + let emojiID = try address.emojis + guard let existingContact = (contactsManager.tariContactModels + contactsManager.externalModels).first(where: { $0.internalModel?.emojiID == emojiID }) else { + return try data(hex: address.byteVector.hex, emojiID: emojiID) + } + return try data(contact: existingContact) ?? data(hex: address.byteVector.hex, emojiID: emojiID) + } + + private func data(hex: String, emojiID: String) -> SimilarAddressData { + SimilarAddressData(address: hex, emojiID: emojiID, alias: nil, transactionsCount: 0, lastTransaction: nil) + } + + private func data(contact: ContactsManager.Model) throws -> SimilarAddressData? { + guard let internalModel = contact.internalModel else { return nil } + let transactions = try transactions(forHex: internalModel.hex) + let lastTransaction = try formattedLastTransaction(transactions: transactions) + return SimilarAddressData(address: internalModel.hex, emojiID: internalModel.emojiID, alias: contact.name, transactionsCount: transactions.count, lastTransaction: lastTransaction) + } + + private func transactions(forHex hex: String) throws -> [Transaction] { + try Tari.shared.transactions.all + .filter { try $0.address.byteVector.hex == hex } + .sorted { try $0.timestamp > $1.timestamp } + } + + private func formattedLastTransaction(transactions: [Transaction]) throws -> String? { + guard let timestamp = try transactions.last?.timestamp else { return nil } + return Date(timeIntervalSince1970: TimeInterval(timestamp)).relativeDayFromToday() + } +} diff --git a/MobileWallet/Common/Managers/MigrationManager.swift b/MobileWallet/Common/Managers/MigrationManager.swift index 4e2c3e52..e74d2ab2 100644 --- a/MobileWallet/Common/Managers/MigrationManager.swift +++ b/MobileWallet/Common/Managers/MigrationManager.swift @@ -42,10 +42,15 @@ enum MigrationManager { // MARK: - Properties - private static let minValidVersion = "0.52.0" + private static let minValidVersion = "1.0.0-rc.5" // MARK: - Actions + static func performPeerDBMigration() async -> Bool { + guard let version = await fetchDBVersion() else { return false } + return performPeerDBMigration(dbVersion: version) + } + static func validateWalletVersion(completion: @escaping (Bool) -> Void) { Task { @@ -63,17 +68,7 @@ enum MigrationManager { private static func isWalletHasValidVersion(retryCount: Int = 0) async -> Bool { - let maxRetryCount = 5 - var version: String? - - do { - version = try Tari.shared.walletVersion() - } catch { - guard retryCount < maxRetryCount else { return false } - Logger.log(message: "Waiting for cookies: Retry Count: \(retryCount)", domain: .general, level: .info) - try? await Task.sleep(seconds: 1) - return await isWalletHasValidVersion(retryCount: retryCount + 1) - } + let version = await fetchDBVersion() if let version { let isValid = VersionValidator.compare(version, isHigherOrEqualTo: minValidVersion) @@ -85,6 +80,20 @@ enum MigrationManager { } } + private static func fetchDBVersion(retryCount: Int = 0) async -> String? { + + let maxRetryCount = 5 + + do { + return try Tari.shared.walletVersion() + } catch { + guard retryCount < maxRetryCount else { return nil } + Logger.log(message: "Waiting for cookies: Retry Count: \(retryCount)", domain: .general, level: .info) + try? await Task.sleep(seconds: 1) + return await fetchDBVersion(retryCount: retryCount + 1) + } + } + @MainActor private static func showPopUp(completion: @escaping (Bool) -> Void) { let headerSection = PopUpComponentsFactory.makeHeaderView(title: localized("ffi_validation.error.title")) @@ -97,4 +106,12 @@ enum MigrationManager { let popUp = TariPopUp(headerSection: headerSection, contentSection: contentSection, buttonsSection: buttonsSection) PopUpPresenter.show(popUp: popUp) } + + // FIXME: Temporary migration mechanism. It should be removed when minValidVersion is greater than 1.0.0-rc.8 + private static func performPeerDBMigration(dbVersion: String) -> Bool { + guard !VersionValidator.compare(dbVersion, isHigherOrEqualTo: "1.0.0-rc.8") else { return false } + let peerDBPath = Tari.shared.connectedDatabaseDirectory.appendingPathComponent("data.mdb") + try? FileManager.default.removeItem(at: peerDBPath) + return true + } } diff --git a/MobileWallet/Common/Managers/SecurityManager.swift b/MobileWallet/Common/Managers/SecurityManager.swift new file mode 100644 index 00000000..7a816c32 --- /dev/null +++ b/MobileWallet/Common/Managers/SecurityManager.swift @@ -0,0 +1,64 @@ +// SecurityManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 27/02/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +final class SecurityManager { + + // MARK: - Properties + + static let shared = SecurityManager() + @Published var areScreenshotsDisabled: Bool = GroupUserDefaults.areScreenshotsDisabled ?? true + private var cancellables = Set() + + // MARK: - Initialisers + + private init() { + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + $areScreenshotsDisabled + .sink { GroupUserDefaults.areScreenshotsDisabled = $0 } + .store(in: &cancellables) + } +} diff --git a/MobileWallet/Common/Onboarding/OnboardingPageView.swift b/MobileWallet/Common/Onboarding/OnboardingPageView.swift index bea4310e..c52e045f 100644 --- a/MobileWallet/Common/Onboarding/OnboardingPageView.swift +++ b/MobileWallet/Common/Onboarding/OnboardingPageView.swift @@ -64,7 +64,7 @@ final class OnboardingPageView: DynamicThemeView { @View private var backgroundImageView: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFit - view.image = .security.onboarding.background + view.image = .Images.Security.Onboarding.background return view }() @@ -225,7 +225,7 @@ extension OnboardingPageView.ViewModel { static var page1: Self { OnboardingPageView.ViewModel( - image: .security.onboarding.page1, + image: .Images.Security.Onboarding.page1, titleComponents: [ StylizedLabel.StylizedText(text: localized("onboarding.staged_wallet_security.page1.title.part1"), style: .normal), StylizedLabel.StylizedText(text: localized("onboarding.staged_wallet_security.page1.title.part2.bold"), style: .bold) @@ -243,7 +243,7 @@ extension OnboardingPageView.ViewModel { static var page2: Self { OnboardingPageView.ViewModel( - image: .security.onboarding.page2, + image: .Images.Security.Onboarding.page2, titleComponents: [ StylizedLabel.StylizedText(text: localized("onboarding.staged_wallet_security.page2.title.part1"), style: .normal), StylizedLabel.StylizedText(text: localized("onboarding.staged_wallet_security.page2.title.part2.bold"), style: .bold) @@ -263,7 +263,7 @@ extension OnboardingPageView.ViewModel { static var page3: Self { OnboardingPageView.ViewModel( - image: .security.onboarding.page3, + image: .Images.Security.Onboarding.page3, titleComponents: [ StylizedLabel.StylizedText(text: localized("onboarding.staged_wallet_security.page3.title.part1"), style: .normal), StylizedLabel.StylizedText(text: localized("onboarding.staged_wallet_security.page3.title.part2.bold"), style: .bold) @@ -281,7 +281,7 @@ extension OnboardingPageView.ViewModel { static var page4: Self { OnboardingPageView.ViewModel( - image: .security.onboarding.page4, + image: .Images.Security.Onboarding.page4, titleComponents: [ StylizedLabel.StylizedText(text: localized("onboarding.staged_wallet_security.page4.title.part1"), style: .normal), StylizedLabel.StylizedText(text: localized("onboarding.staged_wallet_security.page4.title.part2.bold"), style: .bold) diff --git a/MobileWallet/Common/Onboarding/OnboardingViewController.swift b/MobileWallet/Common/Onboarding/OnboardingViewController.swift index 22f7fede..06e6e298 100644 --- a/MobileWallet/Common/Onboarding/OnboardingViewController.swift +++ b/MobileWallet/Common/Onboarding/OnboardingViewController.swift @@ -41,21 +41,15 @@ import UIKit import Combine -final class OnboardingViewController: UIViewController { +final class OnboardingViewController: SecureViewController { // MARK: - Properties - private let mainView = OnboardingView() private let pageViewController = PageViewController() - private var cancellables = Set() // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupPageViewController() diff --git a/MobileWallet/Common/Persistant Data/UserDefaults.swift b/MobileWallet/Common/Persistant Data/UserDefaults.swift index 8ed3af39..038b740f 100644 --- a/MobileWallet/Common/Persistant Data/UserDefaults.swift +++ b/MobileWallet/Common/Persistant Data/UserDefaults.swift @@ -46,6 +46,8 @@ private enum UserDefaultName: String, CaseIterable { case walletSettings case userSettings case isTrackingEnabled + case areScreenshotsDisabled + case trustedAddresses } enum GroupUserDefaults { @@ -54,6 +56,8 @@ enum GroupUserDefaults { @UserDefault(key: UserDefaultName.walletSettings.rawValue, suiteName: TariSettings.groupIndentifier) static var walletSettings: [WalletSettings]? @UserDefault(key: UserDefaultName.userSettings.rawValue, suiteName: TariSettings.groupIndentifier) static var userSettings: UserSettings? @UserDefault(key: UserDefaultName.isTrackingEnabled.rawValue, suiteName: TariSettings.groupIndentifier) static var isTrackingEnabled: Bool? + @UserDefault(key: UserDefaultName.areScreenshotsDisabled.rawValue, suiteName: TariSettings.groupIndentifier) static var areScreenshotsDisabled: Bool? + @UserDefault(key: UserDefaultName.trustedAddresses.rawValue, suiteName: TariSettings.groupIndentifier) static var trustedAddresses: Set? } // MARK: - Tor Manager User Defaults diff --git a/MobileWallet/Common/Pop-up/Components/PopUpAddressPoisoningContentView.swift b/MobileWallet/Common/Pop-up/Components/PopUpAddressPoisoningContentView.swift new file mode 100644 index 00000000..7ffb49ca --- /dev/null +++ b/MobileWallet/Common/Pop-up/Components/PopUpAddressPoisoningContentView.swift @@ -0,0 +1,337 @@ +// PopUpAddressPoisoningContentView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 15/01/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import TariCommon + +final class PopUpAddressPoisoningContentView: DynamicThemeView { + + // MARK: - Subiews + + @View private var tableView: PopUpTableView = { + let view = PopUpTableView() + view.register(type: PopUpAddressPoisoningContentCell.self) + view.layer.cornerRadius = 10.0 + view.separatorStyle = .none + return view + }() + + @View private var tickView: TickButton = { + let view = TickButton() + view.isUserInteractionEnabled = true + return view + }() + + @View private var tickMessageLabel: UILabel = { + let view = UILabel() + view.text = localized("address_poisoning.pop_up.label.tick_message") + view.font = .Avenir.medium.withSize(14.0) + return view + }() + + @View private var tickMessageButton = BaseButton() + + @View private var shieldIconView: UIImageView = { + let view = UIImageView() + view.image = .Icons.General.shieldCheckmark + return view + }() + + @View private var infoLabel: UILabel = { + let view = UILabel() + view.text = localized("address_poisoning.pop_up.label.trusted_info") + view.font = .Avenir.medium.withSize(11.0) + view.numberOfLines = 0 + view.setContentCompressionResistancePriority(.required, for: .vertical) + return view + }() + + // MARK: - Properties + + var viewModels: [PopUpAddressPoisoningContentCell.ViewModel] = [] { + didSet { update(viewModels: viewModels) } + } + + var selectedIndex: Int? { tableView.indexPathForSelectedRow?.row } + var isTrustedTickSelected: Bool { tickView.isSelected } + + private var dataSource: UITableViewDiffableDataSource? + + // MARK: - Initialisers + + override init() { + super.init() + setupConstratins() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstratins() { + + [tableView, tickView, tickMessageLabel, tickMessageButton, shieldIconView, infoLabel].forEach(addSubview) + + let constraints = [ + tableView.topAnchor.constraint(equalTo: topAnchor), + tableView.leadingAnchor.constraint(equalTo: leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: trailingAnchor), + tickView.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant: 20.0), + tickView.leadingAnchor.constraint(equalTo: leadingAnchor), + tickView.widthAnchor.constraint(equalToConstant: 24.0), + tickView.heightAnchor.constraint(equalToConstant: 24.0), + tickMessageLabel.topAnchor.constraint(equalTo: tickView.topAnchor), + tickMessageLabel.leadingAnchor.constraint(equalTo: tickView.trailingAnchor, constant: 10.0), + tickMessageLabel.trailingAnchor.constraint(equalTo: trailingAnchor), + tickMessageLabel.bottomAnchor.constraint(equalTo: tickView.bottomAnchor), + tickMessageButton.topAnchor.constraint(equalTo: tickMessageLabel.topAnchor), + tickMessageButton.leadingAnchor.constraint(equalTo: tickMessageLabel.leadingAnchor), + tickMessageButton.trailingAnchor.constraint(equalTo: tickMessageLabel.trailingAnchor), + tickMessageButton.bottomAnchor.constraint(equalTo: tickMessageLabel.bottomAnchor), + shieldIconView.topAnchor.constraint(equalTo: tickView.bottomAnchor, constant: 10.0), + shieldIconView.leadingAnchor.constraint(equalTo: leadingAnchor), + shieldIconView.widthAnchor.constraint(equalToConstant: 20.0), + shieldIconView.heightAnchor.constraint(equalToConstant: 20.0), + infoLabel.topAnchor.constraint(equalTo: shieldIconView.topAnchor), + infoLabel.leadingAnchor.constraint(equalTo: shieldIconView.trailingAnchor, constant: 10.0), + infoLabel.trailingAnchor.constraint(equalTo: trailingAnchor), + infoLabel.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + + dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, model in + let cell = tableView.dequeueReusableCell(type: PopUpAddressPoisoningContentCell.self, indexPath: indexPath) + cell.update(viewModel: model) + return cell + } + + tableView.dataSource = dataSource + tableView.delegate = self + + tickView.onTap = { [weak self] in + self?.tickView.isSelected.toggle() + } + + tickMessageButton.onTap = { [weak self] in + self?.tickView.isSelected.toggle() + } + } + + // MARK: - Updates + + private func update(viewModels: [PopUpAddressPoisoningContentCell.ViewModel]) { + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([0]) + snapshot.appendItems(viewModels) + + dataSource?.apply(snapshot, animatingDifferences: false) + tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: .top) + } + + private func updateTrustedContactElements(isTrusted: Bool) { + tickView.isEnabled = !isTrusted + tickView.isSelected = isTrusted + tickMessageButton.isEnabled = !isTrusted + } + + override func update(theme: ColorTheme) { + super.update(theme: theme) + tableView.backgroundColor = theme.backgrounds.secondary + tickMessageLabel.textColor = theme.text.links + shieldIconView.tintColor = theme.text.lightText + infoLabel.textColor = theme.text.body + } +} + +extension PopUpAddressPoisoningContentView: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let isTrusted = viewModels[indexPath.row].isTrusted + updateTrustedContactElements(isTrusted: isTrusted) + } +} + +final class PopUpAddressPoisoningContentCell: DynamicThemeCell { + + struct ViewModel: Identifiable, Hashable { + var id: UUID + let emojiID: String + let name: String? + let transactionsCount: Int + let lastTransaction: String? + let isTrusted: Bool + } + + // MARK: - Subviews + + @View private var floatingContentView: UIView = { + let view = UIView() + view.layer.cornerRadius = 4.0 + view.layer.borderWidth = 2.0 + return view + }() + + @View private var emojiView: ScrollableLabel = { + let view = ScrollableLabel() + view.label.font = .Avenir.medium.withSize(17.0) + view.margin = 10.0 + return view + }() + + @View private var labelsStackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + return view + }() + + @View private var bottomStackView = UIStackView() + + @View private var nameLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.heavy.withSize(14.0) + return view + }() + + @View private var transactionCountLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.medium.withSize(14.0) + return view + }() + + @View private var lastTransactionLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.medium.withSize(14.0) + return view + }() + + @View private var shieldIconView: UIImageView = { + let view = UIImageView() + view.image = .Icons.General.shieldCheckmark + view.isHidden = true + return view + }() + + // MARK: - Initialisers + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupView() + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupView() { + backgroundColor = .clear + selectionStyle = .none + } + + private func setupConstraints() { + + [floatingContentView, emojiView, labelsStackView].forEach(contentView.addSubview) + [nameLabel, transactionCountLabel, bottomStackView].forEach(labelsStackView.addArrangedSubview) + [lastTransactionLabel, shieldIconView].forEach(bottomStackView.addArrangedSubview) + + let constraints = [ + floatingContentView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10.0), + floatingContentView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10.0), + floatingContentView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10.0), + floatingContentView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10.0), + emojiView.topAnchor.constraint(equalTo: floatingContentView.topAnchor, constant: 10.0), + emojiView.leadingAnchor.constraint(equalTo: floatingContentView.leadingAnchor), + emojiView.trailingAnchor.constraint(equalTo: floatingContentView.trailingAnchor), + labelsStackView.topAnchor.constraint(equalTo: emojiView.bottomAnchor, constant: 10.0), + labelsStackView.leadingAnchor.constraint(equalTo: floatingContentView.leadingAnchor, constant: 10.0), + labelsStackView.trailingAnchor.constraint(equalTo: floatingContentView.trailingAnchor, constant: -10.0), + labelsStackView.bottomAnchor.constraint(equalTo: floatingContentView.bottomAnchor, constant: -10.0), + shieldIconView.widthAnchor.constraint(equalToConstant: 20.0), + shieldIconView.heightAnchor.constraint(equalToConstant: 20.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + floatingContentView.backgroundColor = theme.backgrounds.primary + floatingContentView.layer.borderColor = theme.icons.active?.cgColor + floatingContentView.apply(shadow: theme.shadows.box) + nameLabel.textColor = theme.text.body + transactionCountLabel.textColor = theme.text.body + lastTransactionLabel.textColor = theme.text.body + shieldIconView.tintColor = theme.text.links + } + + func update(viewModel: ViewModel) { + emojiView.label.text = viewModel.emojiID + nameLabel.text = viewModel.name + nameLabel.isHidden = viewModel.name == nil + transactionCountLabel.text = localized("address_poisoning.label.transaction_count", arguments: viewModel.transactionsCount) + lastTransactionLabel.text = localized("address_poisoning.label.last_transction", arguments: viewModel.lastTransaction ?? localized("address_poisoning.label.last_transction.never")) + shieldIconView.isHidden = !viewModel.isTrusted + } + + private func update(isElevated: Bool) { + floatingContentView.alpha = isElevated ? 1.0 : 0.0 + } + + // MARK: - Actions + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + UIView.animate(withDuration: 0.2) { + self.update(isElevated: selected) + } + } +} diff --git a/MobileWallet/Common/Pop-up/Components/PopUpButtonsView.swift b/MobileWallet/Common/Pop-up/Components/PopUpButtonsView.swift index 680694d6..0eed59e7 100644 --- a/MobileWallet/Common/Pop-up/Components/PopUpButtonsView.swift +++ b/MobileWallet/Common/Pop-up/Components/PopUpButtonsView.swift @@ -38,7 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import TariCommon final class PopUpButtonsView: UIView { diff --git a/MobileWallet/Common/Pop-up/Components/PopUpSelectionView.swift b/MobileWallet/Common/Pop-up/Components/PopUpSelectionView.swift index 972c9580..5c55d281 100644 --- a/MobileWallet/Common/Pop-up/Components/PopUpSelectionView.swift +++ b/MobileWallet/Common/Pop-up/Components/PopUpSelectionView.swift @@ -136,7 +136,7 @@ private class PopUpSelectionCell: DynamicThemeCell { @View private var tickIcon: UIImageView = { let view = UIImageView() - view.image = Theme.shared.images.utxoTick + view.image = .Icons.General.tick view.contentMode = .scaleAspectFit view.alpha = 0.0 return view diff --git a/MobileWallet/Common/Pop-up/Components/PopUpTableView.swift b/MobileWallet/Common/Pop-up/Components/PopUpTableView.swift index 1a7da031..970a63fd 100644 --- a/MobileWallet/Common/Pop-up/Components/PopUpTableView.swift +++ b/MobileWallet/Common/Pop-up/Components/PopUpTableView.swift @@ -72,6 +72,7 @@ final class PopUpTableView: DynamicThemeTableView { private func setupConstraints() { heightConstraint = heightAnchor.constraint(equalToConstant: 0.0) + heightConstraint?.priority = .defaultHigh heightConstraint?.isActive = true } diff --git a/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift b/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift index ec881d76..163a028d 100644 --- a/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift +++ b/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift @@ -208,6 +208,29 @@ extension PopUpPresenter { show(popUp: popUp, configuration: configuration) } + @MainActor static func showAddressPoisoningPopUp(options: [PopUpAddressPoisoningContentCell.ViewModel], onContinue: ((_ selectedOption: PopUpAddressPoisoningContentCell.ViewModel, _ isTrusted: Bool) -> Void)?) { + + let headerSection = PopUpHeaderWithSubtitle() + let contentSection = PopUpAddressPoisoningContentView() + let buttonsSection = PopUpButtonsView() + + headerSection.titleLabel.text = localized("address_poisoning.pop_up.title") + headerSection.subtitleLabel.text = localized("address_poisoning.pop_up.message", arguments: options.count) + + contentSection.viewModels = options + + buttonsSection.addButton(model: PopUpDialogButtonModel(title: localized("common.continue"), type: .normal, callback: { + PopUpPresenter.dismissPopup { + guard let index = contentSection.selectedIndex else { return } + onContinue?(options[index], contentSection.isTrustedTickSelected) + } + })) + buttonsSection.addButton(model: PopUpDialogButtonModel(title: localized("common.cancel"), type: .text, callback: { PopUpPresenter.dismissPopup() })) + + let popup = TariPopUp(headerSection: headerSection, contentSection: contentSection, buttonsSection: buttonsSection) + PopUpPresenter.show(popUp: popup) + } + private static func log(message: MessageModel) { var log = "Pop-up Title: \(message.title)" @@ -269,7 +292,7 @@ private extension PopUpPresenter.BLEDialogType { switch self { case let .scanForContactListReceiver(onCancel): return BLEDialogModel( - image: .contactBook.bleDialog.icon, + image: .Images.ContactBook.BLEDialog.icon, imageTintColor: .purple, title: localized("contact_book.popup.ble.share.title"), messageComponents: [StylizedLabel.StylizedText(text: localized("contact_book.popup.ble.share.message"), style: .normal)], @@ -279,7 +302,7 @@ private extension PopUpPresenter.BLEDialogType { ) case .successContactSharing: return BLEDialogModel( - image: .contactBook.bleDialog.success, + image: .Images.ContactBook.BLEDialog.success, imageTintColor: .purple, title: localized("contact_book.popup.ble.success.title"), messageComponents: [StylizedLabel.StylizedText(text: localized("contact_book.popup.ble.success.message"), style: .normal)], @@ -289,7 +312,7 @@ private extension PopUpPresenter.BLEDialogType { ) case let .scanForTransactionData(onCancel): return BLEDialogModel( - image: .contactBook.bleDialog.icon, + image: .Images.ContactBook.BLEDialog.icon, imageTintColor: .purple, title: localized("contact_book.popup.ble.transaction.scan.title"), messageComponents: [StylizedLabel.StylizedText(text: localized("contact_book.popup.ble.transaction.scan.message"), style: .normal)], @@ -299,7 +322,7 @@ private extension PopUpPresenter.BLEDialogType { ) case let .confirmTransactionData(receiverName, confirmationCallback, rejectCallback): return BLEDialogModel( - image: .contactBook.bleDialog.icon, + image: .Images.ContactBook.BLEDialog.icon, imageTintColor: .purple, title: localized("contact_book.popup.ble.transaction.confimation.title"), messageComponents: [ @@ -323,7 +346,7 @@ private extension PopUpPresenter.BLEDialogType { } return BLEDialogModel( - image: .contactBook.bleDialog.failure, + image: .Images.ContactBook.BLEDialog.failure, imageTintColor: .red, title: localized("contact_book.popup.ble.failure.title"), messageComponents: components, diff --git a/MobileWallet/Common/Pop-up/PopUpPresenter.swift b/MobileWallet/Common/Pop-up/PopUpPresenter.swift index 27896c50..d6110362 100644 --- a/MobileWallet/Common/Pop-up/PopUpPresenter.swift +++ b/MobileWallet/Common/Pop-up/PopUpPresenter.swift @@ -58,18 +58,14 @@ enum PopUpPresenter { // MARK: - Properties private static let sideOffset = 14.0 - private static let verticalOffset = hasNotch ? -14.0 : 14.0 private static let defaultAttributes: EKAttributes = { var attributes = EKAttributes.bottomFloat attributes.entryBackground = .clear - - if let overlayColor = UIColor.static.popupOverlay { - attributes.screenBackground = .color(color: EKColor(overlayColor)) - } - - attributes.positionConstraints.size = EKAttributes.PositionConstraints.Size(width: .offset(value: sideOffset), height: .intrinsic) - attributes.positionConstraints.verticalOffset = verticalOffset + attributes.screenBackground = .color(color: EKColor(.Static.popupOverlay)) + attributes.positionConstraints.size.width = .offset(value: sideOffset) + attributes.positionConstraints.maxSize.height = .ratio(value: hasNotch ? 0.85 : 0.9) + attributes.positionConstraints.safeArea = .empty(fillSafeArea: false) attributes.screenInteraction = .absorbTouches attributes.entryInteraction = .forward attributes.displayDuration = .infinity diff --git a/MobileWallet/Common/Pop-up/TariPopUp.swift b/MobileWallet/Common/Pop-up/TariPopUp.swift index dac9d4f8..26700216 100644 --- a/MobileWallet/Common/Pop-up/TariPopUp.swift +++ b/MobileWallet/Common/Pop-up/TariPopUp.swift @@ -38,13 +38,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import TariCommon final class TariPopUp: DynamicThemeView { // MARK: - Subviews + @View private var secureContentView = SecureWrapperView() + @View private var backgroundView: UIView = { let view = UIView() view.layer.cornerRadius = 26.0 @@ -74,12 +75,17 @@ final class TariPopUp: DynamicThemeView { private func setupConstraints(headerSection: UIView?, contentSection: UIView?, buttonsSection: UIView?) { - addSubview(backgroundView) + addSubview(secureContentView) + secureContentView.view.addSubview(backgroundView) - let backgroundViewTopConstraint = backgroundView.topAnchor.constraint(equalTo: topAnchor) + let backgroundViewTopConstraint = backgroundView.topAnchor.constraint(equalTo: secureContentView.view.topAnchor) self.backgroundViewTopConstraint = backgroundViewTopConstraint var constraints = [ + secureContentView.topAnchor.constraint(equalTo: topAnchor), + secureContentView.leadingAnchor.constraint(equalTo: leadingAnchor), + secureContentView.trailingAnchor.constraint(equalTo: trailingAnchor), + secureContentView.bottomAnchor.constraint(equalTo: bottomAnchor), backgroundViewTopConstraint, backgroundView.leadingAnchor.constraint(equalTo: leadingAnchor), backgroundView.trailingAnchor.constraint(equalTo: trailingAnchor), @@ -92,21 +98,21 @@ final class TariPopUp: DynamicThemeView { constraints += makeConstraints(forView: headerSection, viewOnTop: viewOnTop) viewOnTop = headerSection headerSection.translatesAutoresizingMaskIntoConstraints = false - addSubview(headerSection) + secureContentView.view.addSubview(headerSection) } if let contentSection = contentSection { constraints += makeConstraints(forView: contentSection, viewOnTop: viewOnTop) viewOnTop = contentSection contentSection.translatesAutoresizingMaskIntoConstraints = false - addSubview(contentSection) + secureContentView.view.addSubview(contentSection) } if let buttonsSection = buttonsSection { constraints += makeConstraints(forView: buttonsSection, viewOnTop: viewOnTop) viewOnTop = buttonsSection buttonsSection.translatesAutoresizingMaskIntoConstraints = false - addSubview(buttonsSection) + secureContentView.view.addSubview(buttonsSection) } guard let viewOnTop = viewOnTop else { return } diff --git a/MobileWallet/Common/Protocols/AddressPoisoningDataHandler.swift b/MobileWallet/Common/Protocols/AddressPoisoningDataHandler.swift new file mode 100644 index 00000000..413e226f --- /dev/null +++ b/MobileWallet/Common/Protocols/AddressPoisoningDataHandler.swift @@ -0,0 +1,131 @@ +// AddressPoisoningDataHandler.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 18/01/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum AddressPoisoningDataHandler { + + // MARK: - Actions + + static func handleAddressSelection(paymentInfo: PaymentInfo, onContinue: ((PaymentInfo) -> Void)?) { + + let rawAddress = paymentInfo.address + + guard !isAddressTrusted(address: rawAddress) else { + onContinue?(paymentInfo) + return + } + + Task { @MainActor in + do { + let address = try TariAddress(hex: rawAddress) + let similarAddresses = try await AddressPoisoningManager.shared.similarAddresses(address: address, includeInputAddress: true) + .map { similarAddress in + PopUpAddressPoisoningContentCell.ViewModel( + id: UUID(), + emojiID: similarAddress.emojiID, + name: similarAddress.alias, + transactionsCount: similarAddress.transactionsCount, + lastTransaction: similarAddress.lastTransaction, + isTrusted: isAddressTrusted(address: similarAddress.address) + ) + } + + if similarAddresses.count > 1 { + showAddressPoisoningDialog(options: similarAddresses, originalPaymentInfo: paymentInfo, onContinue: onContinue) + } else { + onContinue?(paymentInfo) + } + } catch { + showErrorPopUp(error: error) + } + } + } + + private static func confirmAddressSelection(emojiID: String, originalPaymentInfo: PaymentInfo, isTrusted: Bool, onContinue: ((PaymentInfo) -> Void)?) { + do { + let address = try TariAddress(emojiID: emojiID).byteVector.hex + updateSettings(hex: address, isTrusted: isTrusted) + + if address == originalPaymentInfo.address { + onContinue?(originalPaymentInfo) + } else { + onContinue?(PaymentInfo(address: address, alias: nil, yatID: nil, amount: nil, feePerGram: nil, note: nil)) + } + } catch { + showErrorPopUp(error: error) + } + } + + // MARK: - PopUps + + private static func showAddressPoisoningDialog(options: [PopUpAddressPoisoningContentCell.ViewModel], originalPaymentInfo: PaymentInfo, onContinue: ((PaymentInfo) -> Void)?) { + Task { @MainActor in + PopUpPresenter.showAddressPoisoningPopUp(options: options) { + confirmAddressSelection(emojiID: $0.emojiID, originalPaymentInfo: originalPaymentInfo, isTrusted: $1, onContinue: onContinue) + } + } + } + + private static func showErrorPopUp(error: Error) { + let message = ErrorMessageManager.errorModel(forError: error) + Task { @MainActor in + PopUpPresenter.show(message: message) + } + } + + // MARK: - Helpers + + private static func updateSettings(hex: String, isTrusted: Bool) { + + if GroupUserDefaults.trustedAddresses == nil { + GroupUserDefaults.trustedAddresses = Set() + } + + guard isTrusted else { + GroupUserDefaults.trustedAddresses?.remove(hex) + return + } + + GroupUserDefaults.trustedAddresses?.insert(hex) + } + + private static func isAddressTrusted(address: String) -> Bool { + GroupUserDefaults.trustedAddresses?.contains { $0 == address } ?? false + } +} diff --git a/MobileWallet/Common/Theme.swift b/MobileWallet/Common/Theme.swift index 70d85308..b38452f6 100644 --- a/MobileWallet/Common/Theme.swift +++ b/MobileWallet/Common/Theme.swift @@ -72,70 +72,12 @@ struct Images { let successIcon = UIImage(named: "SuccessIcon") let tariIcon = UIImage(named: "TariIcon") let cancelGiphy = UIImage(named: "cancelGiphy") - let cancelGrey = UIImage(named: "cancelGrey") let poweredByGiphy = UIImage(named: "poweredByGiphy") let searchIcon = UIImage(named: "SearchIcon") - let yatLogo = UIImage(named: "YatLogo") - let yatButtonOff = UIImage(named: "YatButtonOff") - let yatButtonOn = UIImage(named: "YatButtonOn") // Amount let delete = UIImage(named: "numpad-delete") - let helpButton = UIImage(named: "QuestionMark") - - // Seed words list - let expandButtonArrow = UIImage(named: "ExpandButtonArrow") - - // UTXOs Wallet Icons - let utxoFaucet = UIImage(named: "icon-faucet") - let utxoTileViewIcon = UIImage(named: "icon-blockview") - let utxoTextListIcon = UIImage(named: "icon-listview") - let utxoStatusHourglass = UIImage(named: "icon-hourglass") - let utxoActionJoin = UIImage(named: "icon-join") - let utxoActionSplit = UIImage(named: "icon-split") - let utxoActionJoinSplit = UIImage(named: "icon-join-split") - - // Settings Icons - let settingsAboutIcon = UIImage(named: "icon-about") - let settingsBaseNodeIcon = UIImage(named: "icon-base-node") - let settingsBlockExplorerIcon = UIImage(named: "icon-block-explorer") - let settingsBridgeConfigIcon = UIImage(named: "icon-bridge-config") - let settingsContributeIcon = UIImage(named: "icon-contribute") - let settingsDeleteIcon = UIImage(named: "icon-delete") - let settingsDisclaimerIcon = UIImage(named: "icon-disclaimer") - let settingsNetworkIcon = UIImage(named: "icon-network") - let settingsPrivacyPolicyIcon = UIImage(named: "icon-privacy-policy") - let settingsReportBugIcon = UIImage(named: "icon-report-bug") - let settingsUserAgreementIcon = UIImage(named: "icon-user-agreement") - let settingsVisitTariIcon = UIImage(named: "icon-visit-tari") - let settingsWalletBackupsIcon = UIImage(named: "icon-wallet-backups") - let settingColorThemeIcon = UIImage(named: "icon-theme") - - // Connection Details Icons - let connectionInternetIcon = UIImage(named: "icon-internet") - let connectionTorIcon = UIImage(named: "icon-tor") - let connectionSyncIcon = UIImage(named: "icon-sync") - - // UTXOs Wallet - - let utxoTick = UIImage(named: "tick") - let utxoWalletPlaceholder = UIImage(named: "UtxoWalletPlaceholder") - let utxoWalletPickerMinus = UIImage(named: "ValuePickerMinus") - let utxoWalletPickerPlus = UIImage(named: "ValuePickerPlus") - let utxoSuccessImage = UIImage(named: "UtxoSuccess") - - // Adjustable fees - - let speedometerLow = UIImage(named: "speedometer-low") - let speedometerMid = UIImage(named: "speedometer-mid") - let speedometerHigh = UIImage(named: "speedometer-high") - - // Color Themes - - let colorThemeSystem = UIImage(named: "Themes/System") - let colorThemeLight = UIImage(named: "Themes/Light") - let colorThemeDark = UIImage(named: "Themes/Dark") - let colorThemePurple = UIImage(named: "Themes/Purple") +// let helpButton = UIImage(named: "QuestionMark") } struct Fonts { @@ -209,9 +151,7 @@ struct Fonts { let refreshViewLabel = UIFont.Avenir.heavy.withSize(12.0) // App table view - let systemTableViewCell = UIFont.Avenir.medium.withSize(15.0) let systemTableViewCellMarkDescription = UIFont.Avenir.medium.withSize(14.0) - let systemTableViewCellMarkDescriptionSmall = UIFont.Avenir.medium.withSize(11.0) let tableViewSection = UIFont.Avenir.medium.withSize(14.0) // Restore pending view @@ -247,134 +187,10 @@ struct Fonts { struct Sizes { let appSidePadding: CGFloat = 22 - // TODO move other constants here } -// MARK: - Color Pallete - -extension UIColor { - static var `static`: StaticColors.Type { StaticColors.self } -} - -enum StaticColors { - static var white: UIColor? { UIColor(named: "White") } - static var black: UIColor? { UIColor(named: "Black") } - static var purple: UIColor? { UIColor(named: "Purple") } - static var red: UIColor? { UIColor(named: "Red") } - static var mediumGrey: UIColor? { UIColor(named: "MediumGrey") } - static var popupOverlay: UIColor? { .black.withAlphaComponent(0.7) } -} +// MARK: - Shadow extension Shadow { static var none: Self { Self(color: nil, opacity: 0.0, radius: 0.0, offset: .zero) } } - -// MARK: - Images - -extension UIImage { - static var security: SecurityImages.Type { SecurityImages.self } - static var contactBook: ContactBookImages.Type { ContactBookImages.self } - static var icons: IconsImages.Type { IconsImages.self } - static var tabBar: TabBarImages.Type { TabBarImages.self } -} - -// MARK: - Security - -enum SecurityImages { - static var onboarding: SecurityOnboardingImages.Type { SecurityOnboardingImages.self } -} - -enum SecurityOnboardingImages { - static var background: UIImage? { UIImage(named: "Images/Security/Onboarding/Background") } - static var page1: UIImage? { UIImage(named: "Images/Security/Onboarding/Page1") } - static var page2: UIImage? { UIImage(named: "Images/Security/Onboarding/Page2") } - static var page3: UIImage? { UIImage(named: "Images/Security/Onboarding/Page3") } - static var page4: UIImage? { UIImage(named: "Images/Security/Onboarding/Page4") } -} - -// MARK: - Contact Book - -enum ContactBookImages { - static var bleDialog: ContactBookBLEDialogImages.Type { ContactBookBLEDialogImages.self } - static var buttons: ContactBookButtonImages.Type { ContactBookButtonImages.self } - static var placeholders: ContactBookPlaceholderImages.Type { ContactBookPlaceholderImages.self } -} - -enum ContactBookBLEDialogImages { - static var icon: UIImage? { UIImage(named: "Images/Contact Book/BLE Dialog/Icon") } - static var success: UIImage? { UIImage(named: "Images/Contact Book/BLE Dialog/Success") } - static var failure: UIImage? { UIImage(named: "Images/Contact Book/BLE Dialog/Failure") } -} - -enum ContactBookButtonImages { - static var addContact: UIImage? { UIImage(named: "Images/Contact Book/Buttons/AddContact") } - static var share: UIImage? { UIImage(named: "Images/Contact Book/Buttons/Share") } -} - -enum ContactBookPlaceholderImages { - static var contactsList: UIImage? { UIImage(named: "Images/Contact Book/Placeholders/ContactBookListPlaceholder") } - static var favoritesContactsList: UIImage? { UIImage(named: "Images/Contact Book/Placeholders/ContactBookListFavPlaceholder") } - static var transactionList: UIImage? { UIImage(named: "Images/Contact Book/Placeholders/TransactionList") } - static var linkList: UIImage? { UIImage(named: "Images/Contact Book/Placeholders/LinkList") } -} - -// MARK: - Tab Bar - -enum TabBarImages { - static var send: UIImage? { UIImage(named: "Images/TabBar/Send") } -} - -// MARK: - Icons - -enum IconsImages { - - static var contactTypes: IconsContactTypesImages.Type { IconsContactTypesImages.self } - static var network: IconsNetworkImages.Type { IconsNetworkImages.self } - static var rotaryMenu: IconsRotaryImages.Type { IconsRotaryImages.self } - static var settings: IconsSettingsImages.Type { IconsSettingsImages.self } - static var star: IconsStarImages.Type { IconsStarImages.self } - static var tabBar: IconsTabBarImages.Type { IconsTabBarImages.self } - - static var analytics: UIImage? { UIImage(named: "Icons/Analytics") } - static var bluetooth: UIImage? { UIImage(named: "Icons/Bluetooth") } - static var checkmark: UIImage? { UIImage(named: "Icons/Checkmark") } - static var close: UIImage? { UIImage(named: "Icons/Close") } - static var link: UIImage? { UIImage(named: "Icons/Link") } - static var magnifyingGlass: UIImage? { UIImage(named: "Icons/Magnifying Glass") } - static var profile: UIImage? { UIImage(named: "Icons/Profile") } - static var send: UIImage? { UIImage(named: "Icons/Send") } - static var tariGem: UIImage? { UIImage(named: "Icons/Tari Gem") } - static var qr: UIImage? { UIImage(named: "Icons/QR") } - static var unlink: UIImage? { UIImage(named: "Icons/Unlink") } - static var wallet: UIImage? { UIImage(named: "Icons/Wallet") } -} - -enum IconsContactTypesImages { - static var `internal`: UIImage? { UIImage(named: "Icons/Contact Types/Internal") } - static var external: UIImage? { UIImage(named: "Icons/Contact Types/External") } - static var linked: UIImage? { UIImage(named: "Icons/Contact Types/Linked") } -} - -enum IconsNetworkImages { - static var full: UIImage? { UIImage(named: "Icons/Network/Full") } - static var limited: UIImage? { UIImage(named: "Icons/Network/Limited") } - static var off: UIImage? { UIImage(named: "Icons/Network/Off") } -} - -enum IconsRotaryImages { - static var close: UIImage? { UIImage(named: "Icons/Rotary Menu/Close") } - static var switchSide: UIImage? { UIImage(named: "Icons/Rotary Menu/Switch Side") } -} - -enum IconsSettingsImages { - static var bluetooth: UIImage? { UIImage(named: "Icons/Settings/Bluetooth") } -} - -enum IconsStarImages { - static var border: UIImage? { UIImage(named: "Icons/Star/Border") } - static var filled: UIImage? { UIImage(named: "Icons/Star/Filled") } -} - -enum IconsTabBarImages { - static var contactBook: UIImage? { UIImage(named: "Icons/TabBar/ContactBook") } -} diff --git a/MobileWallet/Common/Theme/ColorTheme.swift b/MobileWallet/Common/Theme/ColorTheme.swift index 61fd45ea..b984ee0a 100644 --- a/MobileWallet/Common/Theme/ColorTheme.swift +++ b/MobileWallet/Common/Theme/ColorTheme.swift @@ -121,56 +121,56 @@ extension ColorTheme { static var light: Self { Self( brand: Brand( - purple: UIColor(named: "Light/Brand/Purple"), - pink: UIColor(named: "Light/Brand/Pink"), - darkBlue: UIColor(named: "Light/Brand/Dark Blue") + purple: .Light.Brand.purple, + pink: .Light.Brand.pink, + darkBlue: .Light.Brand.darkBlue ), neutral: Neutral( - primary: UIColor(named: "Light/Neutral/Primary"), - secondary: UIColor(named: "Light/Neutral/Secondary"), - tertiary: UIColor(named: "Light/Neutral/Tertiary"), - inactive: UIColor(named: "Light/Neutral/Inactive") + primary: .Light.Neutral.primary, + secondary: .Light.Neutral.secondary, + tertiary: .Light.Neutral.tertiary, + inactive: .Light.Neutral.inactive ), buttons: Buttons( - primaryStart: UIColor(named: "Light/Buttons/PrimaryStart"), - primaryEnd: UIColor(named: "Light/Buttons/PrimaryEnd"), - disabled: UIColor(named: "Light/Buttons/Disabled"), - primaryText: UIColor(named: "Light/Buttons/Primary Text"), - disabledText: UIColor(named: "Light/Buttons/Disabled Text") + primaryStart: .Light.Buttons.primaryStart, + primaryEnd: .Light.Buttons.primaryEnd, + disabled: .Light.Buttons.disabled, + primaryText: .Light.Buttons.primaryText, + disabledText: .Light.Buttons.disabledText ), text: Text( - heading: UIColor(named: "Light/Text/Heading"), - body: UIColor(named: "Light/Text/Body"), - lightText: UIColor(named: "Light/Text/Light Text"), - links: UIColor(named: "Light/Text/Links") + heading: .Light.Text.heading, + body: .Light.Text.body, + lightText: .Light.Text.lightText, + links: .Light.Text.links ), icons: Icons( - default: UIColor(named: "Light/Icons/Default"), - active: UIColor(named: "Light/Icons/Active"), - inactive: UIColor(named: "Light/Icons/Inactive") + default: .Light.Icons.default, + active: .Light.Icons.active, + inactive: .Light.Icons.inactive ), components: Components( - qrBackground: UIColor(named: "Light/Components/QR Background"), - overlay: UIColor(named: "Light/Components/Overlay") + qrBackground: .Light.Components.qrBackground, + overlay: .Light.Components.overlay ), backgrounds: Backgrounds( - primary: UIColor(named: "Light/Backgrounds/Primary"), - secondary: UIColor(named: "Light/Backgrounds/Secondary") + primary: .Light.Backgrounds.primary, + secondary: .Light.Backgrounds.secondary ), system: System( - red: UIColor(named: "Light/System/Red"), - orange: UIColor(named: "Light/System/Orange"), - yellow: UIColor(named: "Light/System/Yellow"), - green: UIColor(named: "Light/System/Green"), - blue: UIColor(named: "Light/System/Blue"), - lightRed: UIColor(named: "Light/System/Light Red"), - lightOrange: UIColor(named: "Light/System/Light Orange"), - lightYellow: UIColor(named: "Light/System/Light Yellow"), - lightGreen: UIColor(named: "Light/System/Light Green"), - lightBlue: UIColor(named: "Light/System/Light Blue") + red: .Light.System.red, + orange: .Light.System.orange, + yellow: .Light.System.yellow, + green: .Light.System.green, + blue: .Light.System.blue, + lightRed: .Light.System.lightGreen, + lightOrange: .Light.System.lightOrange, + lightYellow: .Light.System.lightYellow, + lightGreen: .Light.System.lightGreen, + lightBlue: .Light.System.lightBlue ), shadows: Shadows( - box: Shadow(color: UIColor(named: "Light/Shadows/Box"), opacity: 1.0, radius: 13.5, offset: CGSize(width: -1.0, height: 6.5)) + box: Shadow(color: .Light.Shadows.box, opacity: 1.0, radius: 13.5, offset: CGSize(width: -1.0, height: 6.5)) ) ) } @@ -178,56 +178,56 @@ extension ColorTheme { static var dark: Self { Self( brand: Brand( - purple: UIColor(named: "Dark/Brand/Purple"), - pink: UIColor(named: "Dark/Brand/Pink"), - darkBlue: UIColor(named: "Dark/Brand/Dark Blue") + purple: .Dark.Brand.purple, + pink: .Dark.Brand.pink, + darkBlue: .Dark.Brand.darkBlue ), neutral: Neutral( - primary: UIColor(named: "Dark/Neutral/Primary"), - secondary: UIColor(named: "Dark/Neutral/Secondary"), - tertiary: UIColor(named: "Dark/Neutral/Tertiary"), - inactive: UIColor(named: "Dark/Neutral/Inactive") + primary: .Dark.Neutral.primary, + secondary: .Dark.Neutral.secondary, + tertiary: .Dark.Neutral.tertiary, + inactive: .Dark.Neutral.inactive ), buttons: Buttons( - primaryStart: UIColor(named: "Dark/Buttons/PrimaryStart"), - primaryEnd: UIColor(named: "Dark/Buttons/PrimaryEnd"), - disabled: UIColor(named: "Dark/Buttons/Disabled"), - primaryText: UIColor(named: "Dark/Buttons/Primary Text"), - disabledText: UIColor(named: "Dark/Buttons/Disabled Text") + primaryStart: .Dark.Buttons.primaryStart, + primaryEnd: .Dark.Buttons.primaryEnd, + disabled: .Dark.Buttons.disabled, + primaryText: .Dark.Buttons.primaryText, + disabledText: .Dark.Buttons.disabledText ), text: Text( - heading: UIColor(named: "Dark/Text/Heading"), - body: UIColor(named: "Dark/Text/Body"), - lightText: UIColor(named: "Dark/Text/Light Text"), - links: UIColor(named: "Dark/Text/Links") + heading: .Dark.Text.heading, + body: .Dark.Text.body, + lightText: .Dark.Text.lightText, + links: .Dark.Text.links ), icons: Icons( - default: UIColor(named: "Dark/Icons/Default"), - active: UIColor(named: "Dark/Icons/Active"), - inactive: UIColor(named: "Dark/Icons/Inactive") + default: .Dark.Icons.default, + active: .Dark.Icons.active, + inactive: .Dark.Icons.inactive ), components: Components( - qrBackground: UIColor(named: "Dark/Components/QR Background"), - overlay: UIColor(named: "Dark/Components/Overlay") + qrBackground: .Dark.Components.qrBackground, + overlay: .Dark.Components.overlay ), backgrounds: Backgrounds( - primary: UIColor(named: "Dark/Backgrounds/Primary"), - secondary: UIColor(named: "Dark/Backgrounds/Secondary") + primary: .Dark.Backgrounds.primary, + secondary: .Dark.Backgrounds.secondary ), system: System( - red: UIColor(named: "Dark/System/Red"), - orange: UIColor(named: "Dark/System/Orange"), - yellow: UIColor(named: "Dark/System/Yellow"), - green: UIColor(named: "Dark/System/Green"), - blue: UIColor(named: "Dark/System/Blue"), - lightRed: UIColor(named: "Dark/System/Light Red"), - lightOrange: UIColor(named: "Dark/System/Light Orange"), - lightYellow: UIColor(named: "Dark/System/Light Yellow"), - lightGreen: UIColor(named: "Dark/System/Light Green"), - lightBlue: UIColor(named: "Dark/System/Light Blue") + red: .Dark.System.red, + orange: .Dark.System.orange, + yellow: .Dark.System.yellow, + green: .Dark.System.green, + blue: .Dark.System.blue, + lightRed: .Dark.System.lightRed, + lightOrange: .Dark.System.lightOrange, + lightYellow: .Dark.System.lightYellow, + lightGreen: .Dark.System.lightGreen, + lightBlue: .Dark.System.lightBlue ), shadows: Shadows( - box: Shadow(color: UIColor(named: "Dark/Shadows/Box"), opacity: 1.0, radius: 18.0, offset: CGSize(width: -1.0, height: 0.0)) + box: Shadow(color: .Dark.Shadows.box, opacity: 1.0, radius: 18.0, offset: CGSize(width: -1.0, height: 0.0)) ) ) } @@ -235,56 +235,56 @@ extension ColorTheme { static var tariPurple: Self { Self( brand: Brand( - purple: UIColor(named: "Tari Purple/Brand/Purple"), - pink: UIColor(named: "Tari Purple/Brand/Pink"), - darkBlue: UIColor(named: "Tari Purple/Brand/Dark Blue") + purple: .TariPurple.Brand.purple, + pink: .TariPurple.Brand.pink, + darkBlue: .TariPurple.Brand.darkBlue ), neutral: Neutral( - primary: UIColor(named: "Tari Purple/Neutral/Primary"), - secondary: UIColor(named: "Tari Purple/Neutral/Secondary"), - tertiary: UIColor(named: "Tari Purple/Neutral/Tertiary"), - inactive: UIColor(named: "Tari Purple/Neutral/Inactive") + primary: .TariPurple.Neutral.primary, + secondary: .TariPurple.Neutral.secondary, + tertiary: .TariPurple.Neutral.tertiary, + inactive: .TariPurple.Neutral.inactive ), buttons: Buttons( - primaryStart: UIColor(named: "Tari Purple/Buttons/PrimaryStart"), - primaryEnd: UIColor(named: "Tari Purple/Buttons/PrimaryEnd"), - disabled: UIColor(named: "Tari Purple/Buttons/Disabled"), - primaryText: UIColor(named: "Tari Purple/Buttons/Primary Text"), - disabledText: UIColor(named: "Tari Purple/Buttons/Disabled Text") + primaryStart: .TariPurple.Buttons.primaryStart, + primaryEnd: .TariPurple.Buttons.primaryEnd, + disabled: .TariPurple.Buttons.disabled, + primaryText: .TariPurple.Buttons.primaryText, + disabledText: .TariPurple.Buttons.disabledText ), text: Text( - heading: UIColor(named: "Tari Purple/Text/Heading"), - body: UIColor(named: "Tari Purple/Text/Body"), - lightText: UIColor(named: "Tari Purple/Text/Light Text"), - links: UIColor(named: "Tari Purple/Text/Links") + heading: .TariPurple.Text.heading, + body: .TariPurple.Text.body, + lightText: .TariPurple.Text.lightText, + links: .TariPurple.Text.links ), icons: Icons( - default: UIColor(named: "Tari Purple/Icons/Default"), - active: UIColor(named: "Tari Purple/Icons/Active"), - inactive: UIColor(named: "Tari Purple/Icons/Inactive") + default: .TariPurple.Icons.default, + active: .TariPurple.Icons.active, + inactive: .TariPurple.Icons.inactive ), components: Components( - qrBackground: UIColor(named: "Tari Purple/Components/QR Background"), - overlay: UIColor(named: "Tari Purple/Components/Overlay") + qrBackground: .TariPurple.Components.qrBackground, + overlay: .TariPurple.Components.overlay ), backgrounds: Backgrounds( - primary: UIColor(named: "Tari Purple/Backgrounds/Primary"), - secondary: UIColor(named: "Tari Purple/Backgrounds/Secondary") + primary: .TariPurple.Backgrounds.primary, + secondary: .TariPurple.Backgrounds.secondary ), system: System( - red: UIColor(named: "Tari Purple/System/Red"), - orange: UIColor(named: "Tari Purple/System/Orange"), - yellow: UIColor(named: "Tari Purple/System/Yellow"), - green: UIColor(named: "Tari Purple/System/Green"), - blue: UIColor(named: "Tari Purple/System/Blue"), - lightRed: UIColor(named: "Tari Purple/System/Light Red"), - lightOrange: UIColor(named: "Tari Purple/System/Light Orange"), - lightYellow: UIColor(named: "Tari Purple/System/Light Yellow"), - lightGreen: UIColor(named: "Tari Purple/System/Light Green"), - lightBlue: UIColor(named: "Tari Purple/System/Light Blue") + red: .TariPurple.System.red, + orange: .TariPurple.System.orange, + yellow: .TariPurple.System.yellow, + green: .TariPurple.System.green, + blue: .TariPurple.System.blue, + lightRed: .TariPurple.System.lightRed, + lightOrange: .TariPurple.System.lightOrange, + lightYellow: .TariPurple.System.lightYellow, + lightGreen: .TariPurple.System.lightGreen, + lightBlue: .TariPurple.System.lightBlue ), shadows: Shadows( - box: Shadow(color: UIColor(named: "Tari Purple/Shadows/Box"), opacity: 1.0, radius: 18.0, offset: CGSize(width: -1.0, height: 0.0)) + box: Shadow(color: .TariPurple.Shadows.box, opacity: 1.0, radius: 18.0, offset: CGSize(width: -1.0, height: 0.0)) ) ) } diff --git a/MobileWallet/Common/Theme/DynamicThemeView.swift b/MobileWallet/Common/Theme/DynamicThemeView.swift index 2eed1b14..134600c5 100644 --- a/MobileWallet/Common/Theme/DynamicThemeView.swift +++ b/MobileWallet/Common/Theme/DynamicThemeView.swift @@ -261,7 +261,7 @@ class DynamicThemeTextView: UITextView, ThemeViewProtocol { func update(theme: ColorTheme) {} } -class DynamicThemeViewController: UIViewController, ThemeViewProtocol { +class DynamicThemeViewController: SecureViewController, ThemeViewProtocol { // MARK: - Properties diff --git a/MobileWallet/Common/Toasts/SuccessToast.swift b/MobileWallet/Common/Toasts/SuccessToast.swift index 516c3045..72877c92 100644 --- a/MobileWallet/Common/Toasts/SuccessToast.swift +++ b/MobileWallet/Common/Toasts/SuccessToast.swift @@ -47,7 +47,7 @@ final class SuccessToast: UIView { @View private(set) var label: UILabel = { let view = UILabel() - view.textColor = .static.white + view.textColor = .Static.white view.font = Theme.shared.fonts.feedbackPopupDescription view.textAlignment = .center view.numberOfLines = 0 diff --git a/MobileWallet/Common/Toasts/ToastPresenter.swift b/MobileWallet/Common/Toasts/ToastPresenter.swift index 010d5fda..51684f7b 100644 --- a/MobileWallet/Common/Toasts/ToastPresenter.swift +++ b/MobileWallet/Common/Toasts/ToastPresenter.swift @@ -44,13 +44,11 @@ enum ToastPresenter { static func show(title: String, duration: TimeInterval = 2.0) { - guard let toastColor: UIColor = .static.purple else { return } - let toast = SuccessToast() toast.label.text = title var attributes = EKAttributes.topToast - attributes.entryBackground = .color(color: EKColor(toastColor)) + attributes.entryBackground = .color(color: EKColor(.Static.purple)) attributes.screenBackground = .clear attributes.displayDuration = duration attributes.hapticFeedbackType = .success diff --git a/MobileWallet/Common/Views/ContactSearchView.swift b/MobileWallet/Common/Views/ContactSearchView.swift index b4861727..6cacd183 100644 --- a/MobileWallet/Common/Views/ContactSearchView.swift +++ b/MobileWallet/Common/Views/ContactSearchView.swift @@ -54,7 +54,7 @@ final class ContactSearchView: DynamicThemeView { @View private var yatIconView: UIImageView = { let view = UIImageView() - view.image = Theme.shared.images.yatLogo?.withAlignmentRectInsets(UIEdgeInsets(top: -7.0, left: -11.0, bottom: -7.0, right: 0.0)) + view.image = .Icons.Yat.logo.withAlignmentRectInsets(UIEdgeInsets(top: -7.0, left: -11.0, bottom: -7.0, right: 0.0)) view.contentMode = .scaleAspectFit return view }() @@ -63,7 +63,7 @@ final class ContactSearchView: DynamicThemeView { @View private(set) var qrButton: PulseButton = { let view = PulseButton() - view.setImage(.icons.qr, for: .normal) + view.setImage(.Icons.General.QR, for: .normal) view.contentHorizontalAlignment = .fill view.contentVerticalAlignment = .fill view.imageEdgeInsets = UIEdgeInsets(top: 5.0, left: 5.0, bottom: 5.0, right: 5.0) @@ -116,6 +116,7 @@ final class ContactSearchView: DynamicThemeView { super.update(theme: theme) backgroundColor = theme.backgrounds.primary layer.borderColor = theme.backgrounds.secondary?.cgColor + previewView.backgroundColor = theme.backgrounds.primary qrButton.tintColor = theme.brand.purple yatIconView.tintColor = theme.icons.default } @@ -136,7 +137,8 @@ final class ContactSearchView: DynamicThemeView { previewView.topAnchor.constraint(equalTo: textField.topAnchor), previewView.leadingAnchor.constraint(equalTo: textField.leadingAnchor), previewView.trailingAnchor.constraint(equalTo: textField.trailingAnchor), - previewView.bottomAnchor.constraint(equalTo: textField.bottomAnchor) + previewView.bottomAnchor.constraint(equalTo: textField.bottomAnchor), + heightAnchor.constraint(equalToConstant: 46.0) ] updateViews() diff --git a/MobileWallet/Common/Views/RoundedGlassContentView.swift b/MobileWallet/Common/Views/RoundedGlassContentView.swift index 9620f7fa..aa24d168 100644 --- a/MobileWallet/Common/Views/RoundedGlassContentView.swift +++ b/MobileWallet/Common/Views/RoundedGlassContentView.swift @@ -60,9 +60,9 @@ final class RoundedGlassContentView: UIView { super.init(frame: .zero) setupConstraints() - layer.borderColor = UIColor.static.white?.withAlphaComponent(0.6).cgColor + layer.borderColor = UIColor.Static.white.withAlphaComponent(0.6).cgColor layer.borderWidth = 1.0 - backgroundColor = .static.white?.withAlphaComponent(0.4) + backgroundColor = .Static.white.withAlphaComponent(0.4) } diff --git a/MobileWallet/Common/Views/ScrollableLabel.swift b/MobileWallet/Common/Views/ScrollableLabel.swift index 2e900e3c..a456a5f1 100644 --- a/MobileWallet/Common/Views/ScrollableLabel.swift +++ b/MobileWallet/Common/Views/ScrollableLabel.swift @@ -40,17 +40,13 @@ import TariCommon -final class ScrollableLabel: UIView { - - // MARK: - Constants - - private let margin = 12.0 +final class ScrollableLabel: DynamicThemeView { // MARK: - Subviews @View var label: UILabel = { let view = UILabel() - view.font = Theme.shared.fonts.searchContactsInputBoxText + view.font = UIFont.Avenir.roman.withSize(14.0) return view }() @@ -68,10 +64,19 @@ final class ScrollableLabel: UIView { return layer }() + // MARK: - Properties + + var margin: CGFloat = 12.0 { + didSet { updateLayout() } + } + + private var leadingLabelConstraint: NSLayoutConstraint? + private var trailingLabelConstraint: NSLayoutConstraint? + // MARK: - Initialisers - init() { - super.init(frame: .zero) + override init() { + super.init() setupViews() setupConstraints() } @@ -83,7 +88,6 @@ final class ScrollableLabel: UIView { // MARK: - Setups private func setupViews() { - backgroundColor = .white layer.mask = maskLayer } @@ -92,17 +96,19 @@ final class ScrollableLabel: UIView { scrollView.addSubview(label) addSubview(scrollView) + let leadingLabelConstraint = label.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: margin) + let trailingLabelConstraint = label.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -margin) + let constraints = [ scrollView.topAnchor.constraint(equalTo: topAnchor), scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), scrollView.bottomAnchor.constraint(equalTo: bottomAnchor), label.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), - label.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: margin), - label.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -margin), + leadingLabelConstraint, + trailingLabelConstraint, label.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), - label.heightAnchor.constraint(equalTo: heightAnchor), - heightAnchor.constraint(equalToConstant: 46.0) + label.heightAnchor.constraint(equalTo: heightAnchor) ] NSLayoutConstraint.activate(constraints) @@ -118,4 +124,11 @@ final class ScrollableLabel: UIView { let endPoint = 1.0 - startPoint maskLayer.locations = [0.0, NSNumber(value: startPoint), NSNumber(value: endPoint), 1.0] } + + // MARK: - Updates + + private func updateLayout() { + leadingLabelConstraint?.constant = margin + trailingLabelConstraint?.constant = margin + } } diff --git a/MobileWallet/Common/Views/SearchField.swift b/MobileWallet/Common/Views/SearchField.swift index 7971b408..cb826d7d 100644 --- a/MobileWallet/Common/Views/SearchField.swift +++ b/MobileWallet/Common/Views/SearchField.swift @@ -46,7 +46,7 @@ final class SearchField: DynamicThemeTextField { @View private var iconView: UIImageView = { let view = UIImageView() - view.image = .icons.magnifyingGlass + view.image = .Icons.General.magnifyingGlass view.contentMode = .scaleAspectFit return view }() diff --git a/MobileWallet/Common/Views/Secure View/SecureViewController.swift b/MobileWallet/Common/Views/Secure View/SecureViewController.swift new file mode 100644 index 00000000..3c04ba56 --- /dev/null +++ b/MobileWallet/Common/Views/Secure View/SecureViewController.swift @@ -0,0 +1,61 @@ +// SecureViewController.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 20/02/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +class SecureViewController: UIViewController { + + // MARK: - Views + + var mainView: MainView { secureWrapper.view } + + var screenshotPreventionStatus: ScreenshotPreventionStatus { + get { secureWrapper.screenshotPreventionStatus } + set { secureWrapper.screenshotPreventionStatus = newValue } + } + + private let secureWrapper = SecureWrapperView() + + // MARK: - View lifecycle + + override func loadView() { + view = secureWrapper + } +} diff --git a/MobileWallet/Common/Views/Secure View/SecureWrapperView.swift b/MobileWallet/Common/Views/Secure View/SecureWrapperView.swift new file mode 100644 index 00000000..e3245ef5 --- /dev/null +++ b/MobileWallet/Common/Views/Secure View/SecureWrapperView.swift @@ -0,0 +1,134 @@ +// SecureWrapperView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 13/02/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import TariCommon +import Combine + +enum ScreenshotPreventionStatus { + case enforced + case turnedOff + case automatic +} + +final class SecureWrapperView: UIView { + + // MARK: - Subviews + + @View private(set) var view: MainView + + @View private var textField: UITextField = { + let view = UITextField() + view.isUserInteractionEnabled = false + view.backgroundColor = .clear + return view + }() + + private var contentView: UIView? { textField.subviews.first { type(of: $0).description().contains("CanvasView") }} + + // MARK: - Properties + + @Published var screenshotPreventionStatus: ScreenshotPreventionStatus = .automatic + private var cancellables = Set() + + // MARK: - Initialisers + + convenience init() { + self.init(mainView: MainView()) + } + + init(mainView: MainView) { + self.view = mainView + super.init(frame: .zero) + setupViews() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupViews() { + + guard let contentView else { + Logger.log(message: "No secure content view", domain: .userInterface, level: .warning) + return + } + + addSubview(contentView) + + if #unavailable(iOS 16.0) { + contentView.isUserInteractionEnabled = true + } + + contentView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(view) + + let constraints = [ + contentView.topAnchor.constraint(equalTo: topAnchor), + contentView.leadingAnchor.constraint(equalTo: leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: bottomAnchor), + view.topAnchor.constraint(equalTo: contentView.topAnchor), + view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + Publishers.CombineLatest(SecurityManager.shared.$areScreenshotsDisabled, $screenshotPreventionStatus) + .sink { [weak self] in self?.handle(areScreenshotsDisabled: $0, screenshotPreventionStatus: $1) } + .store(in: &cancellables) + } + + private func handle(areScreenshotsDisabled: Bool, screenshotPreventionStatus: ScreenshotPreventionStatus) { + + guard screenshotPreventionStatus == .automatic else { + textField.isSecureTextEntry = screenshotPreventionStatus == .enforced + return + } + + textField.isSecureTextEntry = areScreenshotsDisabled + } +} diff --git a/MobileWallet/Common/Views/TickButton.swift b/MobileWallet/Common/Views/TickButton.swift index 81d1743e..564a5f8c 100644 --- a/MobileWallet/Common/Views/TickButton.swift +++ b/MobileWallet/Common/Views/TickButton.swift @@ -78,6 +78,6 @@ final class TickButton: DynamicThemeBaseButton { } private func update(selectionState: Bool) { - setImage(selectionState ? Theme.shared.images.utxoTick : nil, for: .normal) + setImage(selectionState ? .Icons.General.tick : nil, for: .normal) } } diff --git a/MobileWallet/Info.plist b/MobileWallet/Info.plist index dd82dc58..2016b8bb 100644 --- a/MobileWallet/Info.plist +++ b/MobileWallet/Info.plist @@ -51,15 +51,15 @@ LSRequiresIPhoneOS NSBluetoothAlwaysUsageDescription - + NSBluetoothAlwaysUsageDescription NSCameraUsageDescription - + NSCameraUsageDescription NSContactsUsageDescription - + NSContactsUsageDescription NSFaceIDUsageDescription - + NSFaceIDUsageDescription NSPhotoLibraryAddUsageDescription - + NSPhotoLibraryAddUsageDescription NSUbiquitousContainers iCloud.com.tari.wallet diff --git a/MobileWallet/Libraries/TariLib/Core/Data Models/WalletBalance.swift b/MobileWallet/Libraries/TariLib/Core/Data Models/WalletBalance.swift index 642c5767..fb71d6ab 100644 --- a/MobileWallet/Libraries/TariLib/Core/Data Models/WalletBalance.swift +++ b/MobileWallet/Libraries/TariLib/Core/Data Models/WalletBalance.swift @@ -49,5 +49,5 @@ struct WalletBalance: Equatable { } extension WalletBalance { - var total: UInt64 { available + incoming } + var total: UInt64 { available + incoming + timeLocked } } diff --git a/MobileWallet/Libraries/TariLib/Core/FFI/BaseNodeConnectivityStatus.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Base Node/BaseNodeConnectivityStatus.swift similarity index 100% rename from MobileWallet/Libraries/TariLib/Core/FFI/BaseNodeConnectivityStatus.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Base Node/BaseNodeConnectivityStatus.swift diff --git a/MobileWallet/Screens/AdvancedSettings/AddBaseNode/AddBaseNodeModel.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Base Node/BaseNodeState.swift similarity index 64% rename from MobileWallet/Screens/AdvancedSettings/AddBaseNode/AddBaseNodeModel.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Base Node/BaseNodeState.swift index 83a63dd1..add7ee4b 100644 --- a/MobileWallet/Screens/AdvancedSettings/AddBaseNode/AddBaseNodeModel.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFI/Base Node/BaseNodeState.swift @@ -1,10 +1,10 @@ -// AddBaseNodeModel.swift +// BaseNodeState.swift /* Package MobileWallet - Created by Adrian Truszczynski on 19/07/2021 + Created by Adrian Truszczyński on 18/03/2024 Using Swift 5.0 - Running on macOS 12.0 + Running on macOS 14.2 Copyright 2019 The Tari Project @@ -38,34 +38,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -final class AddBaseNodeModel { - - final class ViewModel { - @Published var name: String = "" - @Published var peer: String = "" - @Published var isFinished: Bool = false - @Published var errorMessage: String? - } +final class BaseNodeState { // MARK: - Properties - let viewModel = ViewModel() - - // MARK: - Actions + var heightOfTheLongestChain: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = basenode_state_get_height_of_the_longest_chain(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } - func saveNode() { + private let pointer: OpaquePointer - guard !viewModel.name.isEmpty else { - viewModel.errorMessage = localized("add_base_node.error.invalid_peer") - return - } + // MARK: - Initialisers - do { - try Tari.shared.connection.addBaseNode(name: viewModel.name, peer: viewModel.peer) - viewModel.isFinished = true - viewModel.errorMessage = nil - } catch { - viewModel.errorMessage = localized("add_base_node.error.invalid_peer") - } + init(pointer: OpaquePointer) { + self.pointer = pointer } } diff --git a/MobileWallet/Libraries/TariLib/Core/FFI/Errors/BaseNodeConnectionError.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Errors/BaseNodeConnectionError.swift new file mode 100644 index 00000000..5f91dca5 --- /dev/null +++ b/MobileWallet/Libraries/TariLib/Core/FFI/Errors/BaseNodeConnectionError.swift @@ -0,0 +1,44 @@ +// BaseNodeConnectionError.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 12/03/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum BaseNodeConnectionError: Error { + case noSelectedBaseNode + case noBaseNodeToSwitch +} diff --git a/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift index 9ed7f56c..06f9f33d 100644 --- a/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift @@ -59,6 +59,10 @@ final class PublicKey { // MARK: - Initialisers + init(pointer: OpaquePointer) { + self.pointer = pointer + } + init(hex: String) throws { guard hex.count == 64, hex.rangeOfCharacter(from: .hexadecimal) != nil else { throw InternalError.invalidHex } diff --git a/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKeys.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKeys.swift new file mode 100644 index 00000000..8b039953 --- /dev/null +++ b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKeys.swift @@ -0,0 +1,81 @@ +// PublicKeys.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 07/03/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class PublicKeys { + + // MARK: - Properties + + var pointer: OpaquePointer + + var count: UInt32 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = public_keys_get_length(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + // MARK: - Initialisers + + init(pointer: OpaquePointer) { + self.pointer = pointer + } + + // MARK: - Actions + + func publicKey(index: UInt32) throws -> PublicKey { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = public_keys_get_at(pointer, index, errorCodePointer) + guard errorCode == 0, let result else { throw WalletError(code: errorCode) } + return PublicKey(pointer: result) + } +} + +extension PublicKeys { + + var all: [PublicKey] { + get throws { + try (0.. Void = { pointer in WalletCallbacksManager.shared.post(name: .receivedTransaction, object: pointer) @@ -75,7 +75,7 @@ final class Wallet { } let transactionSendResultCallback: (@convention(c) (UInt64, OpaquePointer?) -> Void) = { identifier, pointer in - guard let pointer = pointer, let data = try? TransactionSendResult(identifier: identifier, pointer: pointer) else { return } + guard let pointer, let data = try? TransactionSendResult(identifier: identifier, pointer: pointer) else { return } WalletCallbacksManager.shared.post(name: .transactionSendResult, object: data) } @@ -103,11 +103,8 @@ final class Wallet { let contactsLivenessDataUpdatedCallback: (@convention(c) (OpaquePointer?) -> Void) = { _ in } - let balanceUpdatedCallback: @convention(c) (OpaquePointer?) -> Void = { balancePointer in - guard let balancePointer = balancePointer else { return } - let balance = Balance(pointer: balancePointer) - WalletCallbacksManager.shared.post(name: .walletBalanceUpdate, object: balance) - + let balanceUpdatedCallback: @convention(c) (OpaquePointer?) -> Void = { pointer in + WalletCallbacksManager.shared.post(name: .walletBalanceUpdate, object: pointer) } let trasactionValidationCompleteCallback: @convention(c) (UInt64, UInt64) -> Void = { responseId, status in @@ -118,12 +115,12 @@ final class Wallet { let storedMessagesReceivedCallback: (@convention(c) () -> Void) = { } - let connectivityStatusCallback: (@convention(c) (UInt64) -> Void) = { rawStatus in - guard let status = BaseNodeConnectivityStatus(rawValue: rawStatus) else { return } + let connectivityStatusCallback: (@convention(c) (UInt64) -> Void) = { status in WalletCallbacksManager.shared.post(name: .baseNodeConnectionStatusUpdate, object: status) } - let baseNodeStateCallback: (@convention(c) (OpaquePointer?) -> Void) = { _ in + let baseNodeStateCallback: (@convention(c) (OpaquePointer?) -> Void) = { pointer in + WalletCallbacksManager.shared.post(name: .baseNodeStateUpdate, object: pointer) } var isRecoveryInProgress = false @@ -143,6 +140,8 @@ final class Wallet { passphrase, seedWords?.pointer, networkName, + dnsPeer, + isDnsSecureOn, receivedTransactionCallback, receivedTransactionReplyCallback, receivedFinalizedTransactionCallback, @@ -164,7 +163,7 @@ final class Wallet { errorCodePointer ) - guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } + guard errorCode == 0, let result else { throw WalletError(code: errorCode) } pointer = result } diff --git a/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift b/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift index 053d3e02..57f6f4c5 100644 --- a/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift @@ -88,11 +88,20 @@ final class FFIWalletManager { // MARK: - Actions - func connectWallet(commsConfig: CommsConfig, logFilePath: String, seedWords: SeedWords?, passphrase: String?, networkName: String, logVerbosity: Int32) throws { + func connectWallet(commsConfig: CommsConfig, logFilePath: String, seedWords: SeedWords?, passphrase: String?, networkName: String, dnsPeer: String, isDnsSecureOn: Bool, logVerbosity: Int32) throws { do { let beforeWalletCreationDate = Date() Logger.log(message: "Wallet will be created", domain: .general, level: .info) - wallet = try Wallet(commsConfig: commsConfig, loggingFilePath: logFilePath, seedWords: seedWords, passphrase: passphrase, networkName: networkName, logVerbosity: logVerbosity) + wallet = try Wallet( + commsConfig: commsConfig, + loggingFilePath: logFilePath, + seedWords: seedWords, + passphrase: passphrase, + networkName: networkName, + dnsPeer: dnsPeer, + isDnsSecureOn: isDnsSecureOn, + logVerbosity: logVerbosity + ) Logger.log(message: "Wallet created after \(-beforeWalletCreationDate.timeIntervalSinceNow) seconds", domain: .general, level: .info) } catch { Logger.log(message: "Wallet wasn't created: \(error)", domain: .general, level: .info) @@ -285,18 +294,30 @@ final class FFIWalletManager { return TariFeePerGramStats(pointer: pointer) } - func addBaseNodePeer(publicKeyPointer: OpaquePointer, address: String) throws -> Bool { + func set(baseNodePeer: PublicKey, address: String?) throws -> Bool { let wallet = try exisingWallet var errorCode: Int32 = -1 let errorCodePointer = PointerHandler.pointer(for: &errorCode) - let result = wallet_add_base_node_peer(wallet.pointer, publicKeyPointer, address, errorCodePointer) + let result = wallet_set_base_node_peer(wallet.pointer, baseNodePeer.pointer, address, errorCodePointer) guard errorCode == 0 else { throw WalletError(code: errorCode) } return result } + func seedPeers() throws -> PublicKeys { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_seed_peers(wallet.pointer, errorCodePointer) + + guard errorCode == 0, let result else { throw WalletError(code: errorCode) } + return PublicKeys(pointer: result) + } + func utxos() throws -> [TariUtxo] { let wallet = try exisingWallet diff --git a/MobileWallet/Libraries/TariLib/Core/Services/TariConnectionService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariConnectionService.swift index 8450f7bc..66f567e9 100644 --- a/MobileWallet/Libraries/TariLib/Core/Services/TariConnectionService.swift +++ b/MobileWallet/Libraries/TariLib/Core/Services/TariConnectionService.swift @@ -40,16 +40,20 @@ final class TariConnectionService: CoreTariService { + enum InternalError: Error { + case invalidPeerString + } + // MARK: - Actions @discardableResult func select(baseNode: BaseNode) throws -> Bool { services.validation.reset() do { - let result = try walletManager.addBaseNodePeer(publicKeyPointer: baseNode.publicKey.pointer, address: baseNode.address) - NetworkManager.shared.selectedNetwork.selectedBaseNode = baseNode + let result = try walletManager.set(baseNodePeer: baseNode.makePublicKey(), address: baseNode.address) + NetworkManager.shared.selectedBaseNode = baseNode return result } catch FFIWalletManager.GeneralError.unableToCreateWallet { - NetworkManager.shared.selectedNetwork.selectedBaseNode = baseNode + NetworkManager.shared.selectedBaseNode = baseNode return false } catch { throw error @@ -57,12 +61,26 @@ final class TariConnectionService: CoreTariService { } @discardableResult func selectCurrentNode() throws -> Bool { - try select(baseNode: NetworkManager.shared.selectedNetwork.selectedBaseNode) + guard let selectedBaseNode = NetworkManager.shared.selectedBaseNode else { return false } + return try select(baseNode: selectedBaseNode) } - func addBaseNode(name: String, peer: String) throws { - let baseNode = try BaseNode(name: name, peer: peer) - NetworkManager.shared.selectedNetwork.customBaseNodes.append(baseNode) + func addBaseNode(name: String, hex: String, address: String?) throws { + let baseNode = BaseNode(name: name, hex: hex, address: address) + NetworkManager.shared.customBaseNodes.append(baseNode) try select(baseNode: baseNode) } + + func addBaseNode(name: String, peer: String) throws { + let components = peer.components(separatedBy: "::") + guard components.count == 2 else { throw InternalError.invalidPeerString } + try addBaseNode(name: name, hex: components[0], address: components[1]) + } + + func defaultBaseNodePeers() throws -> [BaseNode] { + try walletManager.seedPeers() + .all + .enumerated() + .map { try BaseNode(name: "\(NetworkManager.shared.selectedNetwork.presentedName) \($0 + 1)", hex: $1.byteVector.hex, address: nil) } + } } diff --git a/MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift index 7fab18bd..437315db 100644 --- a/MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift +++ b/MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift @@ -49,8 +49,8 @@ final class TariRecoveryService: CoreTariService { // MARK: - Actions func startRecovery(recoveredOutputMessage: String) throws -> Bool { - let publicKey = NetworkManager.shared.selectedNetwork.selectedBaseNode.publicKey - return try walletManager.startRecovery(baseNodePublicKey: publicKey, recoveredOutputMessage: recoveredOutputMessage) + guard let selectedBaseNode = NetworkManager.shared.selectedBaseNode else { return false } + return try walletManager.startRecovery(baseNodePublicKey: selectedBaseNode.makePublicKey(), recoveredOutputMessage: recoveredOutputMessage) } func allSeedWords(forLanguage language: SeedWordsMnemonicWordList.Language) throws -> [String] { diff --git a/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift index a9fc66a4..ef564c02 100644 --- a/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift +++ b/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift @@ -52,6 +52,7 @@ final class TariTransactionsService: CoreTariService { @Published private(set) var cancelled: [CompletedTransaction] = [] @Published private(set) var pendingInbound: [PendingInboundTransaction] = [] @Published private(set) var pendingOutbound: [PendingOutboundTransaction] = [] + @Published private(set) var all: [Transaction] = [] @Published private(set) var error: Error? var requiredConfirmationsCount: UInt64 { @@ -108,6 +109,10 @@ final class TariTransactionsService: CoreTariService { cancelled = try cancelledTransactions pendingInbound = try pendingInboundTransactions pendingOutbound = try pendingOutboundTransactions + + all = try (completed + cancelled + pendingInbound + pendingOutbound) + .sorted { try $0.timestamp > $1.timestamp } + } catch { self.error = error } @@ -183,16 +188,8 @@ final class TariTransactionsService: CoreTariService { extension TariTransactionsService { - var all: AnyPublisher<[Transaction], Never> { - Publishers.CombineLatest4($completed, $cancelled, $pendingInbound, $pendingOutbound) - .map { $0 as [Transaction] + $1 + $2 + $3 } - .tryMap { try $0.sorted { try $0.timestamp > $1.timestamp }} - .replaceError(with: [Transaction]()) - .eraseToAnyPublisher() - } - var onUpdate: AnyPublisher { - Publishers.CombineLatest4($completed, $cancelled, $pendingInbound, $pendingOutbound) + $all .onChangePublisher() .eraseToAnyPublisher() } diff --git a/MobileWallet/Libraries/TariLib/Core/Tari.swift b/MobileWallet/Libraries/TariLib/Core/Tari.swift index f9808d5b..ef004d08 100644 --- a/MobileWallet/Libraries/TariLib/Core/Tari.swift +++ b/MobileWallet/Libraries/TariLib/Core/Tari.swift @@ -104,12 +104,13 @@ final class Tari: MainServiceable { @Published private(set) var isWalletConnected: Bool = false @Published private(set) var torError: TorError? + @Published private(set) var blockHeight: UInt64 = NetworkManager.shared.blockHeight var canAutomaticalyReconnectWallet: Bool = false @Published var isDisconnectionDisabled: Bool = false - private let torManager = TorManager() private let walletManager = FFIWalletManager() + private lazy var torManager = TorManager(logPath: logFilePath) private var cancellables = Set() private var passphrase: String { @@ -183,6 +184,15 @@ final class Tari: MainServiceable { torManager.$error .assignPublisher(to: \.torError, on: self) .store(in: &cancellables) + + WalletCallbacksManager.shared.baseNodeState + .compactMap { try? $0.heightOfTheLongestChain } + .removeDuplicates() + .sink { [weak self] in + NetworkManager.shared.blockHeight = $0 + self?.blockHeight = $0 + } + .store(in: &cancellables) } // MARK: - Actions @@ -240,12 +250,20 @@ final class Tari: MainServiceable { try createWalletDirectory() } - let logFilePath = logFilePath Logger.log(message: "Log Path: \(logFilePath)", domain: .general, level: .info) let logVerbosity: Int32 = TariSettings.shared.environment == .debug ? 11 : 4 - try walletManager.connectWallet(commsConfig: commsConfig, logFilePath: logFilePath, seedWords: walletSeedWords, passphrase: passphrase, networkName: selectedNetwork.name, logVerbosity: logVerbosity) + try walletManager.connectWallet( + commsConfig: commsConfig, + logFilePath: logFilePath, + seedWords: walletSeedWords, + passphrase: passphrase, + networkName: selectedNetwork.name, + dnsPeer: selectedNetwork.dnsPeer, + isDnsSecureOn: false, + logVerbosity: logVerbosity + ) resetServices() } @@ -314,13 +332,18 @@ final class Tari: MainServiceable { private func switchBaseNode() throws { - let selectedBaseNode = NetworkManager.shared.selectedNetwork.selectedBaseNode - guard NetworkManager.shared.selectedNetwork.baseNodes.contains(selectedBaseNode), NetworkManager.shared.selectedNetwork.baseNodes.count > 1 else { return } + guard isWalletConnected else { return } + + let selectedBaseNode = NetworkManager.shared.selectedBaseNode + + if let selectedBaseNode, selectedBaseNode.isCustomBaseNode { + return + } var newBaseNode: BaseNode repeat { - newBaseNode = try NetworkManager.shared.selectedNetwork.randomNode() + newBaseNode = try NetworkManager.shared.randomBaseNode() } while newBaseNode == selectedBaseNode try connection.select(baseNode: newBaseNode) diff --git a/MobileWallet/Libraries/TariLib/Core/TorManager.swift b/MobileWallet/Libraries/TariLib/Core/TorManager.swift index 42a72164..7a2c6371 100644 --- a/MobileWallet/Libraries/TariLib/Core/TorManager.swift +++ b/MobileWallet/Libraries/TariLib/Core/TorManager.swift @@ -68,6 +68,8 @@ final class TorManager { var isUsingCustomBridges: Bool { TorManagerUserDefaults.isUsingCustomBridges ?? false } var bridges: String? { TorManagerUserDefaults.torBridges } + private let logPath: String + private var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid private var controller: TorController? private var queuedAction: Action? { @@ -104,7 +106,8 @@ final class TorManager { // MARK: - Init - init() { + init(logPath: String) { + self.logPath = logPath setupCallbacks() } @@ -430,6 +433,7 @@ final class TorManager { var arguments: [String] = makeBaseArguments() arguments += makeBridgeArguments() arguments += makeIpArguments() + arguments += makeLogsArguments() let configuration = TorConfiguration() configuration.cookieAuthentication = true @@ -440,21 +444,13 @@ final class TorManager { } private func makeBaseArguments() -> [String] { - - #if DEBUG - let logLocation = "notice stdout" - #else - let logLocation = "notice file /dev/null" - #endif - - return [ + [ "--allow-missing-torrc", "--ignore-missing-torrc", "--clientonly", "1", "--AvoidDiskWrites", "1", "--socksport", "39059", "--controlport", "\(socketHost):\(port)", - "--log", logLocation, "--clientuseipv6", "1", "--ClientTransportPlugin", "obfs4 socks5 127.0.0.1:47351", "--ClientTransportPlugin", "meek_lite socks5 127.0.0.1:47352", @@ -487,4 +483,15 @@ final class TorManager { return arguments } + + private func makeLogsArguments() -> [String] { + + var arguments: [String] = ["--log", "notice file \(logPath)"] + + if TariSettings.shared.environment == .debug { + arguments += ["--log", "notice stdout"] + } + + return arguments + } } diff --git a/MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift b/MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift index de569efb..497d49e9 100644 --- a/MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift +++ b/MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift @@ -145,7 +145,8 @@ final class WalletCallbacksManager { let walletBalanceUpdatePublisher: AnyPublisher = { NotificationCenter.default .publisher(for: .walletBalanceUpdate) - .compactMap { $0.object as? Balance } + .compactMap { $0.object as? OpaquePointer } + .map { Balance(pointer: $0) } .share() .eraseToAnyPublisher() }() @@ -161,7 +162,17 @@ final class WalletCallbacksManager { let baseNodeConnectionStatus: AnyPublisher = { NotificationCenter.default .publisher(for: .baseNodeConnectionStatusUpdate) - .compactMap { $0.object as? BaseNodeConnectivityStatus } + .compactMap { $0.object as? UInt64 } + .compactMap { BaseNodeConnectivityStatus(rawValue: $0) } + .share() + .eraseToAnyPublisher() + }() + + let baseNodeState: AnyPublisher = { + NotificationCenter.default + .publisher(for: .baseNodeStateUpdate) + .compactMap { $0.object as? OpaquePointer } + .map { BaseNodeState(pointer: $0) } .share() .eraseToAnyPublisher() }() @@ -212,6 +223,7 @@ extension Notification.Name { static let walletBalanceUpdate = Self(rawValue: "com.tari.wallet.balance_update") static let transactionValidation = Self(rawValue: "com.tari.wallet.transaction_validation") static let baseNodeConnectionStatusUpdate = Self(rawValue: "com.tari.wallet.base_node_connection_status_update") + static let baseNodeStateUpdate = Self(rawValue: "com.tari.wallet.base_node_state_update") // MARK: - Recovery Callbacks diff --git a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/BaseNode.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/BaseNode.swift index 813aea2c..d7cb2fe4 100644 --- a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/BaseNode.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/BaseNode.swift @@ -38,62 +38,33 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -struct BaseNode: Equatable { - - // MARK: - Subelements - - enum InternalError: Error { - case invalidPeerString - } - - // MARK: - Properties - +/// Represents a configuration for a base node in a network. +struct BaseNode: Equatable, Codable { + /// The name of the base node. let name: String + /// The hex identifier of the base node. let hex: String - let address: String - let publicKey: PublicKey - - var peer: String { "\(hex)::\(address)" } - - // MARK: - Initializers - - init(name: String, peer: String) throws { - let peerComponents = peer.components(separatedBy: "::") - guard peerComponents.count == 2 else { throw InternalError.invalidPeerString } - try self.init(name: name, hex: peerComponents[0], address: peerComponents[1]) - } - - private init(name: String, hex: String, address: String) throws { - self.name = name - self.hex = hex - self.address = address - - publicKey = try PublicKey(hex: hex) - - try validateData() - } - - // MARK: - Actions - - private func validateData() throws { - let onionRegex = try NSRegularExpression(pattern: "[a-z0-9]{64}::\\/onion3\\/[a-z0-9]{56}:[0-9]{2,6}") - let ip4Regex = try NSRegularExpression(pattern: "[a-z0-9]{64}::\\/ip4\\/[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}\\/tcp\\/[0-9]{2,6}") - let range = NSRange(location: 0, length: peer.utf16.count) - guard onionRegex.matches(in: peer, options: [], range: range).count == 1 || ip4Regex.matches(in: peer, range: range).count == 1 else { throw InternalError.invalidPeerString } - } + /// The network address of the base node, It's equal to `nil` for default base nodes. + let address: String? } -extension BaseNode: Codable { +extension BaseNode { + + /// Determines whether the base node is a custom node. + var isCustomBaseNode: Bool { address != nil } - enum CodingKeys: CodingKey { - case name, hex, address + /// The peer identifier of the base node. The value is a generated based on the base node's hex and address. + var peer: String { + [hex, address] + .compactMap { $0 } + .joined(separator: "::") } - init(from decoder: Decoder) throws { - let containter = try decoder.container(keyedBy: CodingKeys.self) - let name = try containter.decode(String.self, forKey: .name) - let hex = try containter.decode(String.self, forKey: .hex) - let address = try containter.decode(String.self, forKey: .address) - try self.init(name: name, hex: hex, address: address) + /// Generates a `PublicKey` based on the configuration of the base node. + /// + /// - Throws: An error if the public key generation fails. + /// - Returns: The generated public key. + func makePublicKey() throws -> PublicKey { + try PublicKey(hex: hex) } } diff --git a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkManager.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkManager.swift index 27d8ab84..203062a9 100644 --- a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkManager.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkManager.swift @@ -42,13 +42,47 @@ import Combine final class NetworkManager { + enum InternalError: Error { + case noBaseNode + } + // MARK: - Properties static let shared = NetworkManager() + private static var defaultNetwork: TariNetwork { .nextnet } @Published var selectedNetwork: TariNetwork - private static var defaultNetwork: TariNetwork { .nextnet } + var defaultBaseNodes: [BaseNode] { (try? Tari.shared.connection.defaultBaseNodePeers()) ?? [] } + + var selectedBaseNode: BaseNode? { + get { settings.selectedBaseNode } + set { update(settings: settings.update(selectedBaseNode: newValue)) } + } + + var customBaseNodes: [BaseNode] { + get { settings.customBaseNodes } + set { update(settings: settings.update(customBaseNodes: newValue)) } + } + + var blockHeight: UInt64 { + get { settings.blockHeight } + set { update(settings: settings.update(blockHeight: newValue)) } + } + + var allBaseNodes: [BaseNode] { defaultBaseNodes + customBaseNodes } + + private var settings: NetworkSettings { + let allSettings = GroupUserDefaults.networksSettings ?? [] + + guard let existingSettings = allSettings.first(where: { $0.name == selectedNetwork.name }) else { + let newSettings = NetworkSettings(name: selectedNetwork.name, selectedBaseNode: nil, customBaseNodes: [], blockHeight: 0) + update(settings: newSettings) + return newSettings + } + return existingSettings + } + private var cancelables = Set() // MARK: - Initializers @@ -80,4 +114,16 @@ final class NetworkManager { GroupUserDefaults.networksSettings?.removeAll { $0.name == GroupUserDefaults.selectedNetworkName } GroupUserDefaults.selectedNetworkName = nil } + + func randomBaseNode() throws -> BaseNode { + guard let newBaseNode = defaultBaseNodes.randomElement() else { throw InternalError.noBaseNode } + return newBaseNode + } + + private func update(settings: NetworkSettings) { + var allSettings = GroupUserDefaults.networksSettings ?? [] + allSettings.removeAll { $0 == settings } + allSettings.append(settings) + GroupUserDefaults.networksSettings = allSettings + } } diff --git a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkSettings.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkSettings.swift index ff94e873..4e3f4cd9 100644 --- a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkSettings.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkSettings.swift @@ -41,15 +41,15 @@ struct NetworkSettings: Codable, Equatable { let name: String - let selectedBaseNode: BaseNode + let selectedBaseNode: BaseNode? let customBaseNodes: [BaseNode] + let blockHeight: UInt64 static func == (lhs: Self, rhs: Self) -> Bool { lhs.name == rhs.name } } extension NetworkSettings { - func update(selectedBaseNode: BaseNode) -> Self { Self(name: name, selectedBaseNode: selectedBaseNode, customBaseNodes: customBaseNodes) } - func update(customBaseNodes: [BaseNode]) -> Self { Self(name: name, selectedBaseNode: selectedBaseNode, customBaseNodes: customBaseNodes) } - func update(isCloudBackupEnabled: Bool) -> Self { Self(name: name, selectedBaseNode: selectedBaseNode, customBaseNodes: customBaseNodes) } - func update(hasVerifiedSeedPhrase: Bool) -> Self { Self(name: name, selectedBaseNode: selectedBaseNode, customBaseNodes: customBaseNodes) } + func update(selectedBaseNode: BaseNode?) -> Self { Self(name: name, selectedBaseNode: selectedBaseNode, customBaseNodes: customBaseNodes, blockHeight: blockHeight) } + func update(customBaseNodes: [BaseNode]) -> Self { Self(name: name, selectedBaseNode: selectedBaseNode, customBaseNodes: customBaseNodes, blockHeight: blockHeight) } + func update(blockHeight: UInt64) -> Self { Self(name: name, selectedBaseNode: selectedBaseNode, customBaseNodes: customBaseNodes, blockHeight: blockHeight) } } diff --git a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift index b89fba5d..bb157be0 100644 --- a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift @@ -42,74 +42,60 @@ struct TariNetwork { let name: String let presentedName: String let tickerSymbol: String - let baseNodes: [BaseNode] + let isRecommended: Bool + let dnsPeer: String + let blockExplorerURL: URL? } extension TariNetwork { - enum Error: Swift.Error { - case noPredefinedNodes - } - - var allBaseNodes: [BaseNode] { baseNodes.sorted { $0.name < $1.name } + customBaseNodes } - - private var settings: NetworkSettings { - let allSettings = GroupUserDefaults.networksSettings ?? [] - - guard let existingSettings = allSettings.first(where: { $0.name == name }) else { - let newSettings = NetworkSettings(name: name, selectedBaseNode: baseNodes.randomElement()!, customBaseNodes: []) - update(settings: newSettings) - return newSettings - } - return existingSettings - } - - var selectedBaseNode: BaseNode { - get { settings.selectedBaseNode } - set { update(settings: settings.update(selectedBaseNode: newValue)) } - } - - var customBaseNodes: [BaseNode] { - get { settings.customBaseNodes } - set { update(settings: settings.update(customBaseNodes: newValue)) } - } - - func randomNode() throws -> BaseNode { - guard let randomNode = baseNodes.randomElement() else { throw Error.noPredefinedNodes } - return randomNode - } + static var all: [TariNetwork] { [nextnet].compactMap { $0 } } - private func update(settings: NetworkSettings) { - var allSettings = GroupUserDefaults.networksSettings ?? [] - allSettings.removeAll { $0 == settings } - allSettings.append(settings) - GroupUserDefaults.networksSettings = allSettings + static var stagenet: Self { + makeNetwork( + name: "stagenet", + presentedName: "StageNet", + isMainNet: false, + isRecommended: false, + dnsPeer: "seeds.stagenet.tari.com", + blockExplorerURL: nil + ) } -} - -extension TariNetwork { - - static var all: [TariNetwork] { [nextnet].compactMap { $0 } } static var nextnet: Self { makeNetwork( name: "nextnet", - presentedName: "NextNet (\(localized("common.recommended")))", + presentedName: "NextNet", isMainNet: false, - rawBaseNodes: [ - "NextNet 1": "0cff11dff44458bfea3e39444d440e54260746ff2a5ce6a6c3f7355decff2167::/ip4/54.195.217.107/tcp/18189", - "NextNet 2": "0cff11dff44458bfea3e39444d440e54260746ff2a5ce6a6c3f7355decff2167::/onion3/cmdlunlzessyz7snnat6ktxwmcl7bubvfzneb7ljva4w4izcmlqyc2id:18141", - "NextNet 3": "2caf4be53523ecf2b71ca23db33f7df590fd41792b2d14c2bb0bbebe1b2c4b52::/ip4/34.254.114.227/tcp/18189", - "NextNet 4": "2caf4be53523ecf2b71ca23db33f7df590fd41792b2d14c2bb0bbebe1b2c4b52::/onion3/wi2teiwyyq33jwblcuy7anmja6d7iyuciyrzbwumvldeiw73d6ce5ryd:18141", - "NextNet 5": "4c236de788e803ef9615f72a4d973cf3f8a9b83c9d2fb176cbaf65c1b0442572::/ip4/52.210.35.123/tcp/18189", - "NextNet 6": "4c236de788e803ef9615f72a4d973cf3f8a9b83c9d2fb176cbaf65c1b0442572::/onion3/7gwfakr7ko5uo3fl3yz3fsjc7elccbzter5botggodrmmwi2exm3vbid:18141" - ] + isRecommended: true, + dnsPeer: "seeds.nextnet.tari.com", + blockExplorerURL: URL(string: "https://explore-nextnet.tari.com") ) } - private static func makeNetwork(name: String, presentedName: String, isMainNet: Bool, rawBaseNodes: [String: String]) -> Self { - let baseNodes = rawBaseNodes.compactMap { try? BaseNode(name: $0, peer: $1) } + var fullPresentedName: String { + guard isRecommended else { return presentedName } + return "\(presentedName) (\(localized("common.recommended")))" + } + + var isBlockExplorerAvailable: Bool { blockExplorerURL != nil } + + func blockExplorerKernelURL(nounce: String, signature: String) -> URL? { + if #available(iOS 16.0, *) { + return blockExplorerURL? + .appending(path: "kernel_search") + .appending(queryItems: [ + URLQueryItem(name: "nonces", value: nounce), + URLQueryItem(name: "signatures", value: signature) + ]) + } else { + guard let rawURL = blockExplorerURL?.absoluteString.appending("/kernel_search?nonces=\(nounce)&signatures=\(signature)") else { return nil } + return URL(string: rawURL) + } + } + + private static func makeNetwork(name: String, presentedName: String, isMainNet: Bool, isRecommended: Bool, dnsPeer: String, blockExplorerURL: URL?) -> Self { let currencySymbol = isMainNet ? "XTR" : "tXTR" - return Self(name: name, presentedName: presentedName, tickerSymbol: currencySymbol, baseNodes: baseNodes) + return Self(name: name, presentedName: presentedName, tickerSymbol: currencySymbol, isRecommended: isRecommended, dnsPeer: dnsPeer, blockExplorerURL: blockExplorerURL) } } diff --git a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/TariSettings.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/TariSettings.swift index 092e459e..53366a98 100644 --- a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/TariSettings.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/TariSettings.swift @@ -75,12 +75,8 @@ struct TariSettings { let privacyPolicyUrl = "https://www.tari.com/privacy_policy/" let storeUrl = "https://store.tarilabs.com/" let tariLabsUniversityUrl = "https://tlu.tarilabs.com/" - let blockExplorerUrl = "https://explore-esme.tari.com/" - let blockExplorerKernelUrl = "https://explore-esme.tari.com/kernel/" let torBridgesUrl = "https://bridges.torproject.org/bridges" - let isBlockExplorerAvaiable: Bool = false - var pushServerApiKey: String? var sentryPublicDSN: String? static var appleTeamID: String? diff --git a/MobileWallet/Screens/AdvancedSettings/AddBaseNode/AddBaseNodeView.swift b/MobileWallet/Screens/AdvancedSettings/AddBaseNode/AddBaseNodeView.swift deleted file mode 100644 index 3928a354..00000000 --- a/MobileWallet/Screens/AdvancedSettings/AddBaseNode/AddBaseNodeView.swift +++ /dev/null @@ -1,199 +0,0 @@ -// AddBaseNodeView.swift - -/* - Package MobileWallet - Created by Adrian Truszczynski on 19/07/2021 - Using Swift 5.0 - Running on macOS 12.0 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import UIKit - -final class AddBaseNodeView: DynamicThemeView { - - // MARK: - Subviews - - private let nameTitleLabel: UILabel = { - let view = UILabel() - view.text = localized("add_base_node.section.name") - view.font = Theme.shared.fonts.tableViewSection - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - private let nameTextField: RoundedInputField = { - let view = RoundedInputField() - view.font = Theme.shared.fonts.textField - view.returnKeyType = .next - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - private let peerTitleLabel: UILabel = { - let view = UILabel() - view.text = localized("add_base_node.section.peer") - view.font = Theme.shared.fonts.tableViewSection - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - private let peerTextView: RoundedTextView = { - let view = RoundedTextView() - view.font = Theme.shared.fonts.textField - view.returnKeyType = .done - view.autocorrectionType = .no - view.autocapitalizationType = .none - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - override func update(theme: ColorTheme) { - super.update(theme: theme) - backgroundColor = theme.backgrounds.secondary - nameTitleLabel.textColor = theme.text.heading - peerTitleLabel.textColor = theme.text.heading - } - - private let saveButton: ActionButton = { - let view = ActionButton() - view.setTitle(localized("add_base_node.button.save"), for: .normal) - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - private let contentStackView: UIStackView = { - let view = UIStackView() - view.axis = .vertical - view.spacing = 12.0 - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - private let scrollView: ContentScrollView = { - let view = ContentScrollView() - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - // MARK: - Properties - - var name: String? { nameTextField.text } - var peer: String? { peerTextView.text } - var onTapOnSaveButton: (() -> Void)? - - private var scrollViewBottomConstraint: NSLayoutConstraint? - - // MARK: - Initializers - - override init() { - super.init() - setupConstraints() - setupFeedbacks() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupConstraints() { - - peerTitleLabel.setContentHuggingPriority(.required, for: .vertical) - - addSubview(scrollView) - scrollView.contentView.addSubview(contentStackView) - [nameTitleLabel, nameTextField, peerTitleLabel, peerTextView, saveButton].forEach(contentStackView.addArrangedSubview) - - let scrollViewBottomConstraint = scrollView.bottomAnchor.constraint(equalTo: bottomAnchor) - self.scrollViewBottomConstraint = scrollViewBottomConstraint - - let constraints = [ - scrollView.topAnchor.constraint(equalTo: topAnchor), - scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), - scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), - scrollViewBottomConstraint, - contentStackView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor, constant: 12.0), - contentStackView.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor, constant: 12.0), - contentStackView.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor, constant: -12.0), - contentStackView.bottomAnchor.constraint(equalTo: scrollView.contentView.bottomAnchor, constant: -12.0), - peerTextView.heightAnchor.constraint(equalToConstant: 100.0) - ] - - NSLayoutConstraint.activate(constraints) - } - - private func setupFeedbacks() { - saveButton.addTarget(self, action: #selector(onTapOnSaveButtonAction), for: .touchUpInside) - nameTextField.delegate = self - peerTextView.delegate = self - } - - // MARK: - Actions - - func update(bottomMargin: CGFloat, animationTime: TimeInterval) { - scrollViewBottomConstraint?.constant = -bottomMargin - setNeedsLayout() - UIView.animate(withDuration: animationTime, delay: 0.0, options: [.curveEaseInOut]) { - self.layoutIfNeeded() - } - } - - func focusOnNameTextField() { - nameTextField.becomeFirstResponder() - } - - // MARK: - Action targets - - @objc private func onTapOnSaveButtonAction() { - onTapOnSaveButton?() - } -} - -extension AddBaseNodeView: UITextFieldDelegate { - - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - peerTextView.becomeFirstResponder() - return false - } -} - -extension AddBaseNodeView: UITextViewDelegate { - - func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - guard text == "\n" else { return true } - textView.resignFirstResponder() - return false - } -} diff --git a/MobileWallet/Screens/AdvancedSettings/AddBaseNode/AddBaseNodeViewController.swift b/MobileWallet/Screens/AdvancedSettings/AddBaseNode/AddBaseNodeViewController.swift deleted file mode 100644 index 8bb6dbba..00000000 --- a/MobileWallet/Screens/AdvancedSettings/AddBaseNode/AddBaseNodeViewController.swift +++ /dev/null @@ -1,137 +0,0 @@ -// AddBaseNodeViewController.swift - -/* - Package MobileWallet - Created by Adrian Truszczynski on 19/07/2021 - Using Swift 5.0 - Running on macOS 12.0 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import UIKit -import Combine - -final class AddBaseNodeViewController: SettingsParentViewController { - - // MARK: - Properties - - private let mainView = AddBaseNodeView() - private let model = AddBaseNodeModel() - private var cancelables: Set = [] - - // MARK: - View Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - setupFeedbacks() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - mainView.focusOnNameTextField() - } - - // MARK: - Setups - - override func setupViews() { - super.setupViews() - - view.addSubview(mainView) - mainView.translatesAutoresizingMaskIntoConstraints = false - - let constraints = [ - mainView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), - mainView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - mainView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - mainView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ] - - NSLayoutConstraint.activate(constraints) - } - - override func setupNavigationBar() { - super.setupNavigationBar() - navigationBar.title = localized("add_base_node.title") - } - - private func setupFeedbacks() { - - mainView.onTapOnSaveButton = { [weak self] in - self?.model.viewModel.name = self?.mainView.name ?? "" - self?.model.viewModel.peer = self?.mainView.peer ?? "" - self?.model.saveNode() - } - - model.viewModel.$isFinished - .sink { [weak self] in - guard $0 else { return } - self?.navigationController?.popViewController(animated: true) - } - .store(in: &cancelables) - - model.viewModel.$errorMessage - .sink { [weak self] in - guard let errorMessage = $0 else { return } - self?.show(errorMessage: errorMessage) - } - .store(in: &cancelables) - - NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification) - .sink { [weak self] in - - guard let keyboardFrame = $0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, - let animationTime = $0.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval - else { - return - } - - self?.mainView.update(bottomMargin: keyboardFrame.height, animationTime: animationTime) - } - .store(in: &cancelables) - - NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification) - .sink { [weak self] in - guard let animationTime = $0.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else { return } - self?.mainView.update(bottomMargin: 0.0, animationTime: animationTime) - } - .store(in: &cancelables) - } - - // MARK: - Actions - - private func show(errorMessage: String) { - let controller = UIAlertController(title: localized("add_base_node.error.title"), message: errorMessage, preferredStyle: .alert) - controller.addAction(UIAlertAction(title: localized("add_base_node.error.button"), style: .default, handler: nil)) - present(controller, animated: true) - } -} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesView.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesView.swift index a4ac62d6..541a90fa 100644 --- a/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesView.swift +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesView.swift @@ -130,7 +130,7 @@ final class CustomTorBridgesView: BaseNavigationContentView { snapshot.appendItems([.requestBridges], toSection: 1) snapshot.appendItems([.scanQRCode, .uploadQRCode], toSection: 2) - dataSource?.apply(snapshot: snapshot) + dataSource?.applySnapshotUsingReloadData(snapshot) } // MARK: - Updates diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesViewController.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesViewController.swift index 1c67da2c..13b990ab 100644 --- a/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesViewController.swift +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesViewController.swift @@ -41,12 +41,11 @@ import UIKit import Combine -final class CustomTorBridgesViewController: UIViewController { +final class CustomTorBridgesViewController: SecureViewController { // MARK: - Properties private let model: CustomTorBridgesModel - private let mainView = CustomTorBridgesView() private let imageDetector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) private var cancellables = Set() @@ -64,10 +63,6 @@ final class CustomTorBridgesViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesView.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesView.swift index 964c494d..8d5960a4 100644 --- a/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesView.swift +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesView.swift @@ -137,7 +137,7 @@ final class TorBridgesView: BaseNavigationContentView { snapshot.appendSections([0]) snapshot.appendItems(items) - dataSource?.apply(snapshot: snapshot) + dataSource?.applySnapshotUsingReloadData(snapshot) } // MARK: - Actions diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesViewController.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesViewController.swift index 4006614b..f09f7998 100644 --- a/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesViewController.swift +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesViewController.swift @@ -41,13 +41,11 @@ import UIKit import Combine -final class TorBridgesViewController: UIViewController { +final class TorBridgesViewController: SecureViewController { // MARK: - Properties private let model: TorBridgesModel - private let mainView = TorBridgesView() - private var cancellables = Set() // MARK: - Initialisers @@ -63,10 +61,6 @@ final class TorBridgesViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsConstructor.swift b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsConstructor.swift new file mode 100644 index 00000000..7c7005d0 --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsConstructor.swift @@ -0,0 +1,47 @@ +// ScreenRecordingSettingsConstructor.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 06/03/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum ScreenRecordingSettingsConstructor { + + static func buildScene() -> ScreenRecordingSettingsViewController { + let model = ScreenRecordingSettingsModel() + return ScreenRecordingSettingsViewController(model: model) + } +} diff --git a/MobileWallet/Common/Extensions/UITableViewDiffableDataSource+Update.swift b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsModel.swift similarity index 74% rename from MobileWallet/Common/Extensions/UITableViewDiffableDataSource+Update.swift rename to MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsModel.swift index d02fb068..50051e10 100644 --- a/MobileWallet/Common/Extensions/UITableViewDiffableDataSource+Update.swift +++ b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsModel.swift @@ -1,10 +1,10 @@ -// UITableViewDiffableDataSource+Update.swift +// ScreenRecordingSettingsModel.swift /* Package MobileWallet - Created by Adrian Truszczynski on 19/10/2022 + Created by Adrian Truszczyński on 23/02/2024 Using Swift 5.0 - Running on macOS 12.6 + Running on macOS 14.2 Copyright 2019 The Tari Project @@ -38,15 +38,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit +import Combine -extension UITableViewDiffableDataSource { +final class ScreenRecordingSettingsModel { - func apply(snapshot: NSDiffableDataSourceSnapshot, completion: (() -> Void)? = nil) { - if #available(iOS 15.0, *) { - applySnapshotUsingReloadData(snapshot, completion: completion) - } else { - apply(snapshot, animatingDifferences: false, completion: completion) - } + // MARK: - View Model + + @Published var areScreenshotsEnabled: Bool = !SecurityManager.shared.areScreenshotsDisabled { + didSet { SecurityManager.shared.areScreenshotsDisabled = !areScreenshotsEnabled } } } diff --git a/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsView.swift b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsView.swift new file mode 100644 index 00000000..11062aa3 --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsView.swift @@ -0,0 +1,148 @@ +// ScreenRecordingSettingsView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 23/02/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import TariCommon +import Combine + +final class ScreenRecordingSettingsView: BaseNavigationContentView { + + // MARK: - Subviews + + @View private var descriptionLabel: UILabel = { + let view = UILabel() + view.text = localized("screen_recording.label.description") + view.font = .Avenir.medium.withSize(14.0) + view.numberOfLines = 0 + return view + }() + + @View private var tableView: UITableView = { + let view = UITableView() + view.backgroundColor = .clear + view.register(type: SwitchMenuCell.self) + return view + }() + + // MARK: - Properties + + var onSwitchValueChange: ((Bool) -> Void)? + + private let dynamicModel = SwitchMenuCell.DynamicModel() + private var dataSource: UITableViewDiffableDataSource? + private var cancellables = Set() + + // MARK: - Initialisers + + override init() { + super.init() + setupViews() + setupConstraints() + setupCallbacks() + setupCells() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupViews() { + navigationBar.title = localized("screen_recording.title") + } + + private func setupConstraints() { + + [descriptionLabel, tableView].forEach(addSubview) + + let constraints = [ + descriptionLabel.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 33.0), + descriptionLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + descriptionLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), + tableView.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 15.0), + tableView.leadingAnchor.constraint(equalTo: leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + + dataSource = UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, index in + let cell = tableView.dequeueReusableCell(type: SwitchMenuCell.self, indexPath: indexPath) + cell.viewModel = SwitchMenuCell.ViewModel( + id: index, + title: localized("screen_recording.label.confirmation"), + isArrowVisible: false, + isDestructive: false + ) + cell.dynamicModel = self?.dynamicModel + return cell + } + + dynamicModel.$switchValue + .removeDuplicates() + .sink { [weak self] in self?.onSwitchValueChange?($0) } + .store(in: &cancellables) + + tableView.dataSource = dataSource + } + + private func setupCells() { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([0]) + snapshot.appendItems([0]) + dataSource?.applySnapshotUsingReloadData(snapshot) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.secondary + descriptionLabel.textColor = theme.text.body + } + + func update(switchValue: Bool) { + dynamicModel.switchValue = switchValue + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsViewController.swift b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsViewController.swift new file mode 100644 index 00000000..d91d1317 --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsViewController.swift @@ -0,0 +1,104 @@ +// ScreenRecordingSettingsViewController.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 23/02/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +final class ScreenRecordingSettingsViewController: SecureViewController { + + // MARK: - Properties + + private let model: ScreenRecordingSettingsModel + private var cancellables = Set() + + // MARK: - Initialisers + + init(model: ScreenRecordingSettingsModel) { + self.model = model + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupCallbacks() + } + + // MARK: - Settings + + private func setupCallbacks() { + + model.$areScreenshotsEnabled + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.mainView.update(switchValue: $0) } + .store(in: &cancellables) + + mainView.onSwitchValueChange = { [weak self] in + + guard self?.model.areScreenshotsEnabled != $0 else { return } + + if $0 { + self?.showConfirmationDialog() + } else { + self?.model.areScreenshotsEnabled = false + } + } + } + + private func showConfirmationDialog() { + + let model = PopUpDialogModel( + title: localized("screen_recording.pop_up.confirmation.title"), + message: localized("screen_recording.pop_up.confirmation.message"), + buttons: [ + PopUpDialogButtonModel(title: localized("common.confirm"), type: .normal, callback: { [weak self] in self?.model.areScreenshotsEnabled = true }), + PopUpDialogButtonModel(title: localized("common.cancel"), type: .text, callback: { [weak self] in self?.mainView.update(switchValue: false) }) + ], + hapticType: .none + ) + + PopUpPresenter.showPopUp(model: model) + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Select Network/SelectNetworkModel.swift b/MobileWallet/Screens/AdvancedSettings/Select Network/SelectNetworkModel.swift index 4efb13ae..25d683ef 100644 --- a/MobileWallet/Screens/AdvancedSettings/Select Network/SelectNetworkModel.swift +++ b/MobileWallet/Screens/AdvancedSettings/Select Network/SelectNetworkModel.swift @@ -61,7 +61,7 @@ final class SelectNetworkModel { viewModel.selectedIndex = networks.firstIndex { $0.name == NetworkManager.shared.selectedNetwork.name } viewModel.networkModels = networks .enumerated() - .map { NetworkModel(networkName: $1.presentedName, isSelected: $0 == viewModel.selectedIndex) } + .map { NetworkModel(networkName: $1.fullPresentedName, isSelected: $0 == viewModel.selectedIndex) } } func update(selectedIndex: Int) { diff --git a/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/SelectBaseNodeModel.swift b/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/SelectBaseNodeModel.swift index 155826aa..05a7d24c 100644 --- a/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/SelectBaseNodeModel.swift +++ b/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/SelectBaseNodeModel.swift @@ -54,20 +54,20 @@ final class SelectBaseNodeModel { // MARK: - View Model - @Published var nodes: [NodeModel] = [] - @Published var errorMessaage: MessageModel? + @Published private(set) var nodes: [NodeModel] = [] + @Published private(set) var errorMessaage: MessageModel? // MARK: - Properties - private var predefinedNodes: [BaseNode] { NetworkManager.shared.selectedNetwork.baseNodes } - private var avaiableNodes: [BaseNode] { NetworkManager.shared.selectedNetwork.allBaseNodes } + private var predefinedNodes: [BaseNode] { NetworkManager.shared.defaultBaseNodes } + private var avaiableNodes: [BaseNode] { NetworkManager.shared.allBaseNodes } private var selectedNodeIndex: Int? // MARK: - Setups private func updateSelectedNodeIndex() { - let selectedNode = NetworkManager.shared.selectedNetwork.selectedBaseNode - selectedNodeIndex = avaiableNodes.firstIndex { $0 == selectedNode } + let selectedBaseNode = NetworkManager.shared.selectedBaseNode + selectedNodeIndex = avaiableNodes.firstIndex { $0.peer == selectedBaseNode?.peer } } private func updateViewModelNodes() { @@ -103,11 +103,26 @@ final class SelectBaseNodeModel { } } + func addNode(name: String, hex: String, address: String) { + + guard !name.isEmpty else { + errorMessaage = MessageModel(title: localized("add_base_node.error.title"), message: localized("add_base_node.error.no_name"), type: .error) + return + } + + do { + try Tari.shared.connection.addBaseNode(name: name, hex: hex, address: address) + refreshData() + } catch { + errorMessaage = MessageModel(title: localized("add_base_node.error.title"), message: localized("add_base_node.error.invalid_peer"), type: .error) + } + } + func deleteNode(index: Int) { guard avaiableNodes.count >= index else { return } let node = avaiableNodes[index] - guard let index = NetworkManager.shared.selectedNetwork.customBaseNodes.firstIndex(of: node) else { return } - NetworkManager.shared.selectedNetwork.customBaseNodes.remove(at: index) + guard let index = NetworkManager.shared.customBaseNodes.firstIndex(of: node) else { return } + NetworkManager.shared.customBaseNodes.remove(at: index) refreshData() } } diff --git a/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/SelectBaseNodeViewController.swift b/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/SelectBaseNodeViewController.swift index a1653220..d86c7d36 100644 --- a/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/SelectBaseNodeViewController.swift +++ b/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/SelectBaseNodeViewController.swift @@ -54,7 +54,6 @@ final class SelectBaseNodeViewController: SettingsParentTableViewController { override func viewDidLoad() { super.viewDidLoad() setupCallbacks() - } override func viewWillAppear(_ animated: Bool) { @@ -121,8 +120,9 @@ final class SelectBaseNodeViewController: SettingsParentTableViewController { // MARK: - Actions private func presentAddBaseNodeScreen() { - let controller = AddBaseNodeViewController() - navigationController?.pushViewController(controller, animated: true) + FormOverlayPresenter.showAddBaseNodeForm(presenter: self) { [weak self] name, hex, address in + self?.model.addNode(name: name, hex: hex, address: address) + } } } diff --git a/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/Views/SelectBaseNodeCell.swift b/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/Views/SelectBaseNodeCell.swift index 50e3cdea..050c6a57 100644 --- a/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/Views/SelectBaseNodeCell.swift +++ b/MobileWallet/Screens/AdvancedSettings/SelectBaseNode/Views/SelectBaseNodeCell.swift @@ -38,7 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit +import TariCommon final class SelectBaseNodeCell: DynamicThemeCell { @@ -48,40 +48,36 @@ final class SelectBaseNodeCell: DynamicThemeCell { // MARK: - Subviews - private let titleLabel: UILabel = { + @View private var titleLabel: UILabel = { let view = UILabel() - view.font = Theme.shared.fonts.systemTableViewCell - view.translatesAutoresizingMaskIntoConstraints = false + view.font = .Avenir.medium.withSize(15.0) return view }() - private let subtitleLabel: UILabel = { + @View private var subtitleLabel: UILabel = { let view = UILabel() - view.font = Theme.shared.fonts.systemTableViewCellMarkDescriptionSmall - view.translatesAutoresizingMaskIntoConstraints = false + view.font = .Avenir.medium.withSize(11.0) + view.numberOfLines = 0 return view }() - private let labelsStackView: UIStackView = { + @View private var labelsStackView: UIStackView = { let view = UIStackView() view.axis = .vertical view.spacing = 4.0 - view.translatesAutoresizingMaskIntoConstraints = false return view }() - private let tickView: UIImageView = { + @View private var tickView: UIImageView = { let view = UIImageView(image: Theme.shared.images.scheduledIcon) - view.translatesAutoresizingMaskIntoConstraints = false view.contentMode = .scaleAspectFit return view }() - private let deleteButton: UIButton = { + @View private var deleteButton: UIButton = { let view = UIButton() - view.titleLabel?.font = UIFont.Avenir.heavy.withSize(14.0) + view.titleLabel?.font = .Avenir.heavy.withSize(14.0) view.setTitle(localized("select_base_node.cell.delete"), for: .normal) - view.translatesAutoresizingMaskIntoConstraints = false return view }() @@ -114,21 +110,19 @@ final class SelectBaseNodeCell: DynamicThemeCell { [labelsStackView, deleteButton, tickView].forEach(contentView.addSubview) [titleLabel, subtitleLabel].forEach(labelsStackView.addArrangedSubview) - let heightConstraint = contentView.heightAnchor.constraint(equalToConstant: 65.0) - heightConstraint.priority = .sceneSizeStayPut - let constraints = [ - labelsStackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + labelsStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8.0), labelsStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), - deleteButton.topAnchor.constraint(equalTo: topAnchor), + labelsStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8.0), + deleteButton.topAnchor.constraint(equalTo: contentView.topAnchor), deleteButton.leadingAnchor.constraint(equalTo: labelsStackView.trailingAnchor), - deleteButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0), + deleteButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12.0), deleteButton.bottomAnchor.constraint(equalTo: bottomAnchor), - tickView.centerYAnchor.constraint(equalTo: centerYAnchor), tickView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0), + tickView.centerYAnchor.constraint(equalTo: centerYAnchor), tickView.widthAnchor.constraint(equalToConstant: 21.0), tickView.heightAnchor.constraint(equalToConstant: 21.0), - heightConstraint + contentView.heightAnchor.constraint(greaterThanOrEqualToConstant: 65.0) ] NSLayoutConstraint.activate(constraints) diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashView.swift b/MobileWallet/Screens/AppEntry/Splash/SplashView.swift index 0d7135f7..d410d4ef 100644 --- a/MobileWallet/Screens/AppEntry/Splash/SplashView.swift +++ b/MobileWallet/Screens/AppEntry/Splash/SplashView.swift @@ -64,7 +64,7 @@ final class SplashView: UIView { view.text = localized("splash.title") view.font = .Avenir.black.withSize(30.0) view.interlineSpacing(spacingValue: 0) - view.textColor = .static.white + view.textColor = .Static.white view.textAlignment = .center view.numberOfLines = 2 view.adjustsFontSizeToFitWidth = true @@ -91,14 +91,14 @@ final class SplashView: UIView { @View private var tariIconView: UIImageView = { let view = UIImageView() view.image = Theme.shared.images.currencySymbol - view.tintColor = .static.white + view.tintColor = .Static.white return view }() @View private var versionLabel: UILabel = { let view = UILabel() view.font = .Avenir.heavy.withSize(9.0) - view.textColor = .static.mediumGrey + view.textColor = .Static.mediumGrey view.textAlignment = .center return view }() @@ -148,7 +148,7 @@ final class SplashView: UIView { // MARK: - Setups private func setupViews() { - backgroundColor = .static.black + backgroundColor = .Static.black } private func setupConstraints() { @@ -216,7 +216,7 @@ final class SplashView: UIView { private func setupDisclamerView() { - guard let textColor = UIColor.static.mediumGrey else { return } + let textColor = UIColor.Static.mediumGrey disclaimerTextView.linkTextAttributes = [ NSAttributedString.Key.foregroundColor: textColor, diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift index dca2e043..c5f3662b 100644 --- a/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift +++ b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift @@ -91,7 +91,7 @@ final class SplashViewModel { private func setupCallbacks() { NetworkManager.shared.$selectedNetwork - .map(\.presentedName) + .map(\.fullPresentedName) .sink { [weak self] in self?.networkName = $0 } .store(in: &cancellables) @@ -109,7 +109,7 @@ final class SplashViewModel { private func setupData() { appVersion = AppVersionFormatter.version - allNetworkNames = TariNetwork.all.map { $0.presentedName } + allNetworkNames = TariNetwork.all.map { $0.fullPresentedName } } // MARK: - View Model Actions @@ -141,12 +141,13 @@ final class SplashViewModel { } private func openWallet() { - Task { do { let statusRepresentation = status?.statusRepresentation ?? .content status = StatusModel(status: .working, statusRepresentation: statusRepresentation) + let isMigrationPerformed = await MigrationManager.performPeerDBMigration() + guard await validateWallet() else { self.deleteWallet() return @@ -156,6 +157,10 @@ final class SplashViewModel { isWalletConnected = false status = StatusModel(status: .success, statusRepresentation: statusRepresentation) + + guard isMigrationPerformed else { return } + let randomBaseNode = try NetworkManager.shared.randomBaseNode() + try Tari.shared.connection.select(baseNode: randomBaseNode) } catch { self.handle(error: error) } diff --git a/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift b/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift index 946e1679..0116666e 100644 --- a/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift +++ b/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift @@ -233,7 +233,7 @@ final class WalletCreationViewController: DynamicThemeViewController { override func update(theme: ColorTheme) { super.update(theme: theme) - view.backgroundColor = theme.backgrounds.secondary + mainView.backgroundColor = theme.backgrounds.secondary firstLabel.textColor = theme.text.heading secondLabel.textColor = theme.text.heading thirdLabel.textColor = theme.text.heading @@ -491,7 +491,7 @@ extension WalletCreationViewController { stackView.distribution = .fill stackView.alignment = .center - view.addSubview(stackView) + mainView.addSubview(stackView) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true @@ -552,7 +552,7 @@ extension WalletCreationViewController { loadingCircle.animation = Animation.named(.pendingCircleAnimation) loadingCircle.alpha = 0.0 - view.addSubview(loadingCircle) + mainView.addSubview(loadingCircle) loadingCircle.translatesAutoresizingMaskIntoConstraints = false loadingCircle.widthAnchor.constraint(equalToConstant: 45).isActive = true loadingCircle.heightAnchor.constraint(equalToConstant: 45).isActive = true @@ -592,7 +592,7 @@ extension WalletCreationViewController { private func setupContinueButton() { continueButton.addTarget(self, action: #selector(onNavigateNext), for: .touchUpInside) continueButton.alpha = 0.0 - view.addSubview(continueButton) + mainView.addSubview(continueButton) continueButton.translatesAutoresizingMaskIntoConstraints = false continueButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, @@ -636,7 +636,7 @@ extension WalletCreationViewController { private func setupTapToSeeFullEmojiButton() { tapToSeeButtonContainer.backgroundColor = .clear tapToSeeButtonContainer.alpha = 0.0 - view.addSubview(tapToSeeButtonContainer) + mainView.addSubview(tapToSeeButtonContainer) tapToSeeButtonContainer.translatesAutoresizingMaskIntoConstraints = false tapToSeeButtonContainer.bottomAnchor.constraint(equalTo: emojiIdView.topAnchor, constant: 3).isActive = true diff --git a/MobileWallet/Screens/Contact Book/Add Contact/AddContactViewController.swift b/MobileWallet/Screens/Contact Book/Add Contact/AddContactViewController.swift index c96ab6a9..ddba8025 100644 --- a/MobileWallet/Screens/Contact Book/Add Contact/AddContactViewController.swift +++ b/MobileWallet/Screens/Contact Book/Add Contact/AddContactViewController.swift @@ -41,7 +41,7 @@ import UIKit import Combine -final class AddContactViewController: UIViewController { +final class AddContactViewController: SecureViewController { enum NavigationActionType { case moveToDetails @@ -51,7 +51,6 @@ final class AddContactViewController: UIViewController { // MARK: - Properties private let model: AddContactModel - private let mainView = AddContactView() private let navigationActionType: NavigationActionType private var cancellables = Set() @@ -70,10 +69,6 @@ final class AddContactViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift index 92cdedd2..964610aa 100644 --- a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift +++ b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift @@ -78,7 +78,7 @@ final class ContactDetailsView: BaseNavigationContentView { @View private var yatButton: BaseButton = { let view = BaseButton() - view.setImage(Theme.shared.images.yatButtonOn, for: .disabled) + view.setImage(.Icons.Yat.buttonOn, for: .disabled) return view }() @@ -254,13 +254,13 @@ final class ContactDetailsView: BaseNavigationContentView { yatLabel.isHidden = true yatButton.isHidden = false yatButton.isEnabled = true - yatButton.setImage(Theme.shared.images.yatButtonOn, for: .normal) + yatButton.setImage(.Icons.Yat.buttonOn, for: .normal) case .yatVisible: emojiIdView.isHidden = true yatLabel.isHidden = false yatButton.isHidden = false yatButton.isEnabled = true - yatButton.setImage(Theme.shared.images.yatButtonOff, for: .normal) + yatButton.setImage(.Icons.Yat.buttonOff, for: .normal) } } diff --git a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift index 9dafc33f..30717930 100644 --- a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift +++ b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift @@ -41,11 +41,10 @@ import UIKit import Combine -final class ContactDetailsViewController: UIViewController { +final class ContactDetailsViewController: SecureViewController { // MARK: - Properties - private let mainView = ContactDetailsView() private let model: ContactDetailsModel private var needUpdate = false @@ -64,10 +63,6 @@ final class ContactDetailsViewController: UIViewController { // MARK: - View lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Contact Book/Contact Details/Edit Form/ContactBookFormView.swift b/MobileWallet/Screens/Contact Book/Contact Details/Edit Form/ContactBookFormView.swift index 7982e5bf..0c315857 100644 --- a/MobileWallet/Screens/Contact Book/Contact Details/Edit Form/ContactBookFormView.swift +++ b/MobileWallet/Screens/Contact Book/Contact Details/Edit Form/ContactBookFormView.swift @@ -66,6 +66,8 @@ final class ContactBookFormView: DynamicThemeView, FormShowable { return view }() + @View private var secureContentView = SecureWrapperView() + // MARK: - Properties private var cancellables = Set() @@ -78,9 +80,9 @@ final class ContactBookFormView: DynamicThemeView, FormShowable { // MARK: - Initialisers - init(title: String?, textFieldsModels: [TextFieldViewModel]) { + init(title: String?, rightButtonTitle: String?, textFieldsModels: [TextFieldViewModel]) { super.init() - setupTitleBar(title: title) + setupTitleBar(title: title, rightButtonTitle: rightButtonTitle) setupConstraints() update(textFieldsModels: textFieldsModels) } @@ -91,16 +93,21 @@ final class ContactBookFormView: DynamicThemeView, FormShowable { // MARK: - Setups - private func setupTitleBar(title: String?) { + private func setupTitleBar(title: String?, rightButtonTitle: String?) { titleBar.title = title - titleBar.update(rightButton: NavigationBar.ButtonModel(title: localized("common.done"), callback: { [weak self] in self?.onCloseAction?() })) + titleBar.update(rightButton: NavigationBar.ButtonModel(title: rightButtonTitle, callback: { [weak self] in self?.onCloseAction?() })) } private func setupConstraints() { - [titleBar, stackView].forEach(addSubview) + addSubview(secureContentView) + [titleBar, stackView].forEach(secureContentView.view.addSubview) let constraints = [ + secureContentView.topAnchor.constraint(equalTo: topAnchor), + secureContentView.leadingAnchor.constraint(equalTo: leadingAnchor), + secureContentView.trailingAnchor.constraint(equalTo: trailingAnchor), + secureContentView.bottomAnchor.constraint(equalTo: bottomAnchor), titleBar.topAnchor.constraint(equalTo: topAnchor), titleBar.leadingAnchor.constraint(equalTo: leadingAnchor), titleBar.trailingAnchor.constraint(equalTo: trailingAnchor), @@ -117,7 +124,7 @@ final class ContactBookFormView: DynamicThemeView, FormShowable { override func update(theme: ColorTheme) { super.update(theme: theme) - backgroundColor = theme.backgrounds.primary + secureContentView.view.backgroundColor = theme.backgrounds.primary } private func update(textFieldsModels: [TextFieldViewModel]) { diff --git a/MobileWallet/Screens/Contact Book/Contact Transactions List/ContactTransactionListView.swift b/MobileWallet/Screens/Contact Book/Contact Transactions List/ContactTransactionListView.swift index 5d9d9309..66cf931d 100644 --- a/MobileWallet/Screens/Contact Book/Contact Transactions List/ContactTransactionListView.swift +++ b/MobileWallet/Screens/Contact Book/Contact Transactions List/ContactTransactionListView.swift @@ -141,7 +141,7 @@ final class ContactTransactionListView: BaseNavigationContentView { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([0]) snapshot.appendItems(models) - dataSource?.apply(snapshot: snapshot) + dataSource?.applySnapshotUsingReloadData(snapshot) } // MARK: - Layout diff --git a/MobileWallet/Screens/Contact Book/Contact Transactions List/ContactTransactionListViewController.swift b/MobileWallet/Screens/Contact Book/Contact Transactions List/ContactTransactionListViewController.swift index ff5cc8fd..07082095 100644 --- a/MobileWallet/Screens/Contact Book/Contact Transactions List/ContactTransactionListViewController.swift +++ b/MobileWallet/Screens/Contact Book/Contact Transactions List/ContactTransactionListViewController.swift @@ -41,12 +41,11 @@ import UIKit import Combine -final class ContactTransactionListViewController: UIViewController { +final class ContactTransactionListViewController: SecureViewController { // MARK: - Properties private let model: ContactTransactionListModel - private let mainView = ContactTransactionListView() private var cancellables = Set() @@ -63,10 +62,6 @@ final class ContactTransactionListViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Contact Book/Contact Transactions List/Views/ContactTransactionListPlaceholder.swift b/MobileWallet/Screens/Contact Book/Contact Transactions List/Views/ContactTransactionListPlaceholder.swift index f387593c..e6d675f3 100644 --- a/MobileWallet/Screens/Contact Book/Contact Transactions List/Views/ContactTransactionListPlaceholder.swift +++ b/MobileWallet/Screens/Contact Book/Contact Transactions List/Views/ContactTransactionListPlaceholder.swift @@ -46,7 +46,7 @@ final class ContactTransactionListPlaceholder: DynamicThemeView { @View private var imageView: PaintBackgroundImageView = { let view = PaintBackgroundImageView() - view.image = .contactBook.placeholders.transactionList + view.image = .Images.ContactBook.Placeholders.transactionList return view }() @@ -148,7 +148,7 @@ final class PaintBackgroundImageView: DynamicThemeView { @View private var backgroundView: UIImageView = { let view = UIImageView() - view.image = .security.onboarding.background + view.image = .Images.Security.Onboarding.background view.contentMode = .scaleAspectFit return view }() diff --git a/MobileWallet/Screens/Contact Book/Extensions/ContactType+Data.swift b/MobileWallet/Screens/Contact Book/Extensions/ContactType+Data.swift index 0cf7ff76..0f358bf6 100644 --- a/MobileWallet/Screens/Contact Book/Extensions/ContactType+Data.swift +++ b/MobileWallet/Screens/Contact Book/Extensions/ContactType+Data.swift @@ -45,11 +45,11 @@ extension ContactsManager.ContactType { var image: UIImage? { switch self { case .internalOrEmojiID: - return .icons.contactTypes.internal + return .Icons.ContactTypes.internal case .external: - return .icons.contactTypes.external + return .Icons.ContactTypes.external case .linked: - return .icons.contactTypes.linked + return .Icons.ContactTypes.linked case .empty: return nil } diff --git a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsView.swift b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsView.swift index 9092ce34..7c5d0522 100644 --- a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsView.swift +++ b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsView.swift @@ -184,7 +184,7 @@ final class LinkContactsView: BaseNavigationContentView { snapshot.appendSections([0]) snapshot.appendItems(viewModels) - dataSource?.apply(snapshot: snapshot) + dataSource?.applySnapshotUsingReloadData(snapshot) } private func update(placeholderViewModel: ContactBookListPlaceholder.ViewModel?) { diff --git a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift index e9058640..1071009c 100644 --- a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift +++ b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift @@ -42,13 +42,11 @@ import UIKit import Combine import ContactsUI -final class LinkContactsViewController: UIViewController { +final class LinkContactsViewController: SecureViewController { // MARK: - Properties let model: LinkContactsModel - let mainView = LinkContactsView() - private var cancellables = Set() // MARK: - Initialisers @@ -64,10 +62,6 @@ final class LinkContactsViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() @@ -153,7 +147,7 @@ final class LinkContactsViewController: UIViewController { } return ContactBookListPlaceholder.ViewModel( - image: .contactBook.placeholders.linkList, + image: .Images.ContactBook.Placeholders.linkList, titleComponents: titleComponents, messageComponents: messageComponents, actionButtonTitle: model.buttonTitle, diff --git a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift index f5fa3853..91e0dc11 100644 --- a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift +++ b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift @@ -167,7 +167,7 @@ final class ContactBookContactListView: DynamicThemeView { snapshot.appendItems($1.items) } - dataSource?.apply(snapshot: snapshot) + dataSource?.applySnapshotUsingReloadData(snapshot) } private func update(placeholderViewModel: ContactBookListPlaceholder.ViewModel?) { diff --git a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift index 8b722e5c..2a821c17 100644 --- a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift +++ b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift @@ -56,7 +56,7 @@ final class ContactBookListPlaceholder: DynamicThemeView { @View private var backgroundImageView: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFit - view.image = .security.onboarding.background + view.image = .Images.Security.Onboarding.background return view }() diff --git a/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift b/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift index 85374d02..8a6720fc 100644 --- a/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift +++ b/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift @@ -397,11 +397,11 @@ extension ContactBookModel.ShareType { var image: UIImage? { switch self { case .qr: - return .icons.qr + return .Icons.General.QR case .link: - return .icons.link + return .Icons.General.link case .ble: - return .icons.bluetooth + return .Icons.General.bluetooth } } diff --git a/MobileWallet/Screens/Contact Book/List/ContactBookView.swift b/MobileWallet/Screens/Contact Book/List/ContactBookView.swift index a66053d1..bb62adf0 100644 --- a/MobileWallet/Screens/Contact Book/List/ContactBookView.swift +++ b/MobileWallet/Screens/Contact Book/List/ContactBookView.swift @@ -174,8 +174,8 @@ final class ContactBookView: BaseNavigationContentView { ] } else { rightButtons = [ - NavigationBar.ButtonModel(image: .contactBook.buttons.share, callback: { [weak self] in self?.onShareModeButtonTap?() }), - NavigationBar.ButtonModel(image: .contactBook.buttons.addContact, callback: { [weak self] in self?.onAddContactButtonTap?() }) + NavigationBar.ButtonModel(image: .Images.ContactBook.Buttons.share, callback: { [weak self] in self?.onShareModeButtonTap?() }), + NavigationBar.ButtonModel(image: .Images.ContactBook.Buttons.addContact, callback: { [weak self] in self?.onAddContactButtonTap?() }) ] } diff --git a/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift b/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift index 2000cc46..91503957 100644 --- a/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift +++ b/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift @@ -41,12 +41,11 @@ import UIKit import Combine -final class ContactBookViewController: UIViewController, OverlayPresentable { +final class ContactBookViewController: SecureViewController, OverlayPresentable { // MARK: - Properties private let model: ContactBookModel - private let mainView = ContactBookView() private let pagerViewController = TariPagerViewController() private let contactsPageViewController = ContactBookContactListViewController() @@ -68,10 +67,6 @@ final class ContactBookViewController: UIViewController, OverlayPresentable { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupPages() @@ -99,7 +94,7 @@ final class ContactBookViewController: UIViewController, OverlayPresentable { ] favoritesPageViewController.placeholderViewModel = ContactBookListPlaceholder.ViewModel( - image: .contactBook.placeholders.favoritesContactsList, + image: .Images.ContactBook.Placeholders.favourites, titleComponents: [ StylizedLabel.StylizedText(text: localized("contact_book.section.favorites.placeholder.title.part1"), style: .normal), StylizedLabel.StylizedText(text: localized("contact_book.section.favorites.placeholder.title.part2.bold"), style: .bold) @@ -219,7 +214,7 @@ final class ContactBookViewController: UIViewController, OverlayPresentable { guard isPermissionGranted else { contactsPageViewController.placeholderViewModel = ContactBookListPlaceholder.ViewModel( - image: .contactBook.placeholders.contactsList, + image: .Images.ContactBook.Placeholders.list, titleComponents: [ StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.title.part1"), style: .normal), StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.title.part2.bold"), style: .bold) @@ -237,7 +232,7 @@ final class ContactBookViewController: UIViewController, OverlayPresentable { } contactsPageViewController.placeholderViewModel = ContactBookListPlaceholder.ViewModel( - image: .contactBook.placeholders.contactsList, + image: .Images.ContactBook.Placeholders.list, titleComponents: [ StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.title.part1"), style: .normal), StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.title.part2.bold"), style: .bold) @@ -319,7 +314,9 @@ final class ContactBookViewController: UIViewController, OverlayPresentable { } private func moveToSendTokensScreen(paymentInfo: PaymentInfo) { - AppRouter.presentSendTransaction(paymentInfo: paymentInfo) + Task { @MainActor in + AppRouter.presentSendTransaction(paymentInfo: paymentInfo) + } } private func moveToLinkContactsScreen(model: ContactsManager.Model) { diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlay.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlay.swift index 173ee535..5ce39689 100644 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlay.swift +++ b/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlay.swift @@ -56,6 +56,7 @@ final class RotaryMenuOverlay: UIViewController { }() private let contactID: UUID + private lazy var secureWrapperView = SecureWrapperView(mainView: mainView) // MARK: - Initialisers @@ -79,7 +80,7 @@ final class RotaryMenuOverlay: UIViewController { // MARK: - View Lifecycle override func loadView() { - view = mainView + view = secureWrapperView } override func viewDidLoad() { @@ -160,17 +161,17 @@ private extension ContactBookModel.MenuItem { private var icon: UIImage? { switch self { case .send: - return .icons.send + return .Icons.General.send case .addToFavorites: - return .icons.star.filled + return .Icons.Star.filled case .removeFromFavorites: - return .icons.star.border + return .Icons.Star.border case .link: - return .icons.link + return .Icons.General.link case .unlink: - return .icons.unlink + return .Icons.General.unlink case .details: - return .icons.profile + return .Icons.General.profile } } diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlayView.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlayView.swift index 2ea4aa74..889c83ce 100644 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlayView.swift +++ b/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlayView.swift @@ -54,18 +54,18 @@ final class RotaryMenuOverlayView: UIView { @View private var switchSideButton: RoundedButton = { let view = RoundedButton() - view.setImage(.icons.rotaryMenu.switchSide, for: .normal) - view.backgroundColor = .static.white?.withAlphaComponent(0.4) - view.tintColor = .static.black + view.setImage(.Icons.RotaryMenu.switchSide, for: .normal) + view.backgroundColor = .Static.white.withAlphaComponent(0.4) + view.tintColor = .Static.black view.alpha = 0.0 return view }() @View private var closeButton: RoundedButton = { let view = RoundedButton() - view.setImage(.icons.rotaryMenu.close, for: .normal) - view.backgroundColor = .static.white?.withAlphaComponent(0.8) - view.tintColor = .static.black + view.setImage(.Icons.RotaryMenu.close, for: .normal) + view.backgroundColor = .Static.white.withAlphaComponent(0.8) + view.tintColor = .Static.black view.alpha = 0.0 return view }() @@ -111,7 +111,7 @@ final class RotaryMenuOverlayView: UIView { // MARK: - Setups private func setupViews() { - backgroundColor = .static.popupOverlay + backgroundColor = .Static.popupOverlay } private func setupConstraints() { diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuButton.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuButton.swift index 1f94ee66..975f00e1 100644 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuButton.swift +++ b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuButton.swift @@ -60,7 +60,7 @@ final class RotaryMenuButton: BaseButton { @View private(set) var iconView: UIImageView = { let view = UIImageView() - view.tintColor = .static.white + view.tintColor = .Static.white view.contentMode = .scaleAspectFit view.isUserInteractionEnabled = false return view @@ -68,7 +68,7 @@ final class RotaryMenuButton: BaseButton { @View private var label: UILabel = { let view = UILabel() - view.textColor = .static.white + view.textColor = .Static.white view.font = .Avenir.heavy.withSize(16.0) view.numberOfLines = 0 return view diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuCircleBackgroundView.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuCircleBackgroundView.swift index 95283aa9..d8e97e78 100644 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuCircleBackgroundView.swift +++ b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuCircleBackgroundView.swift @@ -58,7 +58,7 @@ final class RotaryMenuCircleBackgroundView: UIView { @View private var firstCircleView: UIView = { let view = UIView() - view.backgroundColor = .static.white?.withAlphaComponent(0.7) + view.backgroundColor = .Static.white.withAlphaComponent(0.7) view.layer.borderWidth = 1.0 view.layer.borderColor = UIColor.white.cgColor view.alpha = 0.0 @@ -68,7 +68,7 @@ final class RotaryMenuCircleBackgroundView: UIView { @View private var secondCircleView: UIView = { let view = UIView() view.layer.borderWidth = 1.0 - view.layer.borderColor = UIColor.static.white?.withAlphaComponent(0.8).cgColor + view.layer.borderColor = UIColor.Static.white.withAlphaComponent(0.8).cgColor view.alpha = 0.0 return view }() diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuOuterCircleView.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuOuterCircleView.swift index 1c498b4d..2da5f568 100644 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuOuterCircleView.swift +++ b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuOuterCircleView.swift @@ -46,7 +46,7 @@ final class RotaryMenuOuterCircleView: UIView { private let shapeLayer: CAShapeLayer = { let layer = CAShapeLayer() - layer.strokeColor = UIColor.static.white?.withAlphaComponent(0.8).cgColor + layer.strokeColor = UIColor.Static.white.withAlphaComponent(0.8).cgColor layer.fillColor = UIColor.clear.cgColor layer.lineWidth = 1.0 layer.lineCap = .round diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactBookBluetoothCell.swift b/MobileWallet/Screens/Contact Book/List/Views/ContactBookBluetoothCell.swift index bd9cd773..3dadbca2 100644 --- a/MobileWallet/Screens/Contact Book/List/Views/ContactBookBluetoothCell.swift +++ b/MobileWallet/Screens/Contact Book/List/Views/ContactBookBluetoothCell.swift @@ -46,7 +46,7 @@ final class ContactBookBluetoothCell: DynamicThemeCell { @View private var avatarView: RoundedAvatarView = { let view = RoundedAvatarView() - view.avatar = .image(.icons.bluetooth) + view.avatar = .image(.Icons.General.bluetooth) view.imagePadding = 10.0 return view }() diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift b/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift index eef4062b..ab37f651 100644 --- a/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift +++ b/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift @@ -77,7 +77,7 @@ final class ContactBookCell: DynamicThemeCell { @View private var favoriteView: UIImageView = { let view = UIImageView() - view.image = .icons.star.filled + view.image = .Icons.Star.filled view.contentMode = .scaleAspectFit return view }() diff --git a/MobileWallet/Screens/Debug/Logs/List/LogsListViewController.swift b/MobileWallet/Screens/Debug/Logs/List/LogsListViewController.swift index 19b2eeb3..c2e0a65f 100644 --- a/MobileWallet/Screens/Debug/Logs/List/LogsListViewController.swift +++ b/MobileWallet/Screens/Debug/Logs/List/LogsListViewController.swift @@ -41,11 +41,10 @@ import UIKit import Combine -final class LogsListViewController: UIViewController { +final class LogsListViewController: SecureViewController { // MARK: - Properties - private let mainView = LogsListView() private let model: LogsListModel private var tableDataSource: UITableViewDiffableDataSource? private var cancellables = Set() @@ -63,10 +62,6 @@ final class LogsListViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupTableView() diff --git a/MobileWallet/Screens/Debug/Logs/Log/LogView.swift b/MobileWallet/Screens/Debug/Logs/Log/LogView.swift index 1bd0cf55..ed886b2d 100644 --- a/MobileWallet/Screens/Debug/Logs/Log/LogView.swift +++ b/MobileWallet/Screens/Debug/Logs/Log/LogView.swift @@ -96,7 +96,7 @@ final class LogView: BaseNavigationContentView { // MARK: - Setups private func setupViews() { - navigationBar.update(rightButton: NavigationBar.ButtonModel(image: Theme.shared.images.utxoFaucet, callback: { [weak self] in self?.onFilterButtonTap?() })) + navigationBar.update(rightButton: NavigationBar.ButtonModel(image: .Icons.General.faucet, callback: { [weak self] in self?.onFilterButtonTap?() })) } private func setupConstraints() { diff --git a/MobileWallet/Screens/Debug/Logs/Log/LogViewController.swift b/MobileWallet/Screens/Debug/Logs/Log/LogViewController.swift index 129a9c47..39a40fdb 100644 --- a/MobileWallet/Screens/Debug/Logs/Log/LogViewController.swift +++ b/MobileWallet/Screens/Debug/Logs/Log/LogViewController.swift @@ -38,16 +38,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import Combine import TariCommon -final class LogViewController: UIViewController { +final class LogViewController: SecureViewController { // MARK: - Properties private let model: LogModel - private let mainView = LogView() private var tableDataSource: UITableViewDiffableDataSource? private var cancellables = Set() @@ -64,10 +62,6 @@ final class LogViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupTableView() @@ -123,7 +117,7 @@ final class LogViewController: UIViewController { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([0]) snapshot.appendItems(items) - tableDataSource?.apply(snapshot: snapshot) + tableDataSource?.applySnapshotUsingReloadData(snapshot) } private func showFiltersPopUp(options: [LogFilterModel]) { @@ -275,7 +269,7 @@ private class PopUpSwitchListViewCell: DynamicThemeCell { label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -15.0), switchView.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 8.0), - switchView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + switchView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -4.0), switchView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor) ] diff --git a/MobileWallet/Screens/Home/Home/HomeModel.swift b/MobileWallet/Screens/Home/Home/HomeModel.swift index 628b9028..ff9d57f8 100644 --- a/MobileWallet/Screens/Home/Home/HomeModel.swift +++ b/MobileWallet/Screens/Home/Home/HomeModel.swift @@ -87,9 +87,8 @@ final class HomeModel { .sink { [weak self] _ in self?.updateAvatar() } .store(in: &cancellables) - let transactionsPublisher = Tari.shared.transactions.all + let transactionsPublisher = Tari.shared.transactions.$all .map { $0.filterDuplicates() } - .tryMap { try $0.sorted { try $0.timestamp > $1.timestamp }} .replaceError(with: [Transaction]()) Publishers.CombineLatest(transactionsPublisher, onContactUpdated) @@ -132,7 +131,7 @@ final class HomeModel { case (.disconnected, _, _, _), (.connected, .disconnected, _, _), (.connected, .disconnecting, _, _): - connectionStatusIcon = .icons.network.off + connectionStatusIcon = .Icons.Network.off case (.connected, .connecting, _, _), (.connected, .waitingForAuthorization, _, _), (.connected, .portsOpen, _, _), @@ -140,15 +139,15 @@ final class HomeModel { (.connected, .connected, .connecting, _), (.connected, .connected, .online, .idle), (.connected, .connected, .online, .failed): - connectionStatusIcon = .icons.network.limited + connectionStatusIcon = .Icons.Network.limited case (.connected, .connected, .online, .syncing), (.connected, .connected, .online, .synced): - connectionStatusIcon = .icons.network.full + connectionStatusIcon = .Icons.Network.full } } private func handle(walletBalance: WalletBalance) { - balance = MicroTari(walletBalance.available + walletBalance.incoming).formatted + balance = MicroTari(walletBalance.total).formatted availableBalance = MicroTari(walletBalance.available).formatted } diff --git a/MobileWallet/Screens/Home/Home/HomeView.swift b/MobileWallet/Screens/Home/Home/HomeView.swift index 9e0b982f..85edc34f 100644 --- a/MobileWallet/Screens/Home/Home/HomeView.swift +++ b/MobileWallet/Screens/Home/Home/HomeView.swift @@ -58,14 +58,14 @@ final class HomeView: UIView { @View private var connectionStatusButton: BaseButton = { let view = BaseButton() - view.tintColor = .static.white + view.tintColor = .Static.white return view }() @View private var qrCodeScannerButton: BaseButton = { let view = BaseButton() view.setImage(UIImage(systemName: "qrcode"), for: .normal) - view.tintColor = .static.white + view.tintColor = .Static.white return view }() @@ -73,8 +73,8 @@ final class HomeView: UIView { @View private var balanceCurrencyView: UIImageView = { let view = UIImageView() - view.tintColor = .static.white - view.image = .icons.tariGem + view.tintColor = .Static.white + view.image = .Icons.General.tariGem view.contentMode = .scaleAspectFit return view }() @@ -91,15 +91,15 @@ final class HomeView: UIView { @View private var availableBalanceTitleLabel: UILabel = { let view = UILabel() view.text = localized("home.label.spendable") - view.textColor = .static.white + view.textColor = .Static.white view.font = .Avenir.medium.withSize(12.0) return view }() @View private var availableBalanceCurrencyView: UIImageView = { let view = UIImageView() - view.tintColor = .static.white - view.image = .icons.tariGem + view.tintColor = .Static.white + view.image = .Icons.General.tariGem view.contentMode = .scaleAspectFit return view @@ -115,7 +115,7 @@ final class HomeView: UIView { @View private var amountHelpButton: BaseButton = { let view = BaseButton() view.setImage(UIImage(systemName: "questionmark.circle"), for: .normal) - view.tintColor = .static.white + view.tintColor = .Static.white return view }() @@ -146,7 +146,7 @@ final class HomeView: UIView { @View private var viewAllTransactionsButton: BaseButton = { let view = BaseButton() view.setTitle(localized("home.button.all_transactions"), for: .normal) - view.setTitleColor(.static.white, for: .normal) + view.setTitleColor(.Static.white, for: .normal) view.titleLabel?.font = .Avenir.medium.withSize(12.0) return view }() @@ -326,7 +326,7 @@ final class HomeView: UIView { private func update(balance: String) { - guard let textColor = UIColor.static.white else { return } + let textColor = UIColor.Static.white let integerFont = UIFont.Avenir.black.withSize(50.0) let fractionalFont = UIFont.Avenir.heavy.withSize(18.0) @@ -359,13 +359,10 @@ final class HomeView: UIView { } private func update(availableBalance: String) { - - guard let foregroundColor = UIColor.static.white else { return } - availableBalanceLabel.attributedText = NSAttributedString( string: availableBalance, attributes: [ - .foregroundColor: foregroundColor, + .foregroundColor: UIColor.Static.white, .font: UIFont.Avenir.medium.withSize(12.0) ] ) diff --git a/MobileWallet/Screens/Home/Home/HomeViewController.swift b/MobileWallet/Screens/Home/Home/HomeViewController.swift index e9739408..36767554 100644 --- a/MobileWallet/Screens/Home/Home/HomeViewController.swift +++ b/MobileWallet/Screens/Home/Home/HomeViewController.swift @@ -41,13 +41,11 @@ import UIKit import Combine -final class HomeViewController: UIViewController { +final class HomeViewController: SecureViewController { // MARK: - Properties - private let mainView = HomeView() private let model: HomeModel - private var cancellables = Set() // MARK: - Initialisers @@ -63,10 +61,6 @@ final class HomeViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Home/Home/Views/HomeGlassView.swift b/MobileWallet/Screens/Home/Home/Views/HomeGlassView.swift index de97328b..4ff60f3a 100644 --- a/MobileWallet/Screens/Home/Home/Views/HomeGlassView.swift +++ b/MobileWallet/Screens/Home/Home/Views/HomeGlassView.swift @@ -56,9 +56,9 @@ class HomeGlassView: UIView { // MARK: - Setups private func setupView() { - backgroundColor = .static.white?.withAlphaComponent(0.2) + backgroundColor = .Static.white.withAlphaComponent(0.2) layer.cornerRadius = 10.0 - layer.borderColor = UIColor.static.white?.withAlphaComponent(0.4).cgColor + layer.borderColor = UIColor.Static.white.withAlphaComponent(0.4).cgColor layer.borderWidth = 1.0 } } diff --git a/MobileWallet/Screens/Home/Home/Views/HomeTransactionsPlaceholderView.swift b/MobileWallet/Screens/Home/Home/Views/HomeTransactionsPlaceholderView.swift index a7ce9e28..7b169fbb 100644 --- a/MobileWallet/Screens/Home/Home/Views/HomeTransactionsPlaceholderView.swift +++ b/MobileWallet/Screens/Home/Home/Views/HomeTransactionsPlaceholderView.swift @@ -46,7 +46,7 @@ final class HomeTransactionsPlaceholderView: HomeGlassView { @View private var label: UILabel = { let view = UILabel() - view.textColor = .static.white + view.textColor = .Static.white view.font = .Avenir.medium.withSize(12.0) view.numberOfLines = 0 return view diff --git a/MobileWallet/Screens/Home/Home/Views/HomeViewTransactionCell.swift b/MobileWallet/Screens/Home/Home/Views/HomeViewTransactionCell.swift index d988ea96..1c821f46 100644 --- a/MobileWallet/Screens/Home/Home/Views/HomeViewTransactionCell.swift +++ b/MobileWallet/Screens/Home/Home/Views/HomeViewTransactionCell.swift @@ -75,7 +75,7 @@ final class HomeViewTransactionCell: UITableViewCell { @View private var titleLabel: StylizedLabel = { let view = StylizedLabel() - view.textColor = .static.white + view.textColor = .Static.white view.normalFont = .Avenir.medium.withSize(12.0) view.boldFont = .Avenir.black.withSize(12.0) view.separator = " " @@ -84,7 +84,7 @@ final class HomeViewTransactionCell: UITableViewCell { @View private var timestampLabel: UILabel = { let view = UILabel() - view.textColor = .static.white + view.textColor = .Static.white view.font = .Avenir.light.withSize(11.0) return view }() diff --git a/MobileWallet/Screens/Home/Home/Views/PopUpNetworkStatusContentView.swift b/MobileWallet/Screens/Home/Home/Views/PopUpNetworkStatusContentView.swift index 1222dbac..42c14d3f 100644 --- a/MobileWallet/Screens/Home/Home/Views/PopUpNetworkStatusContentView.swift +++ b/MobileWallet/Screens/Home/Home/Views/PopUpNetworkStatusContentView.swift @@ -69,25 +69,25 @@ final class PopUpNetworkStatusContentView: UIView { @View private var networkStatusView: StatusView = { let view = StatusView() - view.update(icon: Theme.shared.images.connectionInternetIcon) + view.update(icon: .Icons.ConnectionDetails.internet) return view }() @View private var torStatusView: StatusView = { let view = StatusView() - view.update(icon: Theme.shared.images.connectionTorIcon) + view.update(icon: .Icons.ConnectionDetails.tor) return view }() @View private var baseNodeConnectionStatusView: StatusView = { let view = StatusView() - view.update(icon: Theme.shared.images.settingsBaseNodeIcon) + view.update(icon: .Icons.Settings.baseNode) return view }() @View private var baseNodeSyncStatusView: StatusView = { let view = StatusView() - view.update(icon: Theme.shared.images.connectionSyncIcon) + view.update(icon: .Icons.ConnectionDetails.sync) return view }() diff --git a/MobileWallet/Screens/Home/Home/Views/PulseLayer.swift b/MobileWallet/Screens/Home/Home/Views/PulseLayer.swift index e93563f0..82be57ec 100644 --- a/MobileWallet/Screens/Home/Home/Views/PulseLayer.swift +++ b/MobileWallet/Screens/Home/Home/Views/PulseLayer.swift @@ -66,7 +66,7 @@ final class PulseLayer: CAShapeLayer { path = bezierPath.cgPath lineWidth = 2.0 fillColor = UIColor.clear.cgColor - strokeColor = UIColor.static.white?.cgColor + strokeColor = UIColor.Static.white.cgColor opacity = 0.0 } diff --git a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift index 2597487f..9285838d 100644 --- a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift +++ b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift @@ -122,7 +122,7 @@ final class TransactionDetailsModel { transactionState = try fetchTransactionState() transactionDirection = try fetchTransactionDirection() emojiIdViewModel = try fetchEmojiIdViewModel() - isContactSectionVisible = try !transaction.isOneSidedPayment + isContactSectionVisible = try !transaction.isOneSidedPayment && !transaction.isCoinbase subtitle = try fetchSubtitle() amount = try fetchAmount() fee = try fetchFee() @@ -209,9 +209,9 @@ final class TransactionDetailsModel { } switch try transaction.status { - case .unknown, .txNullError, .completed, .broadcast, .minedUnconfirmed, .pending, .queued: + case .unknown, .txNullError, .completed, .broadcast, .minedUnconfirmed, .pending, .queued, .coinbaseUnconfirmed, .coinbaseNotInBlockChain: return localized("tx_detail.payment_in_progress") - case .minedConfirmed, .imported, .rejected, .fauxUnconfirmed, .fauxConfirmed, .coinbase: + case .minedConfirmed, .imported, .rejected, .oneSidedUnconfirmed, .oneSidedConfirmed, .coinbase, .coinbaseConfirmed: return try transaction.isOutboundTransaction ? localized("tx_detail.payment_sent") : localized("tx_detail.payment_received") } } @@ -251,7 +251,7 @@ final class TransactionDetailsModel { return .txCompleted(confirmationCount: 1) } return .txCompleted(confirmationCount: confirmationCount + 1) - case .txNullError, .imported, .minedConfirmed, .unknown, .rejected, .fauxUnconfirmed, .fauxConfirmed, .queued, .coinbase: + case .txNullError, .imported, .minedConfirmed, .unknown, .rejected, .oneSidedUnconfirmed, .oneSidedConfirmed, .queued, .coinbase, .coinbaseUnconfirmed, .coinbaseConfirmed, .coinbaseNotInBlockChain: return nil } } @@ -283,9 +283,8 @@ final class TransactionDetailsModel { } private func fetchLinkToOpen() -> URL? { - guard let transactionNounce = transactionNounce, let transactionSignature = transactionSignature else { return nil } - let request = [transactionNounce, transactionSignature].joined(separator: "/") - return URL(string: TariSettings.shared.blockExplorerKernelUrl + "\(request)") + guard let transactionNounce, let transactionSignature else { return nil } + return NetworkManager.shared.selectedNetwork.blockExplorerKernelURL(nounce: transactionNounce, signature: transactionSignature) } private func handle(transaction: Transaction) { @@ -331,7 +330,7 @@ final class TransactionDetailsModel { private func handleTransactionKernel() { defer { - isBlockExplorerActionAvailable = transactionNounce != nil && transactionSignature != nil && TariSettings.shared.isBlockExplorerAvaiable + isBlockExplorerActionAvailable = NetworkManager.shared.selectedNetwork.isBlockExplorerAvailable && transactionNounce != nil && transactionSignature != nil } guard let kernel = try? (transaction as? CompletedTransaction)?.transactionKernel else { diff --git a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift index 1c84e765..eedfdfa3 100644 --- a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift +++ b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift @@ -41,13 +41,11 @@ import UIKit import Combine -final class TransactionDetailsViewController: UIViewController { +final class TransactionDetailsViewController: SecureViewController { // MARK: - Properties private let model: TransactionDetailsModel - private let mainView = TransactionDetailsView() - private var cancellables = Set() // MARK: - Initialisers @@ -63,10 +61,6 @@ final class TransactionDetailsViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupModelCallbacks() diff --git a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryView.swift b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryView.swift index 07d8288c..18ab8531 100644 --- a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryView.swift +++ b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryView.swift @@ -63,9 +63,7 @@ final class TransactionHistoryView: BaseNavigationContentView { view.rowHeight = UITableView.automaticDimension view.separatorInset = UIEdgeInsets(top: 0.0, left: 22.0, bottom: 0.0, right: 22.0) view.keyboardDismissMode = .interactive - if #available(iOS 15.0, *) { - view.sectionHeaderTopPadding = .zero - } + view.sectionHeaderTopPadding = .zero view.register(type: TransactionHistoryCell.self) view.register(headerFooterType: MenuTableHeaderView.self) return view @@ -156,7 +154,7 @@ final class TransactionHistoryView: BaseNavigationContentView { } viewModels = transactions - dataSource?.apply(snapshot: snapshot) + dataSource?.applySnapshotUsingReloadData(snapshot) } private func updateCells(indexPath: IndexPath) { diff --git a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift index 0fc14125..1da4980b 100644 --- a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift +++ b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift @@ -41,13 +41,11 @@ import UIKit import Combine -final class TransactionHistoryViewController: UIViewController { +final class TransactionHistoryViewController: SecureViewController { // MARK: - Properties - private let mainView = TransactionHistoryView() private let model: TransactionHistoryModel - private var cancellables = Set() // MARK: - Initialisers @@ -63,10 +61,6 @@ final class TransactionHistoryViewController: UIViewController { // MARK: - View Life Cycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletModel.swift b/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletModel.swift index 18336869..508b345d 100644 --- a/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletModel.swift +++ b/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletModel.swift @@ -269,6 +269,8 @@ final class UTXOsWalletModel { heightScale = heightDiff / CGFloat(amountDiff) } + let networkBlockHeight = Tari.shared.blockHeight + utxoModels = utxosData.data .sorted { switch sortMethod { @@ -283,13 +285,14 @@ final class UTXOsWalletModel { } } .compactMap { [weak self] in - guard let self = self else { return nil } + guard let self else { return nil } let tileHeight = CGFloat($0.model.value - minAmount) * heightScale + minTileHeight let timestamp = Date(timeIntervalSince1970: TimeInterval($0.model.mined_timestamp) / 1000.0) let date = timestamp > Date(timeIntervalSince1970: 0) ? self.dateFormatter.string(from: timestamp) : nil let time = timestamp > Date(timeIntervalSince1970: 0) ? self.timeFormatter.string(from: timestamp) : nil let blockHeight = $0.model.mined_height > 0 ? "\($0.model.mined_height)" : nil + let isSelectable = $0.status == .mined && $0.model.lock_height <= networkBlockHeight return UtxoModel( uuid: UUID(), amount: $0.model.value, @@ -300,7 +303,7 @@ final class UTXOsWalletModel { time: time, commitment: $0.model.commitment.string, blockHeight: blockHeight, - isSelectable: $0.status == .mined + isSelectable: isSelectable ) } @@ -329,9 +332,9 @@ extension UtxoStatus { var icon: UIImage? { switch self { case .mined: - return Theme.shared.images.utxoTick + return .Icons.General.tick case .unconfirmed: - return Theme.shared.images.utxoStatusHourglass + return .Icons.UTXO.hourglass } } diff --git a/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletView.swift b/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletView.swift index 1b10cd95..a93c21a2 100644 --- a/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletView.swift +++ b/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletView.swift @@ -273,9 +273,9 @@ final class UTXOsWalletView: BaseNavigationContentView { private func updateListSwitchIcon(selectedListType: ListType) { switch selectedListType { case .tiles: - navigationBar.rightButton(index: 0)?.setImage(Theme.shared.images.utxoTextListIcon, for: .normal) + navigationBar.rightButton(index: 0)?.setImage(.Icons.UTXO.textListView, for: .normal) case .text: - navigationBar.rightButton(index: 0)?.setImage(Theme.shared.images.utxoTileViewIcon, for: .normal) + navigationBar.rightButton(index: 0)?.setImage(.Icons.UTXO.tileListView, for: .normal) } } @@ -290,11 +290,11 @@ final class UTXOsWalletView: BaseNavigationContentView { switch action { case .break: - return ContextualButtonsOverlay.ButtonModel(text: localized("utxos_wallet.button.actions.break"), image: Theme.shared.images.utxoActionSplit, callback: callback) + return ContextualButtonsOverlay.ButtonModel(text: localized("utxos_wallet.button.actions.break"), image: .Icons.UTXO.split, callback: callback) case .combine: - return ContextualButtonsOverlay.ButtonModel(text: localized("utxos_wallet.button.actions.combine"), image: Theme.shared.images.utxoActionJoin, callback: callback) + return ContextualButtonsOverlay.ButtonModel(text: localized("utxos_wallet.button.actions.combine"), image: .Icons.UTXO.join, callback: callback) case .combineBreak: - return ContextualButtonsOverlay.ButtonModel(text: localized("utxos_wallet.button.actions.combine_break"), image: Theme.shared.images.utxoActionJoinSplit, callback: callback) + return ContextualButtonsOverlay.ButtonModel(text: localized("utxos_wallet.button.actions.combine_break"), image: .Icons.UTXO.joinSplit, callback: callback) } } } diff --git a/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletViewController.swift b/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletViewController.swift index 23933b1a..c9652d41 100644 --- a/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletViewController.swift +++ b/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletViewController.swift @@ -41,13 +41,11 @@ import UIKit import Combine -final class UTXOsWalletViewController: UIViewController { +final class UTXOsWalletViewController: SecureViewController { // MARK: - Properties private let model: UTXOsWalletModel - private let mainView = UTXOsWalletView() - private var cancellables = Set() // MARK: - Initialisers @@ -63,10 +61,6 @@ final class UTXOsWalletViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() @@ -341,7 +335,7 @@ final class UTXOsWalletViewController: UIViewController { let contentSection = PopUpDescriptionContentView() let buttonsSection = PopUpButtonsView() - headerSection.imageView.image = Theme.shared.images.utxoSuccessImage + headerSection.imageView.image = .Images.UTXO.success headerSection.imageHeight = 90.0 headerSection.label.text = localized("utxos_wallet.pop_up.success.title") diff --git a/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOTileView.swift b/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOTileView.swift index efd0d1e6..dca4b518 100644 --- a/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOTileView.swift +++ b/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOTileView.swift @@ -76,7 +76,7 @@ final class UTXOTileView: DynamicThemeCollectionCell { @View private var amountLabel: CurrencyLabelView = { let view = CurrencyLabelView() - view.textColor = .static.white + view.textColor = .Static.white view.font = .Avenir.black.withSize(30.0) view.secondaryFont = .Avenir.black.withSize(12.0) view.separator = Locale.current.decimalSeparator @@ -86,7 +86,7 @@ final class UTXOTileView: DynamicThemeCollectionCell { @View private var dateLabel: UILabel = { let view = UILabel() - view.textColor = .static.white + view.textColor = .Static.white view.font = .Avenir.medium.withSize(12.0) return view }() @@ -94,7 +94,7 @@ final class UTXOTileView: DynamicThemeCollectionCell { @View private var statusIcon: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFit - view.tintColor = .static.white + view.tintColor = .Static.white return view }() diff --git a/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOsWalletPlaceholderView.swift b/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOsWalletPlaceholderView.swift index 6b9ef194..20de9a09 100644 --- a/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOsWalletPlaceholderView.swift +++ b/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOsWalletPlaceholderView.swift @@ -54,7 +54,7 @@ final class UTXOsWalletPlaceholderView: DynamicThemeView { @View private var imageView: UIImageView = { let view = UIImageView() - view.image = Theme.shared.images.utxoWalletPlaceholder?.withRenderingMode(.alwaysTemplate) + view.image = .Images.UTXO.placeholder view.contentMode = .scaleAspectFit return view }() diff --git a/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOsWalletTopBar.swift b/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOsWalletTopBar.swift index 9ac61fca..0eef04e2 100644 --- a/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOsWalletTopBar.swift +++ b/MobileWallet/Screens/Home/UTXOs Wallet/Views/UTXOsWalletTopBar.swift @@ -46,7 +46,7 @@ final class UTXOsWalletTopBar: BaseToolbar { @View private var filterButton: LeftImageButton = { let view = LeftImageButton() - view.iconView.image = Theme.shared.images.utxoFaucet + view.iconView.image = .Icons.General.faucet view.label.font = .Avenir.roman.withSize(14.0) view.iconSize = CGSize(width: 14.0, height: 14.0) view.internalPadding = 12.0 diff --git a/MobileWallet/Screens/Home/UTXOs Wallet/Views/ValuePickerView.swift b/MobileWallet/Screens/Home/UTXOs Wallet/Views/ValuePickerView.swift index 7348dfdb..0d5a4ba9 100644 --- a/MobileWallet/Screens/Home/UTXOs Wallet/Views/ValuePickerView.swift +++ b/MobileWallet/Screens/Home/UTXOs Wallet/Views/ValuePickerView.swift @@ -53,7 +53,7 @@ final class ValuePickerView: DynamicThemeView { @View private var minusButton: BaseButton = { let view = BaseButton() - view.setImage(Theme.shared.images.utxoWalletPickerMinus, for: .normal) + view.setImage(.Icons.UTXO.valuePickerMinus, for: .normal) return view }() @@ -72,7 +72,7 @@ final class ValuePickerView: DynamicThemeView { @View private var plusButton: BaseButton = { let view = BaseButton() - view.setImage(Theme.shared.images.utxoWalletPickerPlus, for: .normal) + view.setImage(.Icons.UTXO.valuePickerPlus, for: .normal) return view }() diff --git a/MobileWallet/Screens/PasswordVerification/PasswordVerificationViewController.swift b/MobileWallet/Screens/PasswordVerification/PasswordVerificationViewController.swift index b27a975b..b73dd4d2 100644 --- a/MobileWallet/Screens/PasswordVerification/PasswordVerificationViewController.swift +++ b/MobileWallet/Screens/PasswordVerification/PasswordVerificationViewController.swift @@ -40,7 +40,7 @@ import UIKit -class PasswordVerificationViewController: SettingsParentViewController { +final class PasswordVerificationViewController: SettingsParentViewController { enum PasswordVerificationScreenStyle { case restore case change @@ -175,7 +175,7 @@ extension PasswordVerificationViewController { scrollView.showsVerticalScrollIndicator = false scrollView.backgroundColor = .clear - view.addSubview(scrollView) + mainView.addSubview(scrollView) scrollView.translatesAutoresizingMaskIntoConstraints = false scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true @@ -206,7 +206,7 @@ extension PasswordVerificationViewController { continueButton.addTarget(self, action: #selector(continueButtonAction), for: .touchUpInside) continueButton.variation = .disabled - view.addSubview(continueButton) + mainView.addSubview(continueButton) continueButton.translatesAutoresizingMaskIntoConstraints = false continueButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, diff --git a/MobileWallet/Screens/Profile/ProfileModel.swift b/MobileWallet/Screens/Profile/ProfileModel.swift index ee61e900..9fd14321 100644 --- a/MobileWallet/Screens/Profile/ProfileModel.swift +++ b/MobileWallet/Screens/Profile/ProfileModel.swift @@ -187,14 +187,9 @@ final class ProfileModel { } private func makeDeeplink() throws -> URL? { - guard let alias = name else { return nil } let hex = try Tari.shared.walletAddress.byteVector.hex - - let deeplinkModel = ContactListDeeplink(list: [ - ContactListDeeplink.Contact(alias: alias, hex: hex) - ]) - + let deeplinkModel = UserProfileDeeplink(alias: alias, tariAddress: hex) return try DeepLinkFormatter.deeplink(model: deeplinkModel) } diff --git a/MobileWallet/Screens/Profile/ProfileView.swift b/MobileWallet/Screens/Profile/ProfileView.swift index 5c1762e2..3903f7d3 100644 --- a/MobileWallet/Screens/Profile/ProfileView.swift +++ b/MobileWallet/Screens/Profile/ProfileView.swift @@ -89,7 +89,7 @@ final class ProfileView: BaseNavigationContentView { let view = RoundedLabeledButton() view.buttonSize = 46.0 view.padding = 12.0 - view.update(image: .icons.wallet, text: localized("profile_view.button.wallet")) + view.update(image: .Icons.General.wallet, text: localized("profile_view.button.wallet")) return view }() @@ -97,7 +97,7 @@ final class ProfileView: BaseNavigationContentView { let view = RoundedLabeledButton() view.buttonSize = 46.0 view.padding = 12.0 - view.update(image: Theme.shared.images.yatLogo, text: localized("profile_view.button.connect_yat")) + view.update(image: .Icons.Yat.logo, text: localized("profile_view.button.connect_yat")) return view }() @@ -131,13 +131,13 @@ final class ProfileView: BaseNavigationContentView { let view = RoundedLabeledButton() view.buttonSize = 46.0 view.padding = 12.0 - view.update(image: .icons.qr, text: localized("contact_book.share_bar.buttons.qr")) + view.update(image: .Icons.General.QR, text: localized("contact_book.share_bar.buttons.qr")) return view }() @View private var linkCodeButton: RoundedLabeledButton = { let view = RoundedLabeledButton() - view.update(image: .icons.link, text: localized("contact_book.share_bar.buttons.link")) + view.update(image: .Icons.General.link, text: localized("contact_book.share_bar.buttons.link")) view.buttonSize = 46.0 view.padding = 12.0 return view @@ -145,7 +145,7 @@ final class ProfileView: BaseNavigationContentView { @View private var bleCodeButton: RoundedLabeledButton = { let view = RoundedLabeledButton() - view.update(image: .icons.bluetooth, text: localized("contact_book.share_bar.buttons.ble")) + view.update(image: .Icons.General.bluetooth, text: localized("contact_book.share_bar.buttons.ble")) view.buttonSize = 46.0 view.padding = 12.0 return view @@ -323,7 +323,7 @@ final class ProfileView: BaseNavigationContentView { private func updateYatButton(isOn: Bool) { yatButton.isHidden = false - let icon = isOn ? Theme.shared.images.yatButtonOn : Theme.shared.images.yatButtonOff + let icon: UIImage = isOn ? .Icons.Yat.buttonOn : .Icons.Yat.buttonOff yatButton.setImage(icon, for: .normal) yatButton.tintColor = isOn ? yatButtonOnTintColor : yatButtonOffTintColor yatSpinnerView.isHidden = true diff --git a/MobileWallet/Screens/Profile/ProfileViewController.swift b/MobileWallet/Screens/Profile/ProfileViewController.swift index 823e1f90..db6c016b 100644 --- a/MobileWallet/Screens/Profile/ProfileViewController.swift +++ b/MobileWallet/Screens/Profile/ProfileViewController.swift @@ -42,13 +42,11 @@ import UIKit import Combine import YatLib -final class ProfileViewController: UIViewController { +final class ProfileViewController: SecureViewController { // MARK: - Properties - private let mainView = ProfileView() private let model = ProfileModel() - private weak var qrCodePopUpContentView: PopUpQRContentView? private var cancellables = Set() @@ -65,10 +63,6 @@ final class ProfileViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupBindings() diff --git a/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationController.swift b/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationController.swift index 386c8b76..b3eb24f8 100644 --- a/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationController.swift +++ b/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationController.swift @@ -41,12 +41,10 @@ import UIKit @available(*, deprecated, message: "This class is deprecated and will be removed later. Please user PopUpPresenter.showQRCodeDialog() instead.") -final class QRCodePresentationController: UIViewController { +final class QRCodePresentationController: SecureViewController { // MARK: - Properties - private let mainView = QRCodePresentationView() - var onShareButtonTap: (() -> Void)? // MARK: - Initialisers @@ -65,10 +63,6 @@ final class QRCodePresentationController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupBindings() diff --git a/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationView.swift b/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationView.swift index fc1fc40a..c2eea54d 100644 --- a/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationView.swift +++ b/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationView.swift @@ -86,7 +86,7 @@ final class QRCodePresentationView: DynamicThemeView { // MARK: - Setups private func setupViews() { - backgroundColor = .static.popupOverlay + backgroundColor = .Static.popupOverlay } private func setupConstraints() { diff --git a/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountModel.swift b/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountModel.swift index d5d8cb77..aec0d5d1 100644 --- a/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountModel.swift +++ b/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountModel.swift @@ -58,7 +58,6 @@ final class RequestTariAmountModel { // MARK: - Properties private let amountFormatter = AmountNumberFormatter() - private var cancellables = Set() init() { setupCallbacks() @@ -68,23 +67,13 @@ final class RequestTariAmountModel { private func setupCallbacks() { - if #available(iOS 14.0, *) { - amountFormatter.$amount - .assign(to: &$amount) - - amountFormatter.$amountValue - .map { $0 > 0.0 } - .assign(to: &$isValidAmount) - } else { - amountFormatter.$amount - .assign(to: \.amount, on: self) - .store(in: &cancellables) - - amountFormatter.$amountValue - .map { $0 > 0.0 } - .assign(to: \.isValidAmount, on: self) - .store(in: &cancellables) - } + amountFormatter.$amount + .assign(to: &$amount) + + amountFormatter.$amountValue + .map { $0 > 0.0 } + .assign(to: &$isValidAmount) + } // MARK: - Actions diff --git a/MobileWallet/Screens/QR Code Scanner/QRCodeScannerView.swift b/MobileWallet/Screens/QR Code Scanner/QRCodeScannerView.swift index 61c68559..88501f1b 100644 --- a/MobileWallet/Screens/QR Code Scanner/QRCodeScannerView.swift +++ b/MobileWallet/Screens/QR Code Scanner/QRCodeScannerView.swift @@ -63,8 +63,8 @@ final class QRCodeScannerView: UIView { @View private var closeButton: BaseButton = { let view = BaseButton() - view.setImage(.icons.close, for: .normal) - view.tintColor = .static.white + view.setImage(.Icons.General.close, for: .normal) + view.tintColor = .Static.white return view }() @@ -72,7 +72,7 @@ final class QRCodeScannerView: UIView { @View private var titleContentView: UIView = { let view = UIView() - view.backgroundColor = .static.popupOverlay + view.backgroundColor = .Static.popupOverlay view.layer.cornerRadius = 10.0 return view }() @@ -83,7 +83,7 @@ final class QRCodeScannerView: UIView { view.textAlignment = .center view.font = .Avenir.medium.withSize(16.0) view.numberOfLines = 0 - view.textColor = .static.white + view.textColor = .Static.white return view }() @@ -115,25 +115,25 @@ final class QRCodeScannerView: UIView { @View private var approveButton: RoundedButton = { let view = RoundedButton() - view.setImage(.icons.checkmark, for: .normal) - view.backgroundColor = .static.white + view.setImage(.Icons.General.checkmark, for: .normal) + view.backgroundColor = .Static.white view.layer.cornerRadius = 22.0 return view }() @View private var cancelButton: RoundedButton = { let view = RoundedButton() - view.setImage(.icons.close, for: .normal) - view.backgroundColor = .static.white - view.tintColor = .static.black + view.setImage(.Icons.General.close, for: .normal) + view.backgroundColor = .Static.white + view.tintColor = .Static.black view.layer.cornerRadius = 22.0 return view }() @View private var bottomCenteredContentView: UIView = { let view = UIView() - view.backgroundColor = .static.popupOverlay - view.tintColor = .static.black + view.backgroundColor = .Static.popupOverlay + view.tintColor = .Static.black view.layer.cornerRadius = 10.0 return view }() @@ -166,7 +166,7 @@ final class QRCodeScannerView: UIView { // MARK: - Setups private func setupView() { - backgroundColor = .static.black + backgroundColor = .Static.black } private func setupLayers() { @@ -252,9 +252,9 @@ final class QRCodeScannerView: UIView { private func updateActionLabel(actionType: ActionType) { switch actionType { case .normal: - self.actionLabel.textColor = .static.white + self.actionLabel.textColor = .Static.white case .error: - self.actionLabel.textColor = .static.red + self.actionLabel.textColor = .Static.red } } diff --git a/MobileWallet/Screens/QR Code Scanner/QRCodeScannerViewController.swift b/MobileWallet/Screens/QR Code Scanner/QRCodeScannerViewController.swift index 8b272755..00beafe4 100644 --- a/MobileWallet/Screens/QR Code Scanner/QRCodeScannerViewController.swift +++ b/MobileWallet/Screens/QR Code Scanner/QRCodeScannerViewController.swift @@ -42,13 +42,12 @@ import UIKit import AVFoundation import Combine -final class QRCodeScannerViewController: UIViewController { +final class QRCodeScannerViewController: SecureViewController { // MARK: - Properties var onExpectedDataScan: ((QRCodeData) -> Void)? - private let mainView = QRCodeScannerView() private let model: QRCodeScannerModel private var cancellables = Set() @@ -67,10 +66,6 @@ final class QRCodeScannerViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/QR Code Scanner/Views/QRCodeScannerBoxView.swift b/MobileWallet/Screens/QR Code Scanner/Views/QRCodeScannerBoxView.swift index eed73fa6..99075c81 100644 --- a/MobileWallet/Screens/QR Code Scanner/Views/QRCodeScannerBoxView.swift +++ b/MobileWallet/Screens/QR Code Scanner/Views/QRCodeScannerBoxView.swift @@ -46,7 +46,7 @@ final class QRCodeScannerBoxView: UIView { private let borderLayer: CAShapeLayer = { let layer = CAShapeLayer() - layer.strokeColor = UIColor.static.white?.cgColor + layer.strokeColor = UIColor.Static.white.cgColor layer.fillColor = UIColor.clear.cgColor layer.lineWidth = 10.0 layer.lineCap = .round diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift index 66e87bb8..63906f6b 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift @@ -48,14 +48,16 @@ struct TokenViewModel: Identifiable, Hashable { final class RestoreWalletFromSeedsModel { final class ViewModel { - @Published var seedWordModels: [SeedWordModel] = [] - @Published var updatedInputText: String = "" - @Published var error: MessageModel? - @Published var isConfimationEnabled: Bool = false - @Published var isEmptyWalletCreated: Bool = false - @Published var isAutocompletionAvailable: Bool = false - @Published var autocompletionTokens: [TokenViewModel] = [] - @Published var autocompletionMessage: String? + @Published fileprivate(set) var seedWordModels: [SeedWordModel] = [] + @Published fileprivate(set) var updatedInputText: String = "" + @Published fileprivate(set) var error: MessageModel? + @Published fileprivate(set) var isConfimationEnabled: Bool = false + @Published fileprivate(set) var isEmptyWalletCreated: Bool = false + @Published fileprivate(set) var isAutocompletionAvailable: Bool = false + @Published fileprivate(set) var autocompletionTokens: [TokenViewModel] = [] + @Published fileprivate(set) var autocompletionMessage: String? + @Published fileprivate(set) var customBaseNodeHex: String? + @Published fileprivate(set) var customBaseNodeAddress: String? } // MARK: - Properties @@ -147,9 +149,27 @@ final class RestoreWalletFromSeedsModel { viewModel.updatedInputText = "" } + func updateCustomBaseNode(hex: String?, address: String?) { + + let hex = hex ?? "" + let address = address ?? "" + + switch (hex.isEmpty, address.isEmpty) { + case (true, true): + viewModel.customBaseNodeHex = nil + viewModel.customBaseNodeAddress = nil + case (true, false), (false, true): + viewModel.error = MessageModel(title: localized("restore_from_seed_words.error.title"), message: localized("restore_from_seed_words.form.error.message"), type: .error) + return + case (false, false): + handle(hex: hex, address: address) + } + } + private func restoreWallet(seedWords: [String]) { do { try Tari.shared.restoreWallet(seedWords: seedWords) + try selectCustomBaseNode() viewModel.isEmptyWalletCreated = true } catch let error as SeedWords.InternalError { handle(seedWordsError: error) @@ -170,6 +190,11 @@ final class RestoreWalletFromSeedsModel { viewModel.seedWordModels.insert(contentsOf: models, at: index) } + private func selectCustomBaseNode() throws { + guard let hex = viewModel.customBaseNodeHex, let address = viewModel.customBaseNodeAddress else { return } + try Tari.shared.connection.addBaseNode(name: localized("restore_from_seed_words.custom_node_name"), hex: hex, address: address) + } + // MARK: - Handlers private func handle(inputText: String) { @@ -208,6 +233,17 @@ final class RestoreWalletFromSeedsModel { ) } + private func handle(hex: String, address: String) { + + guard String.isBaseNodeAddress(hex: hex, address: address) else { + viewModel.error = MessageModel(title: localized("restore_from_seed_words.error.title"), message: localized("restore_from_seed_words.form.error.message"), type: .error) + return + } + + viewModel.customBaseNodeHex = hex + viewModel.customBaseNodeAddress = address + } + // MARK: - Helpers private func state(seedWord: String) -> SeedWordModel.State { diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/Views/RestoreWalletFromSeedsView.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsView.swift similarity index 65% rename from MobileWallet/Screens/RestoreWalletFromSeeds/Views/RestoreWalletFromSeedsView.swift rename to MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsView.swift index 0928f76e..eb3a65dd 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/Views/RestoreWalletFromSeedsView.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsView.swift @@ -38,14 +38,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit -import Combine import TariCommon -final class RestoreWalletFromSeedsView: KeyboardAvoidingContentView { +final class RestoreWalletFromSeedsView: BaseNavigationContentView { // MARK: - Subviews + @View private var mainContentView = KeyboardAvoidingContentView() + @View private var descriptionLabel: UILabel = { let view = UILabel() view.font = Theme.shared.fonts.restoreFormSeedWordsDescription @@ -59,7 +59,6 @@ final class RestoreWalletFromSeedsView: KeyboardAvoidingContentView { @View private(set) var selectBaseNodeButton: TextButton = { let view = TextButton() view.setVariation(.secondary) - view.setTitle(localized("restore_from_seed_words.button.select_base_node"), for: .normal) return view }() @@ -69,10 +68,20 @@ final class RestoreWalletFromSeedsView: KeyboardAvoidingContentView { return view }() + // MARK: - Properties + + var isCustomBaseNodeSet: Bool = false { + didSet { + let title = isCustomBaseNodeSet ? localized("restore_from_seed_words.button.select_base_node.edit") : localized("restore_from_seed_words.button.select_base_node.select") + selectBaseNodeButton.setTitle(title, for: .normal) + } + } + // MARK: - Initializers override init() { super.init() + setupViews() setupConstraints() } @@ -82,25 +91,34 @@ final class RestoreWalletFromSeedsView: KeyboardAvoidingContentView { // MARK: - Setups + private func setupViews() { + navigationBar.title = localized("restore_from_seed_words.title") + } + private func setupConstraints() { - [descriptionLabel, tokenView, selectBaseNodeButton, submitButton].forEach(contentView.addSubview) + addSubview(mainContentView) + [descriptionLabel, tokenView, selectBaseNodeButton, submitButton].forEach(mainContentView.contentView.addSubview) let constraints = [ - descriptionLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20.0), - descriptionLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), - descriptionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), + mainContentView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + mainContentView.leadingAnchor.constraint(equalTo: leadingAnchor), + mainContentView.trailingAnchor.constraint(equalTo: trailingAnchor), + mainContentView.bottomAnchor.constraint(equalTo: bottomAnchor), + descriptionLabel.topAnchor.constraint(equalTo: mainContentView.contentView.topAnchor, constant: 20.0), + descriptionLabel.leadingAnchor.constraint(equalTo: mainContentView.contentView.leadingAnchor, constant: 25.0), + descriptionLabel.trailingAnchor.constraint(equalTo: mainContentView.contentView.trailingAnchor, constant: -25.0), tokenView.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 20.0), - tokenView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), - tokenView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), + tokenView.leadingAnchor.constraint(equalTo: mainContentView.contentView.leadingAnchor, constant: 25.0), + tokenView.trailingAnchor.constraint(equalTo: mainContentView.contentView.trailingAnchor, constant: -25.0), tokenView.heightAnchor.constraint(equalToConstant: 272.0), selectBaseNodeButton.topAnchor.constraint(greaterThanOrEqualTo: tokenView.bottomAnchor, constant: 20.0), - selectBaseNodeButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), - selectBaseNodeButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), + selectBaseNodeButton.leadingAnchor.constraint(equalTo: mainContentView.contentView.leadingAnchor, constant: 25.0), + selectBaseNodeButton.trailingAnchor.constraint(equalTo: mainContentView.contentView.trailingAnchor, constant: -25.0), submitButton.topAnchor.constraint(equalTo: selectBaseNodeButton.bottomAnchor, constant: 20.0), - submitButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), - submitButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), - submitButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -28.0) + submitButton.leadingAnchor.constraint(equalTo: mainContentView.contentView.leadingAnchor, constant: 25.0), + submitButton.trailingAnchor.constraint(equalTo: mainContentView.contentView.trailingAnchor, constant: -25.0), + submitButton.bottomAnchor.constraint(equalTo: mainContentView.contentView.bottomAnchor, constant: -28.0) ] NSLayoutConstraint.activate(constraints) diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift index 0b20bffd..c4cd3afb 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift @@ -41,11 +41,10 @@ import UIKit import Combine -final class RestoreWalletFromSeedsViewController: SettingsParentViewController, OverlayPresentable { +final class RestoreWalletFromSeedsViewController: SecureViewController, OverlayPresentable { // MARK: - Properties - private let mainView = RestoreWalletFromSeedsView() private let model = RestoreWalletFromSeedsModel() private var cancelables = Set() @@ -63,26 +62,6 @@ final class RestoreWalletFromSeedsViewController: SettingsParentViewController, // MARK: - Setups - override func setupViews() { - super.setupViews() - setupConstraints() - } - - private func setupConstraints() { - - view.addSubview(mainView) - mainView.translatesAutoresizingMaskIntoConstraints = false - - let constraints = [ - mainView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), - mainView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - mainView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - mainView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ] - - NSLayoutConstraint.activate(constraints) - } - private func setupFeedbacks() { model.viewModel.$isEmptyWalletCreated @@ -121,6 +100,12 @@ final class RestoreWalletFromSeedsViewController: SettingsParentViewController, .assign(to: \.seedWords, on: mainView.tokenView) .store(in: &cancelables) + Publishers.CombineLatest(model.viewModel.$customBaseNodeHex, model.viewModel.$customBaseNodeAddress) + .receive(on: DispatchQueue.main) + .map { $0 != nil && $1 != nil } + .assign(to: \.isCustomBaseNodeSet, on: mainView) + .store(in: &cancelables) + mainView.tokenView.$inputText .receive(on: DispatchQueue.main) .assign(to: \.inputText, on: model) @@ -139,7 +124,7 @@ final class RestoreWalletFromSeedsViewController: SettingsParentViewController, } mainView.selectBaseNodeButton.onTap = { [weak self] in - self?.moveToSelectBaseNodeScene() + self?.showCustomBaseNodeForm() } mainView.submitButton.onTap = { [weak self] in @@ -161,7 +146,9 @@ final class RestoreWalletFromSeedsViewController: SettingsParentViewController, show(overlay: overlay) } - private func moveToSelectBaseNodeScene() { - navigationController?.pushViewController(SelectBaseNodeViewController(), animated: true) + private func showCustomBaseNodeForm() { + FormOverlayPresenter.showSelectCustomBaseNodeForm(hex: model.viewModel.customBaseNodeHex, address: model.viewModel.customBaseNodeAddress, presenter: self) { [weak self] in + self?.model.updateCustomBaseNode(hex: $0, address: $1) + } } } diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/Views/TokenView.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/Views/TokenView.swift index ab6a3af7..db21efc3 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/Views/TokenView.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/Views/TokenView.swift @@ -53,7 +53,7 @@ final class TokenView: DynamicThemeCollectionCell { @View private var deleteIconView: UIImageView = { let view = UIImageView() - view.image = Theme.shared.images.cancelGrey + view.image = .Icons.General.roundedCancelButton return view }() diff --git a/MobileWallet/Screens/RestoreWalletFromSeedsProgress/SeedWordsRecoveryProgressViewController.swift b/MobileWallet/Screens/RestoreWalletFromSeedsProgress/SeedWordsRecoveryProgressViewController.swift index d6910fd6..9719a549 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeedsProgress/SeedWordsRecoveryProgressViewController.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeedsProgress/SeedWordsRecoveryProgressViewController.swift @@ -41,22 +41,17 @@ import UIKit import Combine -final class SeedWordsRecoveryProgressViewController: UIViewController { +final class SeedWordsRecoveryProgressViewController: SecureViewController { // MARK: - Properties var onSuccess: (() -> Void)? - private let mainView = SeedWordsRecoveryProgressView() private let model = SeedWordsRecoveryProgressModel() private var cancelables: Set = [] // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupFeedbacks() diff --git a/MobileWallet/Screens/Send/AddAmount/AddAmountViewController.swift b/MobileWallet/Screens/Send/AddAmount/AddAmountViewController.swift index 6110aaff..943f6b10 100644 --- a/MobileWallet/Screens/Send/AddAmount/AddAmountViewController.swift +++ b/MobileWallet/Screens/Send/AddAmount/AddAmountViewController.swift @@ -91,7 +91,7 @@ final class AddAmountViewController: DynamicThemeViewController { let view = TextButton() view.setVariation(.primary, font: .Avenir.medium.withSize(14.0)) view.spacing = 3.0 - view.setRightImage(Theme.shared.images.helpButton) + view.setRightImage(.Icons.General.roundedQuestionMark) return view }() @@ -115,7 +115,7 @@ final class AddAmountViewController: DynamicThemeViewController { @View private var oneSidedPaymentHelpButton: BaseButton = { let view = BaseButton() - view.setImage(Theme.shared.images.helpButton, for: .normal) + view.setImage(.Icons.General.roundedQuestionMark, for: .normal) return view }() @@ -264,7 +264,7 @@ final class AddAmountViewController: DynamicThemeViewController { override func update(theme: ColorTheme) { super.update(theme: theme) - view.backgroundColor = theme.backgrounds.primary + mainView.backgroundColor = theme.backgrounds.primary oneSidedPaymentLabel.textColor = theme.text.heading oneSidedPaymentSwitch.onTintColor = theme.brand.purple oneSidedPaymentHelpButton.tintColor = theme.text.body @@ -531,7 +531,7 @@ extension AddAmountViewController { // navigationBar navigationBar.isSeparatorVisible = false navigationBar.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(navigationBar) + mainView.addSubview(navigationBar) navigationBar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true @@ -542,7 +542,7 @@ extension AddAmountViewController { emojiIdView.centerYAnchor.constraint(equalTo: navigationBar.contentView.centerYAnchor).isActive = true // contiue button - view.addSubview(continueButton) + mainView.addSubview(continueButton) continueButton.translatesAutoresizingMaskIntoConstraints = false continueButton.leftAnchor.constraint( equalTo: view.safeAreaLayoutGuide.leftAnchor, @@ -575,7 +575,7 @@ extension AddAmountViewController { // amount container let amountContainer = UIView() amountContainer.backgroundColor = .clear - view.addSubview(amountContainer) + mainView.addSubview(amountContainer) amountContainer.translatesAutoresizingMaskIntoConstraints = false amountContainer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true amountContainer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true @@ -585,7 +585,7 @@ extension AddAmountViewController { let amountHeight: CGFloat = isSmallScreen ? 50.0 : 75.0 // amount label - view.addSubview(amountLabel) + mainView.addSubview(amountLabel) amountLabel.animation = .type amountLabel.textAlignment = .center(inset: -30) amountLabel.translatesAutoresizingMaskIntoConstraints = false @@ -595,7 +595,7 @@ extension AddAmountViewController { amountLabel.heightAnchor.constraint(equalToConstant: amountHeight).isActive = true // warning view - view.addSubview(warningView) + mainView.addSubview(warningView) warningView.isHidden = true warningView.translatesAutoresizingMaskIntoConstraints = false warningView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor, constant: -50).isActive = true @@ -655,7 +655,7 @@ extension AddAmountViewController { // tx fee txViewContainer.alpha = 0.0 - view.addSubview(txViewContainer) + mainView.addSubview(txViewContainer) txViewContainer.translatesAutoresizingMaskIntoConstraints = false txViewContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true txViewContainer.topAnchor.constraint(equalTo: amountLabel.bottomAnchor, constant: 12).isActive = true @@ -677,7 +677,7 @@ extension AddAmountViewController { } private func setupKeypad() { - view.addSubview(amountKeyboardView) + mainView.addSubview(amountKeyboardView) let constraints = [ amountKeyboardView.leadingAnchor.constraint(equalTo: view.leadingAnchor), @@ -701,7 +701,7 @@ extension AddAmountViewController { oneSidedPaymentLabel.minimumScaleFactor = 0.5 oneSidedPaymentLabel.adjustsFontSizeToFitWidth = true - view.addSubview(oneSidedPaymentStackView) + mainView.addSubview(oneSidedPaymentStackView) [oneSidedPaymentLabel, oneSidedPaymentSwitch, oneSidedPaymentHelpButton].forEach(oneSidedPaymentStackView.addArrangedSubview) let margin = isSmallScreen ? 8.0 : 20.0 @@ -725,7 +725,7 @@ extension AddAmountViewController { private func setupSliderBar() { - view.addSubview(sliderBar) + mainView.addSubview(sliderBar) let constraints = [ sliderBar.topAnchor.constraint(equalTo: continueButton.topAnchor), diff --git a/MobileWallet/Screens/Send/AddAmount/Views/NetworkTrafficView.swift b/MobileWallet/Screens/Send/AddAmount/Views/NetworkTrafficView.swift index ca3b8319..933c7a32 100644 --- a/MobileWallet/Screens/Send/AddAmount/Views/NetworkTrafficView.swift +++ b/MobileWallet/Screens/Send/AddAmount/Views/NetworkTrafficView.swift @@ -111,11 +111,11 @@ final class NetworkTrafficView: DynamicThemeView { private func updateIcon() { switch variant { case .lowTraffic: - iconView.image = Theme.shared.images.speedometerLow + iconView.image = .Icons.Fees.Speedometer.low case .mediumTraffic: - iconView.image = Theme.shared.images.speedometerMid + iconView.image = .Icons.Fees.Speedometer.mid case .highTraffic: - iconView.image = Theme.shared.images.speedometerHigh + iconView.image = .Icons.Fees.Speedometer.high } } } diff --git a/MobileWallet/Screens/Send/AddAmount/Views/PopUpModifyFeeContentView.swift b/MobileWallet/Screens/Send/AddAmount/Views/PopUpModifyFeeContentView.swift index f335d83a..6c5fbf00 100644 --- a/MobileWallet/Screens/Send/AddAmount/Views/PopUpModifyFeeContentView.swift +++ b/MobileWallet/Screens/Send/AddAmount/Views/PopUpModifyFeeContentView.swift @@ -45,7 +45,7 @@ final class PopUpModifyFeeContentView: DynamicThemeView { // MARK: - Subviews - @View private(set) var segmentedControl = TariSegmentedControl(icons: [Theme.shared.images.speedometerLow, Theme.shared.images.speedometerMid, Theme.shared.images.speedometerHigh]) + @View private(set) var segmentedControl = TariSegmentedControl(icons: [.Icons.Fees.Speedometer.low, .Icons.Fees.Speedometer.mid, .Icons.Fees.Speedometer.high]) @View private var estimatedFeeTitleLabel: UILabel = { let view = UILabel() diff --git a/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift b/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift index 23c9496b..cc1cff6e 100644 --- a/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift +++ b/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift @@ -177,7 +177,7 @@ final class AddNoteViewController: DynamicThemeViewController, UIScrollViewDeleg setupGiphy() scrollView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(scrollView) + mainView.addSubview(scrollView) scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true @@ -277,7 +277,7 @@ final class AddNoteViewController: DynamicThemeViewController, UIScrollViewDeleg override func update(theme: ColorTheme) { super.update(theme: theme) - view.backgroundColor = theme.backgrounds.primary + mainView.backgroundColor = theme.backgrounds.primary searchGiphyButton.backgroundColor = theme.icons.default searchGiphyButton.tintColor = theme.neutral.primary @@ -296,7 +296,7 @@ final class AddNoteViewController: DynamicThemeViewController, UIScrollViewDeleg extension AddNoteViewController { private func setupNavigationBar() { - view.addSubview(navigationBar) + mainView.addSubview(navigationBar) navigationBar.addSubview(emojiIdView) navigationBar.isSeparatorVisible = false @@ -325,7 +325,7 @@ extension AddNoteViewController { sendButton.isEnabled = false sendButton.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(sendButton) + mainView.addSubview(sendButton) sendButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 25.0).isActive = true sendButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -25.0).isActive = true sendButtonBottomConstraint = sendButton.bottomAnchor.constraint(equalTo: view.safeBottomAnchor) @@ -397,7 +397,7 @@ extension AddNoteViewController { let giphyVC = GiphyGridController() giphyCarouselContainerView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(giphyCarouselContainerView) + mainView.addSubview(giphyCarouselContainerView) giphyCarouselContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: giffPadding).isActive = true giphyCarouselContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -giffPadding).isActive = true giphyCarouselBottomConstraint = giphyCarouselContainerView.bottomAnchor.constraint(equalTo: sendButton.topAnchor, constant: -(giffPadding * 2)) diff --git a/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift b/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift index 7d8f844c..87d5790e 100644 --- a/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift +++ b/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift @@ -82,8 +82,8 @@ final class AddRecipientModel { @Published private var contactModels: [ContactsManager.Model] = [] private weak var bleTask: BLECentralTask? - private var incomingUserProfile: UserProfileDeeplink? + private var incomingUserProfile: UserProfileDeeplink? private var contactDictornary: [UUID: ContactsManager.Model] = [:] private var cancellables = Set() @@ -174,15 +174,15 @@ final class AddRecipientModel { if let rawAmount = deeplink.amount { amount = MicroTari(rawAmount) } - action = .sendTokens(paymentInfo: PaymentInfo(address: deeplink.receiverAddress, alias: nil, yatID: nil, amount: amount, feePerGram: nil, note: deeplink.note)) + handleAddressSelection(paymentInfo: PaymentInfo(address: deeplink.receiverAddress, alias: nil, yatID: nil, amount: amount, feePerGram: nil, note: deeplink.note)) } else if let deeplink = deeplink as? UserProfileDeeplink { - action = .sendTokens(paymentInfo: PaymentInfo(address: deeplink.tariAddress, alias: deeplink.alias, yatID: nil, amount: nil, feePerGram: nil, note: nil)) + handleAddressSelection(paymentInfo: PaymentInfo(address: deeplink.tariAddress, alias: deeplink.alias, yatID: nil, amount: nil, feePerGram: nil, note: nil)) } } func select(elementID: UUID) { guard let model = contactDictornary[elementID]?.internalModel else { return } - action = .sendTokens(paymentInfo: PaymentInfo(address: model.hex, alias: nil, yatID: yatID, amount: nil, feePerGram: nil, note: nil)) + handleAddressSelection(paymentInfo: PaymentInfo(address: model.hex, alias: nil, yatID: yatID, amount: nil, feePerGram: nil, note: nil)) } func fetchTransactionDataViaBLE() { @@ -210,14 +210,14 @@ final class AddRecipientModel { } func confirmIncomingTransaction() { + guard let incomingUserProfile else { action = .show(dialog: .bleFailureDialog(message: ErrorMessageManager.errorMessage(forError: nil))) return } - let paymentInfo = PaymentInfo(address: incomingUserProfile.tariAddress, alias: incomingUserProfile.alias, yatID: nil, amount: nil, feePerGram: nil, note: nil) self.incomingUserProfile = nil - action = .sendTokens(paymentInfo: paymentInfo) + handleAddressSelection(paymentInfo: PaymentInfo(address: incomingUserProfile.tariAddress, alias: incomingUserProfile.alias, yatID: nil, amount: nil, feePerGram: nil, note: nil)) } func cancelIncomingTransaction() { @@ -236,11 +236,15 @@ final class AddRecipientModel { return } - action = .sendTokens(paymentInfo: PaymentInfo(address: hex, alias: nil, yatID: yatID, amount: nil, feePerGram: nil, note: nil)) + handleAddressSelection(paymentInfo: PaymentInfo(address: hex, alias: nil, yatID: yatID, amount: nil, feePerGram: nil, note: nil)) } // MARK: - Handlers + private func handleAddressSelection(paymentInfo: PaymentInfo) { + action = .sendTokens(paymentInfo: paymentInfo) + } + private func filter(contacts: [[ContactsManager.Model]], searchText: String) -> [[ContactsManager.Model]] { guard !searchText.isEmpty else { return contacts } diff --git a/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift b/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift index aead5547..0b2877e5 100644 --- a/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift +++ b/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift @@ -216,7 +216,7 @@ final class AddRecipientView: DynamicThemeView { snapshot.appendItems($1.items) } - dataSource?.apply(snapshot: snapshot) + dataSource?.applySnapshotUsingReloadData(snapshot) } private func updateSearchFieldState() { diff --git a/MobileWallet/Screens/Send/SendingTari/SendingTariViewController.swift b/MobileWallet/Screens/Send/SendingTari/SendingTariViewController.swift index 3b1c8ad3..fb97cefc 100644 --- a/MobileWallet/Screens/Send/SendingTari/SendingTariViewController.swift +++ b/MobileWallet/Screens/Send/SendingTari/SendingTariViewController.swift @@ -41,15 +41,13 @@ import UIKit import Combine -final class SendingTariViewController: UIViewController, TransactionViewControllable { +final class SendingTariViewController: SecureViewController, TransactionViewControllable { // MARK: - Properties var onCompletion: ((WalletTransactionsManager.TransactionError?) -> Void)? - private let mainView = SendingTariView() private let model: SendingTariModel - private var cancelables = Set() // MARK: - Initialisers @@ -66,10 +64,6 @@ final class SendingTariViewController: UIViewController, TransactionViewControll // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() runBackgroundAnimation() diff --git a/MobileWallet/Screens/Send/Transactions/TransactionsViewController.swift b/MobileWallet/Screens/Send/Transactions/TransactionsViewController.swift index 4ec0e05e..020523fe 100644 --- a/MobileWallet/Screens/Send/Transactions/TransactionsViewController.swift +++ b/MobileWallet/Screens/Send/Transactions/TransactionsViewController.swift @@ -41,7 +41,7 @@ import UIKit import Combine -final class TransactionsViewController: UIViewController { +final class TransactionsViewController: SecureViewController { fileprivate enum Page: Int { case send @@ -50,7 +50,6 @@ final class TransactionsViewController: UIViewController { // MARK: - Properties - private let mainView = TransactionsView() private let pagerViewController = TariPagerViewController() private let addRecipientViewController = AddRecipientConstructor.buildScene() private let requestTariAmountViewController = RequestTariAmountViewController() @@ -59,10 +58,6 @@ final class TransactionsViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupViews() @@ -84,7 +79,7 @@ final class TransactionsViewController: UIViewController { private func setupCallbacks() { addRecipientViewController.onContactSelected = { [weak self] in - self?.moveToAddAmount(paymentInfo: $0) + AppRouter.presentSendTransaction(paymentInfo: $0, presenter: self?.navigationController) } pagerViewController.pageIndex @@ -97,13 +92,6 @@ final class TransactionsViewController: UIViewController { private func updateTitle(index: Int) { mainView.navigationBar.title = Page(rawValue: index)?.navBarTitle } - - // MARK: - Actions - - private func moveToAddAmount(paymentInfo: PaymentInfo) { - let controller = AddAmountViewController(paymentInfo: paymentInfo) - navigationController?.pushViewController(controller, animated: true) - } } private extension TransactionsViewController.Page { diff --git a/MobileWallet/Screens/Send/YatTransaction/YatTransactionViewController.swift b/MobileWallet/Screens/Send/YatTransaction/YatTransactionViewController.swift index 1298e05c..7a69b44b 100644 --- a/MobileWallet/Screens/Send/YatTransaction/YatTransactionViewController.swift +++ b/MobileWallet/Screens/Send/YatTransaction/YatTransactionViewController.swift @@ -41,13 +41,11 @@ import UIKit import Combine -final class YatTransactionViewController: UIViewController, TransactionViewControllable { +final class YatTransactionViewController: SecureViewController, TransactionViewControllable { var onCompletion: ((WalletTransactionsManager.TransactionError?) -> Void)? - private let mainView = YatTransactionView() private let model: YatTransactionModel - private var cancellables = Set() init(model: YatTransactionModel) { @@ -60,10 +58,6 @@ final class YatTransactionViewController: UIViewController, TransactionViewContr fatalError("init(coder:) has not been implemented") } - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupFeedbacks() diff --git a/MobileWallet/Screens/Settings/BackUpSettings/SecureBackupViewController.swift b/MobileWallet/Screens/Settings/BackUpSettings/SecureBackupViewController.swift index 996c6ca1..517bc0df 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/SecureBackupViewController.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/SecureBackupViewController.swift @@ -41,7 +41,7 @@ import UIKit import Combine -class SecureBackupViewController: SettingsParentViewController { +final class SecureBackupViewController: SettingsParentViewController { private let scrollView = UIScrollView() private let stackView = UIStackView() private let headerLabel = UILabel() @@ -243,7 +243,7 @@ extension SecureBackupViewController { scrollView.showsVerticalScrollIndicator = false scrollView.backgroundColor = .clear - view.addSubview(scrollView) + mainView.addSubview(scrollView) scrollView.translatesAutoresizingMaskIntoConstraints = false scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true @@ -268,7 +268,7 @@ extension SecureBackupViewController { continueButton.addTarget(self, action: #selector(continueButtonAction), for: .touchUpInside) continueButton.variation = .disabled - view.addSubview(continueButton) + mainView.addSubview(continueButton) continueButton.translatesAutoresizingMaskIntoConstraints = false continueButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, diff --git a/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListViewController.swift b/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListViewController.swift index 70ac62c7..e53e02c4 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListViewController.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListViewController.swift @@ -41,13 +41,11 @@ import UIKit import Combine -final class SeedWordsListViewController: UIViewController { +final class SeedWordsListViewController: SecureViewController { // MARK: - Properties - private let mainView = SeedWordsListView() private let model: SeedWordsListModel - private var cancellables = Set() // MARK: - Initialisers @@ -64,14 +62,11 @@ final class SeedWordsListViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() model.fetchSeedWords() + screenshotPreventionStatus = .enforced } override func viewDidAppear(_ animated: Bool) { diff --git a/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/Views/ExpandButton.swift b/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/Views/ExpandButton.swift index be5d465d..dd3eb351 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/Views/ExpandButton.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/Views/ExpandButton.swift @@ -53,13 +53,13 @@ final class ExpandButton: DynamicThemeView { @View private var topArrowView: UIImageView = { let view = UIImageView() - view.image = Theme.shared.images.expandButtonArrow + view.image = .Icons.SeedWordsList.expandButtonArrow return view }() @View private var bottomArrowView: UIImageView = { let view = UIImageView() - view.image = Theme.shared.images.expandButtonArrow + view.image = .Icons.SeedWordsList.expandButtonArrow return view }() diff --git a/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsViewController.swift b/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsViewController.swift index c33112d5..fddffbf6 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsViewController.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsViewController.swift @@ -41,12 +41,11 @@ import UIKit import Combine -final class BackupWalletSettingsViewController: UIViewController { +final class BackupWalletSettingsViewController: SecureViewController { // MARK: - Properties private let model: BackupWalletSettingsModel - private let mainView = BackupWalletSettingsView() private let seedWordsItem = SystemMenuTableViewCellItem(title: localized("backup_wallet_settings.item.with_recovery_phrase")) private let iCloudItem = SystemMenuTableViewCellItem(title: localized("backup_wallet_settings.item.icloud_backups"), hasArrow: false, hasSwitch: true) private let dropboxItem = SystemMenuTableViewCellItem(title: localized("backup_wallet_settings.item.dropbox_backups"), hasArrow: false, hasSwitch: true) @@ -71,10 +70,6 @@ final class BackupWalletSettingsViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() @@ -186,7 +181,7 @@ final class BackupWalletSettingsViewController: UIViewController { var items = [seedWordsItem] - if !AppValues.isSimulator { + if !AppValues.general.isSimulator { items.append(iCloudItem) } diff --git a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift index 61a170ff..9e1ecbeb 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift @@ -41,13 +41,11 @@ import UIKit import Combine -final class VerifySeedWordsViewController: UIViewController { +final class VerifySeedWordsViewController: SecureViewController { // MARK: - Properties - private let mainView = VerifySeedWordsView() private let model: VerifySeedWordsModel - private var cancellables = Set() // MARK: - Initialisers @@ -63,10 +61,6 @@ final class VerifySeedWordsViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Settings/Bluetooth Settings/BluetoothSettingsView.swift b/MobileWallet/Screens/Settings/Bluetooth Settings/BluetoothSettingsView.swift index e4f0a705..5c19306a 100644 --- a/MobileWallet/Screens/Settings/Bluetooth Settings/BluetoothSettingsView.swift +++ b/MobileWallet/Screens/Settings/Bluetooth Settings/BluetoothSettingsView.swift @@ -123,7 +123,7 @@ final class BluetoothSettingsView: BaseNavigationContentView { snapshot.appendItems($1.items) } - dataSource?.apply(snapshot: snapshot) + dataSource?.applySnapshotUsingReloadData(snapshot) } // MARK: - Layout diff --git a/MobileWallet/Screens/Settings/Bluetooth Settings/BluetoothSettingsViewController.swift b/MobileWallet/Screens/Settings/Bluetooth Settings/BluetoothSettingsViewController.swift index c22c9605..95a99728 100644 --- a/MobileWallet/Screens/Settings/Bluetooth Settings/BluetoothSettingsViewController.swift +++ b/MobileWallet/Screens/Settings/Bluetooth Settings/BluetoothSettingsViewController.swift @@ -41,12 +41,11 @@ import UIKit import Combine -final class BluetoothSettingsViewController: UIViewController { +final class BluetoothSettingsViewController: SecureViewController { // MARK: - Properties private let model: BluetoothSettingsModel - private let mainView = BluetoothSettingsView() private var cancellables = Set() // MARK: - Initialisers @@ -62,10 +61,6 @@ final class BluetoothSettingsViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Settings/Bug Reporting/BugReportingViewController.swift b/MobileWallet/Screens/Settings/Bug Reporting/BugReportingViewController.swift index 692a542b..fd44e661 100644 --- a/MobileWallet/Screens/Settings/Bug Reporting/BugReportingViewController.swift +++ b/MobileWallet/Screens/Settings/Bug Reporting/BugReportingViewController.swift @@ -41,12 +41,11 @@ import UIKit import Combine -final class BugReportingViewController: UIViewController { +final class BugReportingViewController: SecureViewController { // MARK: - Properties private let model: BugReportingModel - private let mainView = BugReportingView() private var cancellables = Set() // MARK: - Initialisers @@ -62,10 +61,6 @@ final class BugReportingViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsView.swift b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsView.swift index 415ad4b6..bbcdf463 100644 --- a/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsView.swift +++ b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsView.swift @@ -129,7 +129,7 @@ final class DataCollectionSettingsView: BaseNavigationContentView { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([0]) snapshot.appendItems([0]) - dataSource?.apply(snapshot: snapshot) + dataSource?.applySnapshotUsingReloadData(snapshot) } // MARK: - Updates diff --git a/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsViewController.swift b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsViewController.swift index c8811717..aab1e2c7 100644 --- a/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsViewController.swift +++ b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsViewController.swift @@ -41,12 +41,11 @@ import UIKit import Combine -final class DataCollectionViewController: UIViewController { +final class DataCollectionViewController: SecureViewController { // MARK: - Properties private let model: DataCollectionSettingsModel - private let mainView = DataCollectionSettingsView() private var cancellables = Set() // MARK: - Initialisers @@ -62,10 +61,6 @@ final class DataCollectionViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Settings/MoreSettings/AboutModel.swift b/MobileWallet/Screens/Settings/MoreSettings/AboutModel.swift index 489838fd..166cfd34 100644 --- a/MobileWallet/Screens/Settings/MoreSettings/AboutModel.swift +++ b/MobileWallet/Screens/Settings/MoreSettings/AboutModel.swift @@ -58,71 +58,71 @@ final class AboutModel { private let rowsData: [RowData] = [ RowData( - model: RowModel(icon: Theme.shared.images.settingsWalletBackupsIcon, title: "Locked Lock by BlackActurus from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.walletBackups, title: "Locked Lock by BlackActurus from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/locked-lock-3734872/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsAboutIcon, title: "About by Anggara Putra from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.about, title: "About by Anggara Putra from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/about-4860865/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsReportBugIcon, title: "Speech Bubbles by Design Circle from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.reportBug, title: "Speech Bubbles by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/speech-bubbles-4213155/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsContributeIcon, title: "Keyboard by Design Circle from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.contribute, title: "Keyboard by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/keyboard-4213010/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsUserAgreementIcon, title: "Writing by Design Circle from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.userAgreement, title: "Writing by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/writing-4213261/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsPrivacyPolicyIcon, title: "Privacy by Gregor Cresnar from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.privacyPolicy, title: "Privacy by Gregor Cresnar from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/privacy-1381695/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsDisclaimerIcon, title: "Bullhorn by Design Circle from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.disclaimer, title: "Bullhorn by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/bullhorn-4213159/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsBlockExplorerIcon, title: "Magnifier by Design Circle from\nNounProject.com (Modified)"), + model: RowModel(icon: .Icons.Settings.blockExplorer, title: "Magnifier by Design Circle from\nNounProject.com (Modified)"), url: URL(string: "https://thenounproject.com/icon/magnifier-4213152/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingColorThemeIcon, title: "Theme by icon 54 from\nNoun Project.com"), + model: RowModel(icon: .Icons.Settings.theme, title: "Theme by icon 54 from\nNoun Project.com"), url: URL(string: "https://thenounproject.com/icon/theme-396505/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsBridgeConfigIcon, title: "Repair Tools by Design Circle from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.bridgeConfig, title: "Repair Tools by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/repair-tools-4213156/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsNetworkIcon, title: "Internet Server by Design Circle from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.network, title: "Internet Server by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/internet-server-4213144/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsBaseNodeIcon, title: "Networking by Design Circle from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.baseNode, title: "Networking by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/networking-4213263/") ), RowData( - model: RowModel(icon: Theme.shared.images.settingsDeleteIcon, title: "Delete by Maya Nurhayati from\nNounProject.com"), + model: RowModel(icon: .Icons.Settings.delete, title: "Delete by Maya Nurhayati from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/delete-4727971/") ), RowData( - model: RowModel(icon: .security.onboarding.page1, title: "Write by Mada Creative from NounProject.com"), + model: RowModel(icon: .Images.Security.Onboarding.page1, title: "Write by Mada Creative from NounProject.com"), url: URL(string: "https://thenounproject.com/icon/write-4207866/") ), RowData( - model: RowModel(icon: .security.onboarding.page2, title: "Cloud by Tippawan Sookruay from NounProject.com"), + model: RowModel(icon: .Images.Security.Onboarding.page2, title: "Cloud by Tippawan Sookruay from NounProject.com"), url: URL(string: "https://thenounproject.com/icon/cloud-3384041/") ), RowData( - model: RowModel(icon: .security.onboarding.page3, title: "Password by Tippawan Sookruay from NounProject.com"), + model: RowModel(icon: .Images.Security.Onboarding.page3, title: "Password by Tippawan Sookruay from NounProject.com"), url: URL(string: "https://thenounproject.com/icon/password-3384056/") ), RowData( - model: RowModel(icon: .security.onboarding.page4, title: "Key by Tippawan Sookruay from NounProject.com"), + model: RowModel(icon: .Images.Security.Onboarding.page4, title: "Key by Tippawan Sookruay from NounProject.com"), url: URL(string: "https://thenounproject.com/icon/key-3384048/") ) ] diff --git a/MobileWallet/Screens/Settings/MoreSettings/AboutViewController.swift b/MobileWallet/Screens/Settings/MoreSettings/AboutViewController.swift index 59703cb3..734f1329 100644 --- a/MobileWallet/Screens/Settings/MoreSettings/AboutViewController.swift +++ b/MobileWallet/Screens/Settings/MoreSettings/AboutViewController.swift @@ -41,17 +41,11 @@ import UIKit import Combine -final class AboutViewController: UIViewController { +final class AboutViewController: SecureViewController { - private let mainView = AboutView() private let model = AboutModel() - private var cancellables = Set() - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() diff --git a/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift b/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift index ba3b1c4d..3a4caf43 100644 --- a/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift +++ b/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift @@ -38,7 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import TariCommon class SettingsParentTableViewController: SettingsParentViewController { @@ -88,7 +87,7 @@ extension SettingsParentTableViewController { func setupTableView() { tableView.register(type: SystemMenuTableViewCell.self) - view.addSubview(tableView) + mainView.addSubview(tableView) let constraints = [ tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), diff --git a/MobileWallet/Screens/Settings/SettingsParentViewController.swift b/MobileWallet/Screens/Settings/SettingsParentViewController.swift index 2aef4d56..4f29b71a 100644 --- a/MobileWallet/Screens/Settings/SettingsParentViewController.swift +++ b/MobileWallet/Screens/Settings/SettingsParentViewController.swift @@ -58,7 +58,7 @@ class SettingsParentViewController: DynamicThemeViewController { private func setupConstraints() { - view.addSubview(navigationBar) + mainView.addSubview(navigationBar) let constraints = [ navigationBar.topAnchor.constraint(equalTo: view.topAnchor), @@ -71,7 +71,7 @@ class SettingsParentViewController: DynamicThemeViewController { override func update(theme: ColorTheme) { super.update(theme: theme) - view.backgroundColor = theme.backgrounds.primary + mainView.backgroundColor = theme.backgrounds.primary } } diff --git a/MobileWallet/Screens/Settings/SettingsViewController.swift b/MobileWallet/Screens/Settings/SettingsViewController.swift index 7219b1ea..b1466234 100644 --- a/MobileWallet/Screens/Settings/SettingsViewController.swift +++ b/MobileWallet/Screens/Settings/SettingsViewController.swift @@ -84,6 +84,7 @@ final class SettingsViewController: SettingsParentTableViewController { case blockExplorer case selectTheme + case screenRecording case bluetoothConfiguration case torBridgeConfiguration case selectNetwork @@ -95,8 +96,9 @@ final class SettingsViewController: SettingsParentTableViewController { case .backUpWallet: return localized("settings.item.wallet_backups") case .dataCollection: return localized("settings.item.data_collection") - case .bluetoothConfiguration: return localized("settings.item.bluetooth_settings") case .selectTheme: return localized("settings.item.select_theme") + case .screenRecording: return localized("settings.item.screen_recording_settings") + case .bluetoothConfiguration: return localized("settings.item.bluetooth_settings") case .torBridgeConfiguration: return localized("settings.item.bridge_configuration") case .selectNetwork: return localized("settings.item.select_network") case .selectBaseNode: return localized("settings.item.select_base_node") @@ -114,35 +116,38 @@ final class SettingsViewController: SettingsParentTableViewController { } } - private let backUpWalletItem = SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsWalletBackupsIcon, title: SettingsItemTitle.backUpWallet.rawValue, disableCellInProgress: false) + private let backUpWalletItem = SystemMenuTableViewCellItem(icon: .Icons.Settings.walletBackups, title: SettingsItemTitle.backUpWallet.rawValue, disableCellInProgress: false) + private let screenRecordingItem = SystemMenuTableViewCellItem(icon: .Icons.Settings.camera, title: SettingsItemTitle.screenRecording.rawValue) private lazy var securitySectionItems: [SystemMenuTableViewCellItem] = [ backUpWalletItem, - SystemMenuTableViewCellItem(icon: .icons.analytics, title: SettingsItemTitle.dataCollection.rawValue) + SystemMenuTableViewCellItem(icon: .Icons.General.analytics, title: SettingsItemTitle.dataCollection.rawValue) ] - private let advancedSettingsSectionItems: [SystemMenuTableViewCellItem] = [ - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingColorThemeIcon, title: SettingsItemTitle.selectTheme.rawValue), - SystemMenuTableViewCellItem(icon: .icons.settings.bluetooth, title: SettingsItemTitle.bluetoothConfiguration.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsBridgeConfigIcon, title: SettingsItemTitle.torBridgeConfiguration.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsNetworkIcon, title: SettingsItemTitle.selectNetwork.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsBaseNodeIcon, title: SettingsItemTitle.selectBaseNode.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsDeleteIcon, title: SettingsItemTitle.deleteWallet.rawValue, isDestructive: true) + private lazy var advancedSettingsSectionItems: [SystemMenuTableViewCellItem] = [ + SystemMenuTableViewCellItem(icon: .Icons.Settings.theme, title: SettingsItemTitle.selectTheme.rawValue), + screenRecordingItem, + SystemMenuTableViewCellItem(icon: .Icons.Settings.bluetooth, title: SettingsItemTitle.bluetoothConfiguration.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.bridgeConfig, title: SettingsItemTitle.torBridgeConfiguration.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.network, title: SettingsItemTitle.selectNetwork.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.baseNode, title: SettingsItemTitle.selectBaseNode.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.delete, title: SettingsItemTitle.deleteWallet.rawValue, isDestructive: true) ] private let moreSectionItems: [SystemMenuTableViewCellItem] = { + var items = [ - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsAboutIcon, title: SettingsItemTitle.about.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsReportBugIcon, title: SettingsItemTitle.reportBug.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsVisitTariIcon, title: SettingsItemTitle.visitTari.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsContributeIcon, title: SettingsItemTitle.contributeToTariAurora.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsUserAgreementIcon, title: SettingsItemTitle.userAgreement.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsPrivacyPolicyIcon, title: SettingsItemTitle.privacyPolicy.rawValue), - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsDisclaimerIcon, title: SettingsItemTitle.disclaimer.rawValue) + SystemMenuTableViewCellItem(icon: .Icons.Settings.about, title: SettingsItemTitle.about.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.reportBug, title: SettingsItemTitle.reportBug.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.visitTari, title: SettingsItemTitle.visitTari.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.contribute, title: SettingsItemTitle.contributeToTariAurora.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.userAgreement, title: SettingsItemTitle.userAgreement.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.privacyPolicy, title: SettingsItemTitle.privacyPolicy.rawValue), + SystemMenuTableViewCellItem(icon: .Icons.Settings.disclaimer, title: SettingsItemTitle.disclaimer.rawValue), ] - if TariSettings.shared.isBlockExplorerAvaiable { - items.append(SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsBlockExplorerIcon, title: SettingsItemTitle.blockExplorer.rawValue)) + if NetworkManager.shared.selectedNetwork.isBlockExplorerAvailable { + items.append(SystemMenuTableViewCellItem(icon: .Icons.Settings.blockExplorer, title: SettingsItemTitle.blockExplorer.rawValue)) } return items @@ -154,7 +159,7 @@ final class SettingsViewController: SettingsParentTableViewController { .userAgreement: URL(string: TariSettings.shared.userAgreementUrl), .privacyPolicy: URL(string: TariSettings.shared.privacyPolicyUrl), .disclaimer: URL(string: TariSettings.shared.disclaimer), - .blockExplorer: URL(string: TariSettings.shared.blockExplorerUrl) + .blockExplorer: NetworkManager.shared.selectedNetwork.blockExplorerURL ] private let profileIndexPath = IndexPath(row: 0, section: 0) @@ -175,10 +180,16 @@ final class SettingsViewController: SettingsParentTableViewController { } private func setupCallbacks() { + BackupManager.shared.$syncState .receive(on: DispatchQueue.main) .sink { [weak self] in self?.updateItems(syncStatus: $0) } .store(in: &cancellables) + + SecurityManager.shared.$areScreenshotsDisabled + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.screenRecordingItem.mark = $0 ? .none : .attention } + .store(in: &cancellables) } private func onBackupWalletAction() { @@ -208,6 +219,11 @@ final class SettingsViewController: SettingsParentTableViewController { navigationController?.pushViewController(controller, animated: true) } + private func onScreenRecordingSettingsAction() { + let controller = ScreenRecordingSettingsConstructor.buildScene() + navigationController?.pushViewController(controller, animated: true) + } + private func onBluetoothSettingsAction() { let controller = BluetoothSettingsConstructor.buildScene() navigationController?.pushViewController(controller, animated: true) @@ -400,14 +416,16 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { case 0: onSelectThemeAction() case 1: - onBluetoothSettingsAction() + onScreenRecordingSettingsAction() case 2: - onBridgeConfigurationAction() + onBluetoothSettingsAction() case 3: - onSelectNetworkAction() + onBridgeConfigurationAction() case 4: - onSelectBaseNodeAction() + onSelectNetworkAction() case 5: + onSelectBaseNodeAction() + case 6: onDeleteWalletAction() default: break diff --git a/MobileWallet/Screens/Settings/Theme Settings/ThemeSettingsViewController.swift b/MobileWallet/Screens/Settings/Theme Settings/ThemeSettingsViewController.swift index cde6f744..b80ccad6 100644 --- a/MobileWallet/Screens/Settings/Theme Settings/ThemeSettingsViewController.swift +++ b/MobileWallet/Screens/Settings/Theme Settings/ThemeSettingsViewController.swift @@ -41,13 +41,11 @@ import UIKit import Combine -final class ThemeSettingsViewController: UIViewController { +final class ThemeSettingsViewController: SecureViewController { // MARK: - Properties - private let mainView = ThemeSettingsView() private let model: ThemeSettingsModel - private var cancellables = Set() // MARK: - Initalisers @@ -63,10 +61,6 @@ final class ThemeSettingsViewController: UIViewController { // MARK: - View Lifecycle - override func loadView() { - view = mainView - } - override func viewDidLoad() { super.viewDidLoad() setupCallbacks() @@ -93,16 +87,16 @@ final class ThemeSettingsViewController: UIViewController { private extension ThemeSettingsModel.Element { - var image: UIImage? { + var image: UIImage { switch self { case .system: - return Theme.shared.images.colorThemeSystem + return .Images.Themes.system case .light: - return Theme.shared.images.colorThemeLight + return .Images.Themes.light case .dark: - return Theme.shared.images.colorThemeDark + return .Images.Themes.dark case .purple: - return Theme.shared.images.colorThemePurple + return .Images.Themes.purple } } diff --git a/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift b/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift index 00b79420..41e7f17a 100644 --- a/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift +++ b/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift @@ -63,7 +63,7 @@ final class SettingsProfileCell: DynamicThemeCell { @View private var scanImage: UIImageView = { let view = UIImageView() - view.image = .icons.qr + view.image = .Icons.General.QR view.contentMode = .scaleAspectFit return view }() diff --git a/MobileWallet/Screens/Settings/Views/SettingsViewFooter.swift b/MobileWallet/Screens/Settings/Views/SettingsViewFooter.swift index a80094c5..f7aaea36 100644 --- a/MobileWallet/Screens/Settings/Views/SettingsViewFooter.swift +++ b/MobileWallet/Screens/Settings/Views/SettingsViewFooter.swift @@ -45,7 +45,7 @@ final class SettingsViewFooter: DynamicThemeView { @View private var label: UILabel = { let view = UILabel() - view.font = Theme.shared.fonts.systemTableViewCell + view.font = .Avenir.medium.withSize(15.0) view.text = AppVersionFormatter.version return view }() diff --git a/MobileWallet/UIElements/Buttons/SlideView.swift b/MobileWallet/UIElements/Buttons/SlideView.swift index 7b24bd3b..dba85d22 100644 --- a/MobileWallet/UIElements/Buttons/SlideView.swift +++ b/MobileWallet/UIElements/Buttons/SlideView.swift @@ -293,7 +293,7 @@ final class SlideView: DynamicThemeView { private func updateSliderState(theme: ColorTheme) { sliderTextLabel.textColor = isEnabled ? .clear : theme.buttons.disabled sliderHolderView.backgroundColor = isEnabled ? .clear : theme.buttons.disabled - textLabel.textColor = isEnabled ? .static.white : theme.buttons.disabledText + textLabel.textColor = isEnabled ? .Static.white : theme.buttons.disabledText thumbnailImageView.backgroundColor = isEnabled ? theme.buttons.primaryText : theme.buttons.disabledText thumbnailImageView.tintColor = isEnabled ? theme.brand.purple : theme.buttons.disabled diff --git a/MobileWallet/UIElements/EmojiIdView.swift b/MobileWallet/UIElements/EmojiIdView.swift index eed84c9f..02ebd23e 100644 --- a/MobileWallet/UIElements/EmojiIdView.swift +++ b/MobileWallet/UIElements/EmojiIdView.swift @@ -36,7 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit +import TariCommon final class EmojiIdView: DynamicThemeView { @@ -48,7 +48,7 @@ final class EmojiIdView: DynamicThemeView { weak var blackoutParent: UIView? private lazy var blackoutView: UIView = { let view = UIView() - view.backgroundColor = .static.popupOverlay + view.backgroundColor = .Static.popupOverlay guard let bounds = UIApplication.shared.firstWindow?.bounds else { return view } view.frame = bounds view.alpha = 0.0 @@ -97,6 +97,36 @@ final class EmojiIdView: DynamicThemeView { } private weak var superVC: UIViewController? + @View private var secureContentView = SecureWrapperView() + + // MARK: - Initialisers + + override init() { + super.init() + setupSecureContentView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupSecureContentView() { + + guard let window = UIApplication.shared.firstWindow else { return } + window.addSubview(secureContentView) + + let constraints = [ + secureContentView.topAnchor.constraint(equalTo: window.topAnchor), + secureContentView.leadingAnchor.constraint(equalTo: window.leadingAnchor), + secureContentView.trailingAnchor.constraint(equalTo: window.trailingAnchor), + secureContentView.bottomAnchor.constraint(equalTo: window.bottomAnchor) + ] + + secureContentView.isHidden = true + NSLayoutConstraint.activate(constraints) + } // MARK: - Updates @@ -123,7 +153,7 @@ final class EmojiIdView: DynamicThemeView { private func updateCopyView(theme: ColorTheme) { copiedLabel?.textColor = theme.text.links containerView?.layer.borderColor = theme.system.green?.cgColor - containerView?.backgroundColor = .static.white?.withAlphaComponent(0.75) + containerView?.backgroundColor = .Static.white.withAlphaComponent(0.75) greenView?.backgroundColor = theme.system.green?.withAlphaComponent(0.12) } @@ -249,6 +279,8 @@ final class EmojiIdView: DynamicThemeView { func expand(completion: (() -> Void)? = nil, callTapCompletion: Bool = false, animated: Bool = true) { guard let scrollViewFrame = condensedEmojiIdContainer.globalFrame else { return } + secureContentView.isHidden = false + secureContentView.isUserInteractionEnabled = blackoutWhileExpanded tapActionIsDisabled = true expanded = true // If they're typing somewhere, close the keyboard @@ -257,7 +289,7 @@ final class EmojiIdView: DynamicThemeView { // fade in blackout fadeView(view: condensedEmojiIdContainer, fadeOut: true) if blackoutWhileExpanded { - UIApplication.shared.firstWindow?.addSubview(blackoutView) + addViewToSecureContentView(view: blackoutView) fadeView(view: blackoutView, fadeOut: false, maxAlpha: 0.65) showCopyEmojiIdButton() showHexPubKeyCopyTip() @@ -273,7 +305,7 @@ final class EmojiIdView: DynamicThemeView { width: scrollViewTargetWidth, height: scrollViewFrame.height ) - UIApplication.shared.firstWindow?.addSubview(expandedEmojiIdScrollView) + addViewToSecureContentView(view: expandedEmojiIdScrollView) if animated { expandedEmojiIdScrollView.alpha = 0 expandedEmojiIdScrollView.setContentOffset( @@ -350,6 +382,7 @@ final class EmojiIdView: DynamicThemeView { if !animated { if blackoutWhileExpanded { blackoutView.removeFromSuperview() + secureContentView.isHidden = true } expandedEmojiIdScrollView.removeFromSuperview() condensedEmojiIdContainer.alpha = 1 @@ -361,6 +394,7 @@ final class EmojiIdView: DynamicThemeView { if self.blackoutWhileExpanded { self.fadeView(view: self.blackoutView, fadeOut: true) { [weak self] in self?.blackoutView.removeFromSuperview() + self?.secureContentView.isHidden = true } } // fade out scroll view @@ -370,6 +404,7 @@ final class EmojiIdView: DynamicThemeView { // fade in condensed emoji id self.fadeView(view: self.condensedEmojiIdContainer, fadeOut: false) { [weak self] in self?.tapActionIsDisabled = false + self?.secureContentView.isHidden = true if callTapCompletion == true { self?.tapToExpand?(false) } @@ -414,6 +449,7 @@ final class EmojiIdView: DynamicThemeView { deinit { UIApplication.shared.menuTabBarController?.tabBar.alpha = 1 UIApplication.shared.menuTabBarController?.tabBar.isUserInteractionEnabled = true + secureContentView.removeFromSuperview() } } @@ -439,7 +475,7 @@ extension EmojiIdView { } } - UIApplication.shared.firstWindow?.addSubview(emojiMenu) + addViewToSecureContentView(view: emojiMenu) guard let globalFrame = condensedEmojiIdContainer.globalFrame else { return } let emojiMenuSize = CGSize(width: 119, height: 37) emojiMenu.alpha = 0.0 @@ -468,11 +504,13 @@ extension EmojiIdView { hexPubKeyTipView = UIView() hexPubKeyTipLabel = UILabel() guard let tipView = hexPubKeyTipView, - let tipLabel = hexPubKeyTipLabel, - let parentView = UIApplication.shared.firstWindow else { + let tipLabel = hexPubKeyTipLabel else { return } - parentView.addSubview(tipView) + + let parentView = secureContentView.view + + addViewToSecureContentView(view: tipView) parentView.bringSubviewToFront(tipView) tipView.layer.cornerRadius = 4 @@ -558,6 +596,11 @@ extension EmojiIdView { completion?() } } + + private func addViewToSecureContentView(view: UIView) { + secureContentView.view.addSubview(view) + UIApplication.shared.firstWindow?.bringSubviewToFront(secureContentView) + } } private class EmojiMenuView: UIView { @@ -684,8 +727,8 @@ extension EmojiIdView { updateCopyView(theme: theme) - UIApplication.shared.firstWindow?.addSubview(containerView) - UIApplication.shared.firstWindow?.bringSubviewToFront(containerView) + addViewToSecureContentView(view: containerView) + secureContentView.view.bringSubviewToFront(containerView) UIView.animate(withDuration: CATransaction.animationDuration(), animations: { diff --git a/MobileWallet/UIElements/Keyboard Form/FormOverlayPresenter.swift b/MobileWallet/UIElements/Keyboard Form/FormOverlayPresenter.swift index e3129700..7b46169a 100644 --- a/MobileWallet/UIElements/Keyboard Form/FormOverlayPresenter.swift +++ b/MobileWallet/UIElements/Keyboard Form/FormOverlayPresenter.swift @@ -42,9 +42,9 @@ import UIKit enum FormOverlayPresenter { - static func showForm(title: String, textFieldModels: [ContactBookFormView.TextFieldViewModel], presenter: UIViewController, onClose: @escaping () -> Void) { + static func showForm(title: String, rightButtonTitle: String? = localized("common.done"), textFieldModels: [ContactBookFormView.TextFieldViewModel], presenter: UIViewController, onClose: @escaping () -> Void) { - let formView = ContactBookFormView(title: title, textFieldsModels: textFieldModels) + let formView = ContactBookFormView(title: title, rightButtonTitle: rightButtonTitle, textFieldsModels: textFieldModels) let overlay = FormOverlay(formView: formView) overlay.onClose = onClose @@ -130,4 +130,39 @@ extension FormOverlayPresenter { onClose?(nameComponents, yat) } } + + static func showSelectCustomBaseNodeForm(hex: String?, address: String?, presenter: UIViewController, onClose: ((_ hex: String?, _ address: String?) -> Void)?) { + + var hex = hex + var address = address + + let title = localized("restore_from_seed_words.form.title") + let models = [ + ContactBookFormView.TextFieldViewModel(placeholder: localized("restore_from_seed_words.form.placeholder.hex"), text: hex, isEmojiKeyboardVisible: false, callback: { hex = $0 }), + ContactBookFormView.TextFieldViewModel(placeholder: localized("restore_from_seed_words.form.placeholder.address"), text: address, isEmojiKeyboardVisible: false, callback: { address = $0 }) + ] + + showForm(title: title, textFieldModels: models, presenter: presenter) { + onClose?(hex, address) + } + } + + static func showAddBaseNodeForm(presenter: UIViewController, onClose: ((_ name: String, _ hex: String, _ address: String) -> Void)?) { + + var name = "" + var hex = "" + var address = "" + + showForm( + title: localized("add_base_node.form.title"), + rightButtonTitle: localized("add_base_node.form.button.save"), + textFieldModels: [ + ContactBookFormView.TextFieldViewModel(placeholder: localized("add_base_node.form.text_field.placeholder.name"), text: nil, isEmojiKeyboardVisible: false, callback: { name = $0 }), + ContactBookFormView.TextFieldViewModel(placeholder: localized("add_base_node.form.text_field.placeholder.hex"), text: nil, isEmojiKeyboardVisible: false, callback: { hex = $0 }), + ContactBookFormView.TextFieldViewModel(placeholder: localized("add_base_node.form.text_field.placeholder.address"), text: nil, isEmojiKeyboardVisible: false, callback: { address = $0 }) + ], + presenter: presenter, + onClose: { onClose?(name, hex, address) } + ) + } } diff --git a/MobileWallet/UIElements/Keyboard Form/FormOverlayView.swift b/MobileWallet/UIElements/Keyboard Form/FormOverlayView.swift index e0ad2c46..ba78b930 100644 --- a/MobileWallet/UIElements/Keyboard Form/FormOverlayView.swift +++ b/MobileWallet/UIElements/Keyboard Form/FormOverlayView.swift @@ -82,7 +82,7 @@ final class FormOverlayView: UIView, UIKeyInput { // MARK: - Setups private func setupViews(formView: FormShowable) { - backgroundColor = .static.black?.withAlphaComponent(0.7) + backgroundColor = .Static.popupOverlay formView.translatesAutoresizingMaskIntoConstraints = false returnKeyType = formView.initalReturkKeyType } diff --git a/MobileWallet/UIElements/Menu/MenuTableView.swift b/MobileWallet/UIElements/Menu/MenuTableView.swift index 1299475d..2a04be8b 100644 --- a/MobileWallet/UIElements/Menu/MenuTableView.swift +++ b/MobileWallet/UIElements/Menu/MenuTableView.swift @@ -111,7 +111,7 @@ final class MenuTableView: DynamicThemeTableView { snapshot.appendItems($1.items) } - tableDataSource?.apply(snapshot: snapshot) + tableDataSource?.applySnapshotUsingReloadData(snapshot) } } diff --git a/MobileWallet/UIElements/RoundedAvatarView.swift b/MobileWallet/UIElements/RoundedAvatarView.swift index 507b5bd2..6ee4d506 100644 --- a/MobileWallet/UIElements/RoundedAvatarView.swift +++ b/MobileWallet/UIElements/RoundedAvatarView.swift @@ -142,7 +142,7 @@ final class RoundedAvatarView: DynamicThemeView { backgroundColor = theme.backgrounds.primary apply(shadow: theme.shadows.box) case .static: - backgroundColor = .static.white + backgroundColor = .Static.white apply(shadow: .none) } } diff --git a/MobileWallet/UIElements/SystemMenuTableViewCell/SystemMenuTableViewCell.swift b/MobileWallet/UIElements/SystemMenuTableViewCell/SystemMenuTableViewCell.swift index 225a469e..2e6653b1 100644 --- a/MobileWallet/UIElements/SystemMenuTableViewCell/SystemMenuTableViewCell.swift +++ b/MobileWallet/UIElements/SystemMenuTableViewCell/SystemMenuTableViewCell.swift @@ -38,7 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import TariCommon class SystemMenuTableViewCellItem: NSObject { @@ -378,13 +377,13 @@ extension SystemMenuTableViewCell { } private func setupTitle() { - titleLabel.font = Theme.shared.fonts.systemTableViewCell + titleLabel.font = .Avenir.medium.withSize(15.0) titleLabel.adjustsFontSizeToFitWidth = true labelsStackView.addArrangedSubview(titleLabel) } private func setupDescriptionLabel() { - subtitleLabel.font = Theme.shared.fonts.systemTableViewCell + subtitleLabel.font = .Avenir.medium.withSize(15.0) subtitleLabel.adjustsFontSizeToFitWidth = true labelsStackView.addArrangedSubview(subtitleLabel) } diff --git a/MobileWallet/UIElements/TabBar/CustomTabBar.swift b/MobileWallet/UIElements/TabBar/CustomTabBar.swift index 2c518beb..e5ffe8f0 100644 --- a/MobileWallet/UIElements/TabBar/CustomTabBar.swift +++ b/MobileWallet/UIElements/TabBar/CustomTabBar.swift @@ -38,10 +38,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit +import TariCommon final class CustomTabBar: DynamicThemeTabBar { + @View private var secureContentView = SecureWrapperView() + override init() { super.init() setup() @@ -58,6 +60,27 @@ final class CustomTabBar: DynamicThemeTabBar { layer.shadowRadius = 8 layer.shadowOpacity = 0.25 layer.masksToBounds = false + + addSubview(secureContentView) + + let constraints = [ + secureContentView.topAnchor.constraint(equalTo: topAnchor), + secureContentView.leadingAnchor.constraint(equalTo: leadingAnchor), + secureContentView.trailingAnchor.constraint(equalTo: trailingAnchor), + secureContentView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + override func addSubview(_ view: UIView) { + + guard view != secureContentView else { + super.addSubview(view) + return + } + + secureContentView.view.addSubview(view) } override func sizeThatFits(_ size: CGSize) -> CGSize { @@ -72,11 +95,13 @@ final class CustomTabBar: DynamicThemeTabBar { let appearance = UITabBarAppearance() appearance.configureWithOpaqueBackground() - appearance.backgroundColor = theme.backgrounds.primary + appearance.backgroundColor = .clear appearance.stackedLayoutAppearance.normal.iconColor = theme.icons.default appearance.stackedLayoutAppearance.selected.iconColor = theme.icons.active standardAppearance = appearance scrollEdgeAppearance = appearance + + secureContentView.view.backgroundColor = theme.backgrounds.primary } } diff --git a/MobileWallet/UIElements/TabBar/MenuTabBarController.swift b/MobileWallet/UIElements/TabBar/MenuTabBarController.swift index fc1ce2a3..a55909ee 100644 --- a/MobileWallet/UIElements/TabBar/MenuTabBarController.swift +++ b/MobileWallet/UIElements/TabBar/MenuTabBarController.swift @@ -179,9 +179,9 @@ private extension MenuTabBarController.Tab { case .ttlStore: return Theme.shared.images.ttlItem case .transactions: - return .tabBar.send + return .Images.TabBar.send case .contactBook: - return .icons.tabBar.contactBook + return .Icons.TabBar.contactBook case .settings: return Theme.shared.images.settingsItem } diff --git a/MobileWallet/UIElements/WebBrowserView/WebBrowserViewController.swift b/MobileWallet/UIElements/WebBrowserView/WebBrowserViewController.swift index 217d6098..af26ad8f 100644 --- a/MobileWallet/UIElements/WebBrowserView/WebBrowserViewController.swift +++ b/MobileWallet/UIElements/WebBrowserView/WebBrowserViewController.swift @@ -107,7 +107,7 @@ class WebBrowserViewController: DynamicThemeViewController { override func update(theme: ColorTheme) { super.update(theme: theme) - view.backgroundColor = theme.backgrounds.primary + mainView.backgroundColor = theme.backgrounds.primary webView.backgroundColor = theme.backgrounds.secondary backButton.tintColor = theme.icons.default forwardButton.tintColor = theme.icons.default @@ -186,7 +186,7 @@ extension WebBrowserViewController { private func setupNavigationBar() { - view.addSubview(navigationBar) + mainView.addSubview(navigationBar) navigationBar.backButtonType = modalPresentationStyle == .popover ? .close : .none navigationBar.update(rightButton: NavigationBar.ButtonModel(image: Theme.shared.images.share, callback: { [weak self] in self?.showShareDialog() })) @@ -204,7 +204,7 @@ extension WebBrowserViewController { navigationPanel.backgroundColor = .clear navigationPanel.clipsToBounds = true - view.addSubview(navigationPanel) + mainView.addSubview(navigationPanel) navigationPanel.translatesAutoresizingMaskIntoConstraints = false navigationPanel.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true navigationPanelHeightConstraint = navigationPanel.heightAnchor.constraint(equalToConstant: 56.0) @@ -237,8 +237,8 @@ extension WebBrowserViewController { webView.navigationDelegate = self webView.scrollView.delegate = self - view.addSubview(webView) - view.bringSubviewToFront(navigationBar) + mainView.addSubview(webView) + mainView.bringSubviewToFront(navigationBar) webView.translatesAutoresizingMaskIntoConstraints = false webView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true @@ -250,7 +250,7 @@ extension WebBrowserViewController { private func setupGrabber() { if modalPresentationStyle != .popover { return } - view.addSubview(grabber) + mainView.addSubview(grabber) grabber.heightAnchor.constraint(equalToConstant: 5).isActive = true grabber.widthAnchor.constraint(equalToConstant: 44).isActive = true diff --git a/MobileWallet/en.lproj/Localizable.strings b/MobileWallet/en.lproj/Localizable.strings index baf133d5..a8e5f886 100644 --- a/MobileWallet/en.lproj/Localizable.strings +++ b/MobileWallet/en.lproj/Localizable.strings @@ -77,6 +77,7 @@ "transaction.one_sided_payment.avatar" = "➡️"; "transaction.one_sided_payment.note.normal" = "You received a one-sided payment"; "transaction.one_sided_payment.note.recovered" = "You recovered some funds"; +"transaction.coinbase.user_placeholder" = "🪙 Coinbase"; "transaction.normal.title.pending.part.2" = "sent a payment"; "transaction.normal.title.inbound.part.2" = "paid you"; "transaction.normal.title.outbound.part.1" = "You paid"; @@ -213,6 +214,7 @@ "settings.last_successful_backup.with_param" = "Last successful backup: %@"; "settings.item.select_theme" = "Select Theme"; +"settings.item.screen_recording_settings" = "Screen Recording"; "settings.item.bluetooth_settings" = "Bluetooth Settings"; "settings.item.wallet_backups" = "Wallet Backups"; "settings.item.data_collection" = "Data Collection"; @@ -588,19 +590,26 @@ "select_base_node.cell.delete" = "Delete"; /* Add Base Node View */ -"add_base_node.title" = "Add New Base Node"; -"add_base_node.section.name" = "Name"; -"add_base_node.section.peer" = "Peer"; -"add_base_node.button.save" = "Save And Activate"; +"add_base_node.form.title" = "Add Base Node"; +"add_base_node.form.text_field.placeholder.name" = "Name"; +"add_base_node.form.text_field.placeholder.hex" = "Hex"; +"add_base_node.form.text_field.placeholder.address" = "Address"; +"add_base_node.form.button.save" = "Save"; "add_base_node.error.title" = "Error"; -"add_base_node.error.no_name" = "Name field can't be empty. Please add the nane of the node and try again."; +"add_base_node.error.no_name" = "Name field can't be empty. Please add the name of the node and try again."; "add_base_node.error.invalid_peer" = "Invalid peer. Please correct the peer address and try again."; -"add_base_node.error.button" = "OK"; /* Restore Wallet From Seed Words */ +"restore_from_seed_words.custom_node_name" = "Recovery Base Node"; +"restore_from_seed_words.title" = "Restore With Seed Phrase"; "restore_from_seed_words.label.description" = "Enter your seed phrase to restore your wallet"; -"restore_from_seed_words.button.select_base_node" = "Select base node"; +"restore_from_seed_words.button.select_base_node.select" = "Select Custom Base Node"; +"restore_from_seed_words.button.select_base_node.edit" = "Edit Base Node"; "restore_from_seed_words.button.submit" = "Restore Wallet"; +"restore_from_seed_words.form.title" = "Enter Custom Base Node"; +"restore_from_seed_words.form.placeholder.hex" = "Hex"; +"restore_from_seed_words.form.placeholder.address" = "Address"; +"restore_from_seed_words.form.error.message" = "Oops, looks like the address you entered isn't good. We had to revert your change."; "restore_from_seed_words.autocompletion_toolbar.label.start_typing" = "Start typing to see suggestions"; "restore_from_seed_words.autocompletion_toolbar.label.no_suggestions" = "There are no suggestions for the phrase"; "restore_from_seed_words.error.title" = "Error"; @@ -694,6 +703,14 @@ "theme_switcher.element.title.dark" = "Dark"; "theme_switcher.element.title.purple" = "Purple"; +/* Screen Recording Settings */ + +"screen_recording.title" = "Screen Recording"; +"screen_recording.label.description" = "Screen recording and taking screenshots can capture your wallet data and transactions. To keep your wallet data private, you can disable this setting.\n\nNote: Screen recording and screenshots will always be disabled for the Seed Phrase screen."; +"screen_recording.label.confirmation" = "Allow screen recording"; +"screen_recording.pop_up.confirmation.title" = "Screen recording will be enabled"; +"screen_recording.pop_up.confirmation.message" = "You will be able to record your screen. Are you sure you want to proceed?"; + /* Bluetooth Settings */ "bluetooth_settings.title" = "Bluetooth Settings"; @@ -888,3 +905,13 @@ "tracking.pop_up.consent.message" = "We're always striving to make Aurora better.\nTo achieve this, we use a third-party service to gather non-identifiable data like crash logs and usage statistics. This data helps us pinpoint and fix any issues, ultimately improving your experience.\n\nDo you consent to your data being collected?"; "tracking.pop_up.consent.button.yes" = "Yes, I consent"; "tracking.pop_up.consent.button.no" = "No, I decline"; + +/* Address Poisoning Prevention */ + +"address_poisoning.pop_up.title" = "Heads Up!"; +"address_poisoning.pop_up.message" = "Looks like there are %d very similar addresses in your contact book. Please double check that you’re sending to the right one!"; +"address_poisoning.pop_up.label.tick_message" = "This is a trusted contact"; +"address_poisoning.pop_up.label.trusted_info" = "Marking a contact as trusted means you trust this person and we won’t ask you to double check the address again."; +"address_poisoning.label.transaction_count" = "No of transactions: %d"; +"address_poisoning.label.last_transction" = "Last transaction date: %@"; +"address_poisoning.label.last_transction.never" = "Never"; diff --git a/UnitTests/DeepLinkFormatterTests.swift b/UnitTests/DeepLinkFormatterTests.swift index ebc9298d..8186acc0 100644 --- a/UnitTests/DeepLinkFormatterTests.swift +++ b/UnitTests/DeepLinkFormatterTests.swift @@ -52,7 +52,7 @@ final class DeepLinkFormatterTests: XCTestCase { func testDeeplinkWithInvalidNetworkName() { - let inputDeeplink = URL(string: "tari://invalid_network/transactions/send?publicKey=testpubkey&amount=123¬e=Hello%20World!")! + let inputDeeplink = URL(string: "tari://invalid_network/transactions/send?tariAddress=testpubkey&amount=123¬e=Hello%20World!")! var result: TransactionsSendDeeplink? var cachedError: DeepLinkError! @@ -74,7 +74,7 @@ final class DeepLinkFormatterTests: XCTestCase { func testDeeplinkWithInvalidCommandName() { - let inputDeeplink = URL(string: "tari://test_network/invalid_command/send?publicKey=testpubkey&amount=123¬e=Hello%20World!")! + let inputDeeplink = URL(string: "tari://test_network/invalid_command/send?tariAddress=testpubkey&amount=123¬e=Hello%20World!")! var result: TransactionsSendDeeplink? var cachedError: DeepLinkError! @@ -96,7 +96,7 @@ final class DeepLinkFormatterTests: XCTestCase { func testDeeplinkWithInvalidValue() { - let inputDeeplink = URL(string: "tari://test_network/transactions/send?publicKey=testpubkey&amount=-123¬e=Hello%20World!")! + let inputDeeplink = URL(string: "tari://test_network/transactions/send?tariAddress=testpubkey&amount=-123¬e=Hello%20World!")! let invalidKey = "amount" var result: TransactionsSendDeeplink? @@ -121,7 +121,7 @@ final class DeepLinkFormatterTests: XCTestCase { func testValidTransactionsSendDeeplinkDecoding() { - let inputDeeplink = URL(string: "tari://test_network/transactions/send?publicKey=testpubkey&amount=123¬e=Hello%20World!")! + let inputDeeplink = URL(string: "tari://test_network/transactions/send?tariAddress=testpubkey&amount=123¬e=Hello%20World!")! let expectedResult = TransactionsSendDeeplink(receiverAddress: "testpubkey", amount: 123, note: "Hello World!") let result = try! DeepLinkFormatter.model(type: TransactionsSendDeeplink.self, deeplink: inputDeeplink) @@ -133,8 +133,8 @@ final class DeepLinkFormatterTests: XCTestCase { func testValidTransactionsSendDeeplinkEncoding() { - let inputModel = TransactionsSendDeeplink(receiverAddress: "testpubkey", amount: 123, note: "Hello World!") - let expectedResult = URL(string: "tari://test_network/transactions/send?publicKey=testpubkey&amount=123¬e=Hello%20World!")! + let inputModel = TransactionsSendDeeplink(receiverAddress: "testaddress", amount: 123, note: "Hello World!") + let expectedResult = URL(string: "tari://test_network/transactions/send?tariAddress=testaddress&amount=123¬e=Hello%20World!")! let result = try! DeepLinkFormatter.deeplink(model: inputModel) @@ -144,7 +144,7 @@ final class DeepLinkFormatterTests: XCTestCase { func testTransactionsSendDeeplinkDecodingWithMissingPublicKey() { let inputDeeplink = URL(string: "tari://test_network/transactions/send?amount=123¬e=Hello%20World!")! - let invalidKey = "publicKey" + let invalidKey = "tariAddress" var result: TransactionsSendDeeplink? var cachedError: DeepLinkError! diff --git a/UnitTests/Mocks/TariNetwork+Mocks.swift b/UnitTests/Mocks/TariNetwork+Mocks.swift index 1a7a3112..b92cb1e1 100644 --- a/UnitTests/Mocks/TariNetwork+Mocks.swift +++ b/UnitTests/Mocks/TariNetwork+Mocks.swift @@ -41,5 +41,5 @@ @testable import Tari_Aurora extension TariNetwork { - static let testNetwork = TariNetwork(name: "test_network", presentedName: "Test Network", tickerSymbol: "Test Symbol", baseNodes: []) + static let testNetwork = TariNetwork(name: "test_network", presentedName: "Test Network", tickerSymbol: "Test Symbol", isRecommended: false, dnsPeer: "", blockExplorerURL: nil) } diff --git a/UnitTests/NetworkManagerTests.swift b/UnitTests/NetworkManagerTests.swift index b183a110..3c6502c2 100644 --- a/UnitTests/NetworkManagerTests.swift +++ b/UnitTests/NetworkManagerTests.swift @@ -45,7 +45,7 @@ final class NetworkManagerTests: XCTestCase { // MARK: - Properties - private let defaultNetwork = TariNetwork.esmeralda + private let defaultNetwork = TariNetwork.stagenet private var networkManager: NetworkManager! // MARK: - Setups @@ -116,9 +116,9 @@ final class NetworkManagerTests: XCTestCase { func testSelectedBaseNodeUpdate() { - let baseNode = try! BaseNode(name: "Test Name", peer: "2e93c460df49d8cfbbf7a06dd9004c25a84f92584f7d0ac5e30bd8e0beee9a43::/onion3/nuuq3e2olck22rudimovhmrdwkmjncxvwdgbvfxhz6myzcnx2j4rssyd:18141") + let baseNode = BaseNode(name: "Test Name", hex: "2e93c460df49d8cfbbf7a06dd9004c25a84f92584f7d0ac5e30bd8e0beee9a43", address: "/onion3/nuuq3e2olck22rudimovhmrdwkmjncxvwdgbvfxhz6myzcnx2j4rssyd:18141") - networkManager.selectedNetwork.selectedBaseNode = baseNode + networkManager.selectedBaseNode = baseNode let selectedNetworkName = GroupUserDefaults.selectedNetworkName! let networkSettings = GroupUserDefaults.networksSettings!.first! @@ -129,14 +129,14 @@ final class NetworkManagerTests: XCTestCase { func testCustomBaseNodesUpdate() { - let baseNode = try! BaseNode(name: "Test Name", peer: "2e93c460df49d8cfbbf7a06dd9004c25a84f92584f7d0ac5e30bd8e0beee9a43::/onion3/nuuq3e2olck22rudimovhmrdwkmjncxvwdgbvfxhz6myzcnx2j4rssyd:18141") + let baseNode = BaseNode(name: "Test Name", hex: "2e93c460df49d8cfbbf7a06dd9004c25a84f92584f7d0ac5e30bd8e0beee9a43", address: "/onion3/nuuq3e2olck22rudimovhmrdwkmjncxvwdgbvfxhz6myzcnx2j4rssyd:18141") - networkManager.selectedNetwork.customBaseNodes = [baseNode] + networkManager.customBaseNodes = [baseNode] let customBaseNodes = GroupUserDefaults.networksSettings!.first!.customBaseNodes XCTAssertEqual(customBaseNodes.count, 1) - XCTAssertEqual(customBaseNodes, networkManager.selectedNetwork.customBaseNodes) + XCTAssertEqual(customBaseNodes, networkManager.customBaseNodes) XCTAssertEqual(customBaseNodes.first!, baseNode) } @@ -144,6 +144,6 @@ final class NetworkManagerTests: XCTestCase { private func initialiseNetworkSettings() { // User defaults are updated with every access to internal settings - _ = networkManager.selectedNetwork.selectedBaseNode + _ = networkManager.selectedBaseNode } } diff --git a/UnitTests/StringBaseNodeTests.swift b/UnitTests/StringBaseNodeTests.swift new file mode 100644 index 00000000..39713408 --- /dev/null +++ b/UnitTests/StringBaseNodeTests.swift @@ -0,0 +1,206 @@ +// StringBaseNodeTests.swift + +/* + Package UnitTests + Created by Adrian Truszczyński on 15/04/2024 + Using Swift 5.0 + Running on macOS 14.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +@testable import Tari_Aurora +import XCTest + +final class StringBaseNodeTests: XCTestCase { + + func testValidHex() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = nil + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertTrue(result) + } + + func testValidHexAndOnionAddress() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/onion3/abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst:12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertTrue(result) + } + + func testValidHexAndIP4Address() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/ip4/11.22.33.44/tcp/12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertTrue(result) + } + + func testInvalidHex() { + + let hex: String = "x111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = nil + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testTooShortHex() { + + let hex: String = "111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = nil + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testTooLongHex() { + + let hex: String = "01111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = nil + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testTooShortHexWithOnionAddress() { + + let hex: String = "111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/onion3/abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst:12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testTooLongHexWithIP4Address() { + + let hex: String = "01111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/ip4/11.22.33.44/tcp/12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testInvalidOnionAddress() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/onion/abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst:12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + + func testTooShortOnionAddress() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/onion3/bcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst:12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testTooLongOnionAddress() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/onion3/xabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst:12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testInvalidIP4Address() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/ip5/11.22.33.44/tcp/12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testTooShortIP4Address() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/ip4/1111.22.33.44/tcp/12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testTooLongIP4Address() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/ip4/22.33.44/tcp/12345" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testOnionInvalidPort() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/onion3/abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst:1234567" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } + + func testIP4InvalidPort() { + + let hex: String = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + let address: String? = "/ip4/11.22.33.44/tcp/1234567" + + let result = String.isBaseNodeAddress(hex: hex, address: address) + + XCTAssertFalse(result) + } +} diff --git a/UnitTests/StringSimilarityTests.swift b/UnitTests/StringSimilarityTests.swift new file mode 100644 index 00000000..73ec34ba --- /dev/null +++ b/UnitTests/StringSimilarityTests.swift @@ -0,0 +1,153 @@ +// StringSimilarityTests.swift + +/* + Package UnitTests + Created by Adrian Truszczyński on 22/01/2024 + Using Swift 5.0 + Running on macOS 14.2 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import XCTest +@testable import Tari_Aurora + +final class StringSimilarityTests: XCTestCase { + + func testSameTexts() { + + let firstText = "123xxxabc" + let secondText = "123xxxabc" + let minSameCharacters = 3 + let usedPrefixSuffixCharacters = 3 + + let result = firstText.isSimilar(to: secondText, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + + XCTAssertFalse(result) + } + + func testSimilarTexts() { + + let firstText = "123xxxabc" + let secondText = "123xxxdef" + let minSameCharacters = 3 + let usedPrefixSuffixCharacters = 3 + + let result = firstText.isSimilar(to: secondText, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + + XCTAssertTrue(result) + } + + func testDifferentTexts() { + + let firstText = "123xxxabc" + let secondText = "a23xxxdef" + let minSameCharacters = 3 + let usedPrefixSuffixCharacters = 3 + + let result = firstText.isSimilar(to: secondText, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + + XCTAssertFalse(result) + } + + func testTextsWithDifferentLenghts() { + + let firstText = "123xxxabc" + let secondText = "123xxxxdef" + let minSameCharacters = 3 + let usedPrefixSuffixCharacters = 3 + + let result = firstText.isSimilar(to: secondText, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + + XCTAssertFalse(result) + } + + func testTextsWithZeroSameCharacters() { + + let firstText = "123xxxabc" + let secondText = "123xxxdef" + let minSameCharacters = 0 + let usedPrefixSuffixCharacters = 3 + + let result = firstText.isSimilar(to: secondText, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + + XCTAssertTrue(result) + } + + func testEmptyString() { + + let firstText = "" + let secondText = "123xxxdef" + let minSameCharacters = 3 + let usedPrefixSuffixCharacters = 3 + + let result = firstText.isSimilar(to: secondText, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + + XCTAssertFalse(result) + } + + func testEmptyStrings() { + + let firstText = "" + let secondText = "" + let minSameCharacters = 3 + let usedPrefixSuffixCharacters = 3 + + let result = firstText.isSimilar(to: secondText, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + + XCTAssertFalse(result) + } + + func testTooShortText() { + + let firstText = "123bc" + let secondText = "123xxxdef" + let minSameCharacters = 3 + let usedPrefixSuffixCharacters = 3 + + let result = firstText.isSimilar(to: secondText, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + + XCTAssertFalse(result) + } + + func testTooShortTexts() { + + let firstText = "123bc" + let secondText = "123ef" + let minSameCharacters = 3 + let usedPrefixSuffixCharacters = 3 + + let result = firstText.isSimilar(to: secondText, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + + XCTAssertFalse(result) + } +} diff --git a/dependencies.env b/dependencies.env index be93eba5..5012281e 100644 --- a/dependencies.env +++ b/dependencies.env @@ -1 +1 @@ -FFI_VERSION="1.0.0-rc.5" \ No newline at end of file +FFI_VERSION="1.0.0-rc.8" \ No newline at end of file