diff --git a/.gitignore b/.gitignore index 06ee11e0..0a0d7d84 100644 --- a/.gitignore +++ b/.gitignore @@ -71,7 +71,7 @@ fastlane/screenshots/*.html* fastlane/test_output .DS_Store fastlane/Appfile -MobileWallet/TariLib/libtari_wallet_ffi_ios.xcframework +MobileWallet/Libraries/TariLib/libminotari_wallet_ffi_ios.xcframework MobileWallet/Constants.plist .idea/ fastlane/.env* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d09901d0..00000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: objective-c -osx_image: xcode11.4 -xcode_workspace: MobileWallet.xcworkspace -xcode_scheme: MobileWallet -xcode_only_testing: MobileWalletTests -xcode_destination: platform=iOS Simulator,OS=13.4.1,name=iPhone 11 -before_script: -- bash update_dependencies.sh -# - ios-sim start --devicetypeid "com.apple.CoreSimulator.SimDeviceType.iPhone-11, 13.4.1" diff --git a/MobileWallet.xcodeproj/project.pbxproj b/MobileWallet.xcodeproj/project.pbxproj index 2464407c..4ae01c7e 100644 --- a/MobileWallet.xcodeproj/project.pbxproj +++ b/MobileWallet.xcodeproj/project.pbxproj @@ -41,17 +41,13 @@ 00E2BCBA236B5BE800C2A105 /* ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E2BCB9236B5BE800C2A105 /* ActionButton.swift */; }; 00E4919A2366E08B007B332D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E491992366E08B007B332D /* AppDelegate.swift */; }; 00E4919C2366E08B007B332D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E4919B2366E08B007B332D /* SceneDelegate.swift */; }; - 00E491A62366E08B007B332D /* MobileWallet.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 00E491A42366E08B007B332D /* MobileWallet.xcdatamodeld */; }; 00E491A82366E08F007B332D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 00E491A72366E08F007B332D /* Assets.xcassets */; }; 00E491AB2366E08F007B332D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 00E491A92366E08F007B332D /* LaunchScreen.storyboard */; }; - 123181E402EDECC46E321212 /* Pods_MobileWallet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F28D9135828811CA84244432 /* Pods_MobileWallet.framework */; }; 37049270247EA0770034EE5D /* RestoreWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3704926F247EA0770034EE5D /* RestoreWalletViewController.swift */; }; 37049274247EA9110034EE5D /* SystemMenuTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37049273247EA9110034EE5D /* SystemMenuTableViewCell.swift */; }; 3708D74A247FCF2300807D72 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3708D749247FCF2300807D72 /* SettingsViewController.swift */; }; 3708D756247FF81900807D72 /* SettingsParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3708D755247FF81900807D72 /* SettingsParentViewController.swift */; }; 37098043249CD48A0004ED2E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 37098045249CD48A0004ED2E /* Localizable.strings */; }; - 370E886124FE7AE800576F61 /* BridgesConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370E886024FE7AE800576F61 /* BridgesConfigurationViewController.swift */; }; - 370E886324FE974100576F61 /* OnionSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370E886224FE974100576F61 /* OnionSettings.swift */; }; 370E887224FEA32600576F61 /* Ipv6Tester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370E887124FEA32600576F61 /* Ipv6Tester.swift */; }; 370E887824FEA54100576F61 /* NetworkTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 370E887724FEA54100576F61 /* NetworkTools.m */; }; 371189732488FF11004D0CE3 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 371189722488FF11004D0CE3 /* CloudKit.framework */; }; @@ -71,7 +67,6 @@ 37875E4424D85C4300C0595B /* LoadingGIFButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37875E4324D85C4300C0595B /* LoadingGIFButton.swift */; }; 37875E4824D8787A00C0595B /* TxTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37875E4724D8787A00C0595B /* TxTableViewModel.swift */; }; 378B4BD02498F83000C00CCF /* PendingCircleAnimation.json in Resources */ = {isa = PBXBuildFile; fileRef = 378B4BCF2498F83000C00CCF /* PendingCircleAnimation.json */; }; - 378EE9A3250126BE009615B5 /* CustomBridgesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378EE9A2250126BE009615B5 /* CustomBridgesViewController.swift */; }; 37ABB69024781CE800F08163 /* UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37ABB68F24781CE800F08163 /* UILabel.swift */; }; 37ABB69424781F8600F08163 /* UILabelWithPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37ABB69324781F8600F08163 /* UILabelWithPadding.swift */; }; 37AFE265245193CA006EA270 /* AlwaysPoppableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AFE264245193CA006EA270 /* AlwaysPoppableNavigationController.swift */; }; @@ -347,6 +342,7 @@ 4C363B762574F15E00D99868 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C363B372574EFE500D99868 /* OpenSSL.xcframework */; }; 4CD20A362407967B007B64D8 /* TransportConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD20A352407967B007B64D8 /* TransportConfig.swift */; }; 4CDEC323273A5E3600999DCB /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDEC322273A5E3500999DCB /* Balance.swift */; }; + 515299BD56554003EF2BD2FD /* Pods_MobileWallet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A04F26CF08C60019DAC177 /* Pods_MobileWallet.framework */; }; 540CB6EE29C1D519003FACEF /* SettingsProfileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540CB6ED29C1D519003FACEF /* SettingsProfileCell.swift */; }; 540CB6F129C1DDCC003FACEF /* AddContactModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540CB6F029C1DDCC003FACEF /* AddContactModel.swift */; }; 540CB6F329C1DE26003FACEF /* AddContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540CB6F229C1DE26003FACEF /* AddContactViewController.swift */; }; @@ -384,13 +380,13 @@ 542585C52A332F32009D12CD /* RotaryMenuOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542585C42A332F32009D12CD /* RotaryMenuOverlayView.swift */; }; 542585C82A334697009D12CD /* RotaryMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542585C72A334697009D12CD /* RotaryMenuView.swift */; }; 542585CA2A3346C0009D12CD /* RotaryMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542585C92A3346C0009D12CD /* RotaryMenuButton.swift */; }; + 542725662AFB7E1000FA4973 /* libminotari_wallet_ffi_ios.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 542725652AFB7E1000FA4973 /* libminotari_wallet_ffi_ios.xcframework */; }; 5430B97729B7400900C80AA2 /* LinkContactsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5430B97629B7400900C80AA2 /* LinkContactsModel.swift */; }; 5430B97929B7401200C80AA2 /* LinkContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5430B97829B7401200C80AA2 /* LinkContactsViewController.swift */; }; 5430B97B29B7401C00C80AA2 /* LinkContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5430B97A29B7401C00C80AA2 /* LinkContactsView.swift */; }; 5430B97D29B7402600C80AA2 /* LinkContactsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5430B97C29B7402600C80AA2 /* LinkContactsConstructor.swift */; }; 54394EDE29CA133400E7CAEA /* LoadingImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */; }; - 543DF19A293F37E70031EA70 /* CustomBridgesHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543DF199293F37E70031EA70 /* CustomBridgesHeaderView.swift */; }; - 543DF19C293F3DA90031EA70 /* BridgesConfigurationFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543DF19B293F3DA90031EA70 /* BridgesConfigurationFooterView.swift */; }; + 543DF19C293F3DA90031EA70 /* TorBridgesFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543DF19B293F3DA90031EA70 /* TorBridgesFooterView.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 */; }; @@ -412,6 +408,8 @@ 5453242229A68CA0009281A7 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453242129A68CA0009281A7 /* RoundedButton.swift */; }; 5453242429A68D14009281A7 /* TariGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453242329A68D14009281A7 /* TariGradientView.swift */; }; 5453242629A68D7D009281A7 /* RoundedAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453242529A68D7D009281A7 /* RoundedAvatarView.swift */; }; + 5453E43C2AC9D5CA00C7F40D /* UIImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453E43B2AC9D5CA00C7F40D /* UIImage+Utils.swift */; }; + 5453E43E2AC9D95900C7F40D /* TorBridgesConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453E43D2AC9D95900C7F40D /* TorBridgesConstructor.swift */; }; 5455969829B08D7B00D6719E /* FormTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5455969729B08D7B00D6719E /* FormTextField.swift */; }; 5455969B29B0A6B500D6719E /* InternalContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5455969A29B0A6B500D6719E /* InternalContactsManager.swift */; }; 5455969D29B0A73300D6719E /* ExternalContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5455969C29B0A73300D6719E /* ExternalContactsManager.swift */; }; @@ -423,7 +421,6 @@ 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 */; }; - 545B80A2298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545B80A1298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework */; }; 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 */; }; @@ -445,6 +442,7 @@ 546CE7F429AF514A00264699 /* FormOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546CE7F329AF514A00264699 /* FormOverlay.swift */; }; 546CE7F629AF517900264699 /* FormOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546CE7F529AF517900264699 /* FormOverlayView.swift */; }; 546CE7F929AF523F00264699 /* ContactBookFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546CE7F829AF523F00264699 /* ContactBookFormView.swift */; }; + 5477518D2AAB227000B78E1D /* CustomTorBridgesHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5477518C2AAB227000B78E1D /* CustomTorBridgesHeaderView.swift */; }; 547A85D329ACF2D100D94985 /* MenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547A85D229ACF2D100D94985 /* MenuCell.swift */; }; 547A85D529AD23D800D94985 /* MenuTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547A85D429AD23D800D94985 /* MenuTableView.swift */; }; 547A85D729AD248400D94985 /* MenuTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547A85D629AD248400D94985 /* MenuTableHeaderView.swift */; }; @@ -456,26 +454,29 @@ 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 */; }; + 548C4C322AC1689B00E376EF /* SwitchMenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548C4C312AC1689B00E376EF /* SwitchMenuCell.swift */; }; 54909AAF29C05822002D1070 /* ContactType+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54909AAE29C05822002D1070 /* ContactType+Data.swift */; }; 5491696E2940F78000783E54 /* LogFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5491696D2940F78000783E54 /* LogFilesManager.swift */; }; + 5494BFB02AF0E98E00791A52 /* TorConnectionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5494BFAF2AF0E98E00791A52 /* TorConnectionStatus.swift */; }; + 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 */; }; 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 */; }; + 549F5AF22ACAC32900FF4C6F /* PosixError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549F5AF12ACAC32900FF4C6F /* PosixError.swift */; }; 54A0903829E553A900D1CDDE /* PopUpQRContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A0903729E553A900D1CDDE /* PopUpQRContentView.swift */; }; + 54A148EC2AA88B7B00C68D9D /* CustomTorBridgesConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A148EB2AA88B7B00C68D9D /* CustomTorBridgesConstructor.swift */; }; 54A6E9F0297F1F9900A60853 /* StagedWalletSecurityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6E9EF297F1F9900A60853 /* StagedWalletSecurityManager.swift */; }; 54A6E9F2297F1FBA00A60853 /* PopUpStagedWalletSecurityHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6E9F1297F1FBA00A60853 /* PopUpStagedWalletSecurityHeaderView.swift */; }; 54A6E9F4297F260800A60853 /* UIViewController+Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6E9F3297F260800A60853 /* UIViewController+Common.swift */; }; 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 */; }; - 54A9C88A29F6A54B009B0653 /* ContactBookShareButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A9C88929F6A54B009B0653 /* ContactBookShareButton.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 */; }; 54AD5F662A420A9F00D223B8 /* Data+Utlis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AD5F652A420A9F00D223B8 /* Data+Utlis.swift */; }; - 54ADC08E2A3857850082C455 /* AddRecipientSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ADC08C2A3857840082C455 /* AddRecipientSectionHeaderView.swift */; }; - 54ADC08F2A3857850082C455 /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ADC08D2A3857840082C455 /* ContactCell.swift */; }; - 54ADC0912A38582B0082C455 /* ContactAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ADC0902A38582B0082C455 /* ContactAvatarView.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 */; }; @@ -487,14 +488,20 @@ 54C36D112A5D3EB600BD973A /* QRCodeScannerConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C36D102A5D3EB600BD973A /* QRCodeScannerConstructor.swift */; }; 54C416232A1C914200454096 /* LocalNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C416222A1C914200454096 /* LocalNotificationsManager.swift */; }; 54C416252A1CA81700454096 /* PendingDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C416242A1CA81700454096 /* PendingDataManager.swift */; }; + 54C7EB602AC6B22C00F387DF /* TrackingConsentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C7EB5F2AC6B22C00F387DF /* TrackingConsentManager.swift */; }; 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 */; }; + 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 */; }; + 54D1E1A92ABD92490021A365 /* DataCollectionSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D1E1A82ABD92490021A365 /* DataCollectionSettingsView.swift */; }; 54D419B32995092F00D496B4 /* ContactBookModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D419B22995092F00D496B4 /* ContactBookModel.swift */; }; 54D419B52995093800D496B4 /* ContactBookViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D419B42995093800D496B4 /* ContactBookViewController.swift */; }; 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 */; }; + 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 */; }; 54DD2E8629C37A7600C8C0D9 /* UINavigationController+Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DD2E8529C37A7600C8C0D9 /* UINavigationController+Common.swift */; }; @@ -515,8 +522,13 @@ 54F2E34C29EE9FFC00A7A15A /* ContactTransactionListHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F2E34B29EE9FFC00A7A15A /* ContactTransactionListHeaderView.swift */; }; 54F2E34E29EEAEBB00A7A15A /* ContactTransactionListPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F2E34D29EEAEBB00A7A15A /* ContactTransactionListPlaceholder.swift */; }; 54F306112A3B049500B5689D /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F306102A3B049500B5689D /* UIView+Utils.swift */; }; - 54F306132A3B1E1B00B5689D /* GlassButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F306122A3B1E1B00B5689D /* GlassButton.swift */; }; - 54F306152A3B1E6300B5689D /* UIImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F306142A3B1E6300B5689D /* UIImage+Utils.swift */; }; + 54F9C5A42AA712F100DA87D8 /* CustomTorBridgesInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F9C5A32AA712F100DA87D8 /* CustomTorBridgesInputCell.swift */; }; + 54FCC3672AA5C2D100CA0025 /* TorBridgesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCC3662AA5C2D100CA0025 /* TorBridgesViewController.swift */; }; + 54FCC3692AA5C2DB00CA0025 /* TorBridgesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCC3682AA5C2DB00CA0025 /* TorBridgesView.swift */; }; + 54FCC36B2AA5C2E400CA0025 /* TorBridgesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCC36A2AA5C2E400CA0025 /* TorBridgesModel.swift */; }; + 54FCC36F2AA5F00900CA0025 /* CustomTorBridgesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCC36E2AA5F00900CA0025 /* CustomTorBridgesViewController.swift */; }; + 54FCC3712AA5FF1100CA0025 /* CustomTorBridgesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCC3702AA5FF1100CA0025 /* CustomTorBridgesView.swift */; }; + 54FCC3732AA5FF1B00CA0025 /* CustomTorBridgesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCC3722AA5FF1B00CA0025 /* CustomTorBridgesModel.swift */; }; A01F54A8255EAB7E00F49AFA /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = A01F54A7255EAB7E00F49AFA /* Localization.swift */; }; A0779C612552C1AF00614EF3 /* DeleteWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0779C602552C1AF00614EF3 /* DeleteWalletViewController.swift */; }; BF0A7766241EEA0A00861A3E /* FaceID.json in Resources */ = {isa = PBXBuildFile; fileRef = BF0A7765241EEA0A00861A3E /* FaceID.json */; }; @@ -607,7 +619,6 @@ 00E491962366E08B007B332D /* Tari Aurora.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Tari Aurora.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 00E491992366E08B007B332D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 00E4919B2366E08B007B332D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 00E491A52366E08B007B332D /* MobileWallet.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MobileWallet.xcdatamodel; sourceTree = ""; }; 00E491A72366E08F007B332D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 00E491AA2366E08F007B332D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 00E491AC2366E08F007B332D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -616,8 +627,6 @@ 3708D749247FCF2300807D72 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 3708D755247FF81900807D72 /* SettingsParentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsParentViewController.swift; sourceTree = ""; }; 37098044249CD48A0004ED2E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - 370E886024FE7AE800576F61 /* BridgesConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgesConfigurationViewController.swift; sourceTree = ""; }; - 370E886224FE974100576F61 /* OnionSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnionSettings.swift; sourceTree = ""; }; 370E887124FEA32600576F61 /* Ipv6Tester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ipv6Tester.swift; sourceTree = ""; }; 370E887624FEA54100576F61 /* NetworkTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NetworkTools.h; sourceTree = ""; }; 370E887724FEA54100576F61 /* NetworkTools.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NetworkTools.m; sourceTree = ""; }; @@ -638,7 +647,6 @@ 37875E4324D85C4300C0595B /* LoadingGIFButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingGIFButton.swift; sourceTree = ""; }; 37875E4724D8787A00C0595B /* TxTableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxTableViewModel.swift; sourceTree = ""; }; 378B4BCF2498F83000C00CCF /* PendingCircleAnimation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = PendingCircleAnimation.json; sourceTree = ""; }; - 378EE9A2250126BE009615B5 /* CustomBridgesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBridgesViewController.swift; sourceTree = ""; }; 37ABB68F24781CE800F08163 /* UILabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UILabel.swift; sourceTree = ""; }; 37ABB69324781F8600F08163 /* UILabelWithPadding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UILabelWithPadding.swift; sourceTree = ""; }; 37AFE264245193CA006EA270 /* AlwaysPoppableNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlwaysPoppableNavigationController.swift; sourceTree = ""; }; @@ -700,7 +708,6 @@ 3A3D70B8291A625700A8CD7A /* BackupStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupStatus.swift; sourceTree = ""; }; 3A3D70BA291A637100A8CD7A /* BaseMenuTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMenuTableView.swift; sourceTree = ""; }; 3A3E7AD5284E03140065F3C0 /* UTXOTileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOTileView.swift; sourceTree = ""; }; - 3A3E8BF02924F84300490E57 /* Constants.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Constants.plist; sourceTree = ""; }; 3A3E8BF22924FB4C00490E57 /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = ""; }; 3A4205912798001A00A8D49C /* AmountKeyboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountKeyboardView.swift; sourceTree = ""; }; 3A420593279802E500A8D49C /* BaseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseButton.swift; sourceTree = ""; }; @@ -954,13 +961,13 @@ 542585C42A332F32009D12CD /* RotaryMenuOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuOverlayView.swift; sourceTree = ""; }; 542585C72A334697009D12CD /* RotaryMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuView.swift; sourceTree = ""; }; 542585C92A3346C0009D12CD /* RotaryMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuButton.swift; sourceTree = ""; }; + 542725652AFB7E1000FA4973 /* libminotari_wallet_ffi_ios.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libminotari_wallet_ffi_ios.xcframework; path = MobileWallet/Libraries/TariLib/libminotari_wallet_ffi_ios.xcframework; sourceTree = ""; }; 5430B97629B7400900C80AA2 /* LinkContactsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkContactsModel.swift; sourceTree = ""; }; 5430B97829B7401200C80AA2 /* LinkContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkContactsViewController.swift; sourceTree = ""; }; 5430B97A29B7401C00C80AA2 /* LinkContactsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkContactsView.swift; sourceTree = ""; }; 5430B97C29B7402600C80AA2 /* LinkContactsConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkContactsConstructor.swift; sourceTree = ""; }; 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingImageView.swift; sourceTree = ""; }; - 543DF199293F37E70031EA70 /* CustomBridgesHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBridgesHeaderView.swift; sourceTree = ""; }; - 543DF19B293F3DA90031EA70 /* BridgesConfigurationFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgesConfigurationFooterView.swift; sourceTree = ""; }; + 543DF19B293F3DA90031EA70 /* TorBridgesFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBridgesFooterView.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 = ""; }; @@ -982,6 +989,8 @@ 5453242129A68CA0009281A7 /* RoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; 5453242329A68D14009281A7 /* TariGradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariGradientView.swift; sourceTree = ""; }; 5453242529A68D7D009281A7 /* RoundedAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedAvatarView.swift; sourceTree = ""; }; + 5453E43B2AC9D5CA00C7F40D /* UIImage+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Utils.swift"; sourceTree = ""; }; + 5453E43D2AC9D95900C7F40D /* TorBridgesConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBridgesConstructor.swift; sourceTree = ""; }; 5455969729B08D7B00D6719E /* FormTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormTextField.swift; sourceTree = ""; }; 5455969A29B0A6B500D6719E /* InternalContactsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalContactsManager.swift; sourceTree = ""; }; 5455969C29B0A73300D6719E /* ExternalContactsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalContactsManager.swift; sourceTree = ""; }; @@ -993,7 +1002,6 @@ 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 = ""; }; - 545B80A1298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libtari_wallet_ffi_ios.xcframework; path = MobileWallet/TariLib/libtari_wallet_ffi_ios.xcframework; 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 = ""; }; @@ -1015,6 +1023,7 @@ 546CE7F329AF514A00264699 /* FormOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormOverlay.swift; sourceTree = ""; }; 546CE7F529AF517900264699 /* FormOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormOverlayView.swift; sourceTree = ""; }; 546CE7F829AF523F00264699 /* ContactBookFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookFormView.swift; sourceTree = ""; }; + 5477518C2AAB227000B78E1D /* CustomTorBridgesHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTorBridgesHeaderView.swift; sourceTree = ""; }; 547A85D229ACF2D100D94985 /* MenuCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCell.swift; sourceTree = ""; }; 547A85D429AD23D800D94985 /* MenuTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuTableView.swift; sourceTree = ""; }; 547A85D629AD248400D94985 /* MenuTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuTableHeaderView.swift; sourceTree = ""; }; @@ -1026,26 +1035,29 @@ 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 = ""; }; + 548C4C312AC1689B00E376EF /* SwitchMenuCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchMenuCell.swift; sourceTree = ""; }; 54909AAE29C05822002D1070 /* ContactType+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactType+Data.swift"; sourceTree = ""; }; 5491696D2940F78000783E54 /* LogFilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogFilesManager.swift; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; + 549F5AF12ACAC32900FF4C6F /* PosixError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosixError.swift; sourceTree = ""; }; 54A0903729E553A900D1CDDE /* PopUpQRContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpQRContentView.swift; sourceTree = ""; }; + 54A148EB2AA88B7B00C68D9D /* CustomTorBridgesConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTorBridgesConstructor.swift; sourceTree = ""; }; 54A6E9EF297F1F9900A60853 /* StagedWalletSecurityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StagedWalletSecurityManager.swift; sourceTree = ""; }; 54A6E9F1297F1FBA00A60853 /* PopUpStagedWalletSecurityHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpStagedWalletSecurityHeaderView.swift; sourceTree = ""; }; 54A6E9F3297F260800A60853 /* UIViewController+Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Common.swift"; sourceTree = ""; }; 54A783DA29E96FF600A30594 /* BLEPeripheralManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEPeripheralManager.swift; sourceTree = ""; }; 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 /* ContactBookShareButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookShareButton.swift; sourceTree = ""; }; + 54A9C88929F6A54B009B0653 /* RoundedLabeledButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabeledButton.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 = ""; }; 54AD5F652A420A9F00D223B8 /* Data+Utlis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utlis.swift"; sourceTree = ""; }; - 54ADC08C2A3857840082C455 /* AddRecipientSectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipientSectionHeaderView.swift; sourceTree = ""; }; - 54ADC08D2A3857840082C455 /* ContactCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = ""; }; - 54ADC0902A38582B0082C455 /* ContactAvatarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactAvatarView.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 = ""; }; @@ -1057,14 +1069,20 @@ 54C36D102A5D3EB600BD973A /* QRCodeScannerConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerConstructor.swift; sourceTree = ""; }; 54C416222A1C914200454096 /* LocalNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationsManager.swift; sourceTree = ""; }; 54C416242A1CA81700454096 /* PendingDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingDataManager.swift; sourceTree = ""; }; + 54C7EB5F2AC6B22C00F387DF /* TrackingConsentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingConsentManager.swift; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; + 54D1E1A82ABD92490021A365 /* DataCollectionSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCollectionSettingsView.swift; sourceTree = ""; }; 54D419B22995092F00D496B4 /* ContactBookModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookModel.swift; sourceTree = ""; }; 54D419B42995093800D496B4 /* ContactBookViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookViewController.swift; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; 54DD2E8529C37A7600C8C0D9 /* UINavigationController+Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Common.swift"; sourceTree = ""; }; @@ -1085,8 +1103,13 @@ 54F2E34B29EE9FFC00A7A15A /* ContactTransactionListHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTransactionListHeaderView.swift; sourceTree = ""; }; 54F2E34D29EEAEBB00A7A15A /* ContactTransactionListPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTransactionListPlaceholder.swift; sourceTree = ""; }; 54F306102A3B049500B5689D /* UIView+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = ""; }; - 54F306122A3B1E1B00B5689D /* GlassButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlassButton.swift; sourceTree = ""; }; - 54F306142A3B1E6300B5689D /* UIImage+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Utils.swift"; sourceTree = ""; }; + 54F9C5A32AA712F100DA87D8 /* CustomTorBridgesInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTorBridgesInputCell.swift; sourceTree = ""; }; + 54FCC3662AA5C2D100CA0025 /* TorBridgesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBridgesViewController.swift; sourceTree = ""; }; + 54FCC3682AA5C2DB00CA0025 /* TorBridgesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBridgesView.swift; sourceTree = ""; }; + 54FCC36A2AA5C2E400CA0025 /* TorBridgesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBridgesModel.swift; sourceTree = ""; }; + 54FCC36E2AA5F00900CA0025 /* CustomTorBridgesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTorBridgesViewController.swift; sourceTree = ""; }; + 54FCC3702AA5FF1100CA0025 /* CustomTorBridgesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTorBridgesView.swift; sourceTree = ""; }; + 54FCC3722AA5FF1B00CA0025 /* CustomTorBridgesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTorBridgesModel.swift; sourceTree = ""; }; A01F54A7255EAB7E00F49AFA /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; A0779C602552C1AF00614EF3 /* DeleteWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteWalletViewController.swift; sourceTree = ""; }; BF0A7765241EEA0A00861A3E /* FaceID.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = FaceID.json; sourceTree = ""; }; @@ -1114,8 +1137,8 @@ BFF1ED9C2408111000CC9EF6 /* SendingTariViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendingTariViewController.swift; sourceTree = ""; }; BFF1EDA22408225900CC9EF6 /* sendingTariAnimation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = sendingTariAnimation.json; sourceTree = ""; }; BFF1EDA62408226200CC9EF6 /* sending-background.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "sending-background.mp4"; sourceTree = ""; }; + C0A04F26CF08C60019DAC177 /* Pods_MobileWallet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MobileWallet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E6C8060CF3B17C7AA315F558 /* Pods-MobileWallet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MobileWallet.release.xcconfig"; path = "Target Support Files/Pods-MobileWallet/Pods-MobileWallet.release.xcconfig"; sourceTree = ""; }; - F28D9135828811CA84244432 /* Pods_MobileWallet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MobileWallet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F4E5AB1EC7D42438C3CA62CF /* Pods-MobileWallet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MobileWallet.debug.xcconfig"; path = "Target Support Files/Pods-MobileWallet/Pods-MobileWallet.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1124,13 +1147,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 545B80A2298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework in Frameworks */, 4C363B762574F15E00D99868 /* OpenSSL.xcframework in Frameworks */, 371189732488FF11004D0CE3 /* CloudKit.framework in Frameworks */, 00DC2E22238554FD00036DDC /* libsqlite3.tbd in Frameworks */, 0070A7BC2379847100C25E1C /* LocalAuthentication.framework in Frameworks */, 4C2C95C7237962CB005058AB /* libc++.tbd in Frameworks */, - 123181E402EDECC46E321212 /* Pods_MobileWallet.framework in Frameworks */, + 542725662AFB7E1000FA4973 /* libminotari_wallet_ffi_ios.xcframework in Frameworks */, + 515299BD56554003EF2BD2FD /* Pods_MobileWallet.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1181,6 +1204,7 @@ 541029052A406E4E0095F7CB /* CGFloat+Utils.swift */, 541029072A406E810095F7CB /* CGPoint+Utils.swift */, 3A420596279803DF00A8D49C /* CharacterSet+CustomSets.swift */, + 54AD5F652A420A9F00D223B8 /* Data+Utlis.swift */, 00BBD808237D80C800EBF5E6 /* Date.swift */, 3A685425290AD04600032963 /* DateFormatter+Formats.swift */, 54621F8829E00E38000E9659 /* Dictionary+Tools.swift */, @@ -1192,12 +1216,12 @@ 0042780223E8421100AE7BD9 /* String.swift */, 37CB9F892451A2AD00C495F2 /* String+Emoji.swift */, 54621F8A29E00E78000E9659 /* String+Tools.swift */, + 549565DF2AA1D21E0092A10F /* Task+Utils.swift */, 3AE138D22804AB4100443D34 /* UIApplication+Keyboard.swift */, 37547D512460165500EB59CC /* UIApplication+KeyWindow.swift */, 3A8826C427F1B8320037F779 /* UICollectionViewDiffableDataSource+Animation.swift */, 3AECD493284F46FD00D81C80 /* UIColor+Utils.swift */, 37E0B007249B700F00DFE315 /* UIFont+FontStyle.swift */, - 54F306142A3B1E6300B5689D /* UIImage+Utils.swift */, 37ABB68F24781CE800F08163 /* UILabel.swift */, 54DD2E8529C37A7600C8C0D9 /* UINavigationController+Common.swift */, 54DEF9F22987DA8700C4B749 /* UIScreen+Tools.swift */, @@ -1212,7 +1236,7 @@ 54A6E9F3297F260800A60853 /* UIViewController+Common.swift */, 004277F623E0628A00AE7BD9 /* UIViewController+Content.swift */, 54621F8629E00E04000E9659 /* URL+Tools.swift */, - 54AD5F652A420A9F00D223B8 /* Data+Utlis.swift */, + 5453E43B2AC9D5CA00C7F40D /* UIImage+Utils.swift */, ); path = Extensions; sourceTree = ""; @@ -1292,6 +1316,7 @@ 0087A1A423F4231E00B89EE7 /* AddRecipient */ = { isa = PBXGroup; children = ( + 54F9AB782AAF153A001D0AF5 /* Views */, 540F022F2A6A67DA00691FF5 /* AddRecipientViewController.swift */, 540F02312A6A67E500691FF5 /* AddRecipientView.swift */, 540F02332A6A67EF00691FF5 /* AddRecipientModel.swift */, @@ -1391,7 +1416,6 @@ isa = PBXGroup; children = ( 54C36D092A5D3E8700BD973A /* QR Code Scanner */, - 54ADC07A2A3855840082C455 /* Legacy */, 54D419B12995091B00D496B4 /* Contact Book */, 3ACDA70326B0317300F138B8 /* RestoreWalletFromSeedsProgress */, 3A4376E3269C0BF0006107B0 /* RestoreWalletFromSeeds */, @@ -1428,6 +1452,7 @@ 37875E4324D85C4300C0595B /* LoadingGIFButton.swift */, 00A994322396434B007D9990 /* PulseButton.swift */, 5453242129A68CA0009281A7 /* RoundedButton.swift */, + 54A9C88929F6A54B009B0653 /* RoundedLabeledButton.swift */, 0053872F24065F6C00901A68 /* SlideView.swift */, 004277F223E0407900AE7BD9 /* TextButton.swift */, ); @@ -1461,7 +1486,6 @@ 4C2C95B8237959B3005058AB /* MobileWallet-bridging-header.h */, 54F23DE629BBA0C3001E39A2 /* InfoPlist.strings */, 00E491AC2366E08F007B332D /* Info.plist */, - 3A3E8BF02924F84300490E57 /* Constants.plist */, 00E491992366E08B007B332D /* AppDelegate.swift */, 00E4919B2366E08B007B332D /* SceneDelegate.swift */, 00E491A72366E08F007B332D /* Assets.xcassets */, @@ -1469,10 +1493,9 @@ 001256432371EBAA00A9C067 /* Boards */, 00262EB2236F022000A6C8A0 /* Common */, 37098045249CD48A0004ED2E /* Localizable.strings */, - 00E491A42366E08B007B332D /* MobileWallet.xcdatamodeld */, 00E2BCB5236B469C00C2A105 /* Screens */, - 4C2C95AA2379554B005058AB /* TariLib */, 00E2BCAF236B44CE00C2A105 /* UIElements */, + 54B3A5432AC2CB8600FDC578 /* Libraries */, ); path = MobileWallet; sourceTree = ""; @@ -1480,14 +1503,14 @@ 27F235021D9E2060A3CBB465 /* Frameworks */ = { isa = PBXGroup; children = ( - 545B80A1298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework */, + 542725652AFB7E1000FA4973 /* libminotari_wallet_ffi_ios.xcframework */, 4C363B372574EFE500D99868 /* OpenSSL.xcframework */, 371189722488FF11004D0CE3 /* CloudKit.framework */, 0070A7BB2379847100C25E1C /* LocalAuthentication.framework */, - F28D9135828811CA84244432 /* Pods_MobileWallet.framework */, 00C520AE2469716E0077BF7F /* libc++.1.tbd */, 4C2C95C6237962CB005058AB /* libc++.tbd */, 00DC2E21238554FD00036DDC /* libsqlite3.tbd */, + C0A04F26CF08C60019DAC177 /* Pods_MobileWallet.framework */, ); name = Frameworks; sourceTree = ""; @@ -1503,6 +1526,7 @@ 3708D748247FCF0700807D72 /* Settings */ = { isa = PBXGroup; children = ( + 54D1E1A12ABD920F0021A365 /* Data Collection Settings */, 5444C80429F17C0000BF3875 /* Bluetooth Settings */, 545A9D12294F6EBD008D24A6 /* Theme Settings */, 3A0391E2290BA22400352D73 /* Bug Reporting */, @@ -1785,7 +1809,6 @@ 3AF79D5F2727206200613C24 /* ContactSearchView.swift */, 3A35413926A738F5002AB5A8 /* ContentScrollView.swift */, 5455969729B08D7B00D6719E /* FormTextField.swift */, - 54F306122A3B1E1B00B5689D /* GlassButton.swift */, 3AFAEBF126B15A3300B82603 /* KeyboardAvoidingContentView.swift */, 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */, 54DF83C42A4C5A220040E3F4 /* RoundedGlassContentView.swift */, @@ -1817,6 +1840,7 @@ 54C416242A1CA81700454096 /* PendingDataManager.swift */, 549E1A022A5EC2260063022C /* VideoCaptureManager.swift */, 5482C8CF2A71105700C2C80A /* AnimationHandler.swift */, + 54C7EB5F2AC6B22C00F387DF /* TrackingConsentManager.swift */, ); path = Managers; sourceTree = ""; @@ -2204,6 +2228,7 @@ children = ( 3A87C3B427A94086007A553F /* CoreError.swift */, 3A87C3B627A9409E007A553F /* WalletError.swift */, + 549F5AF12ACAC32900FF4C6F /* PosixError.swift */, ); path = Errors; sourceTree = ""; @@ -2260,7 +2285,8 @@ isa = PBXGroup; children = ( 370E886A24FE9FF800576F61 /* Helpers */, - 370E886224FE974100576F61 /* OnionSettings.swift */, + 5494BFAF2AF0E98E00791A52 /* TorConnectionStatus.swift */, + 5494BFB12AF0E9B500791A52 /* TorError.swift */, ); path = Tor; sourceTree = ""; @@ -2598,10 +2624,8 @@ 543DF198293F37DB0031EA70 /* Bridges */ = { isa = PBXGroup; children = ( - 370E886024FE7AE800576F61 /* BridgesConfigurationViewController.swift */, - 378EE9A2250126BE009615B5 /* CustomBridgesViewController.swift */, - 543DF199293F37E70031EA70 /* CustomBridgesHeaderView.swift */, - 543DF19B293F3DA90031EA70 /* BridgesConfigurationFooterView.swift */, + 54FCC36C2AA5EF8900CA0025 /* Tor Bridges List */, + 54FCC36D2AA5EF9F00CA0025 /* Custom Tor Bridges */, ); path = Bridges; sourceTree = ""; @@ -2658,6 +2682,14 @@ path = Pager; sourceTree = ""; }; + 5453E43F2AC9DB5E00C7F40D /* Views */ = { + isa = PBXGroup; + children = ( + 543DF19B293F3DA90031EA70 /* TorBridgesFooterView.swift */, + ); + path = Views; + sourceTree = ""; + }; 5455969929B0A67400D6719E /* Managers */ = { isa = PBXGroup; children = ( @@ -2788,6 +2820,8 @@ 547A85D429AD23D800D94985 /* MenuTableView.swift */, 547A85D229ACF2D100D94985 /* MenuCell.swift */, 547A85D629AD248400D94985 /* MenuTableHeaderView.swift */, + 548C4C312AC1689B00E376EF /* SwitchMenuCell.swift */, + 54D835572AC6C99D00D74FE8 /* AccessoryImageMenuCell.swift */, ); path = Menu; sourceTree = ""; @@ -2820,31 +2854,12 @@ path = Views; sourceTree = ""; }; - 54ADC07A2A3855840082C455 /* Legacy */ = { - isa = PBXGroup; - children = ( - 54ADC0842A3856B50082C455 /* AddRecipient */, - ); - path = Legacy; - sourceTree = ""; - }; - 54ADC0842A3856B50082C455 /* AddRecipient */ = { + 54B3A5432AC2CB8600FDC578 /* Libraries */ = { isa = PBXGroup; children = ( - 54ADC0892A3857280082C455 /* Views */, - ); - path = AddRecipient; - sourceTree = ""; - }; - 54ADC0892A3857280082C455 /* Views */ = { - isa = PBXGroup; - children = ( - 54ADC0922A3858C40082C455 /* ErrorView.swift */, - 54ADC0902A38582B0082C455 /* ContactAvatarView.swift */, - 54ADC08C2A3857840082C455 /* AddRecipientSectionHeaderView.swift */, - 54ADC08D2A3857840082C455 /* ContactCell.swift */, + 4C2C95AA2379554B005058AB /* TariLib */, ); - path = Views; + path = Libraries; sourceTree = ""; }; 54B7F4432996582B00BB484B /* Views */ = { @@ -2853,7 +2868,6 @@ 54B7F4442996583800BB484B /* ContactBookCell.swift */, 54CF5E002A27460700B01F21 /* ContactBookBluetoothCell.swift */, 5488068829D4244800C2A0F9 /* ContactBookShareBar.swift */, - 54A9C88929F6A54B009B0653 /* ContactBookShareButton.swift */, ); path = Views; sourceTree = ""; @@ -2879,6 +2893,17 @@ path = "Data Models"; sourceTree = ""; }; + 54D1E1A12ABD920F0021A365 /* Data Collection Settings */ = { + isa = PBXGroup; + children = ( + 54D1E1A22ABD92290021A365 /* DataCollectionSettingsConstructor.swift */, + 54D1E1A42ABD92380021A365 /* DataCollectionSettingsModel.swift */, + 54D1E1A62ABD92420021A365 /* DataCollectionSettingsViewController.swift */, + 54D1E1A82ABD92490021A365 /* DataCollectionSettingsView.swift */, + ); + path = "Data Collection Settings"; + sourceTree = ""; + }; 54D419B12995091B00D496B4 /* Contact Book */ = { isa = PBXGroup; children = ( @@ -2926,6 +2951,47 @@ path = Views; sourceTree = ""; }; + 54F9AB782AAF153A001D0AF5 /* Views */ = { + isa = PBXGroup; + children = ( + 54ADC0922A3858C40082C455 /* ErrorView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 54F9C5A22AA7121F00DA87D8 /* Views */ = { + isa = PBXGroup; + children = ( + 54F9C5A32AA712F100DA87D8 /* CustomTorBridgesInputCell.swift */, + 5477518C2AAB227000B78E1D /* CustomTorBridgesHeaderView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 54FCC36C2AA5EF8900CA0025 /* Tor Bridges List */ = { + isa = PBXGroup; + children = ( + 5453E43F2AC9DB5E00C7F40D /* Views */, + 54FCC3662AA5C2D100CA0025 /* TorBridgesViewController.swift */, + 54FCC3682AA5C2DB00CA0025 /* TorBridgesView.swift */, + 54FCC36A2AA5C2E400CA0025 /* TorBridgesModel.swift */, + 5453E43D2AC9D95900C7F40D /* TorBridgesConstructor.swift */, + ); + path = "Tor Bridges List"; + sourceTree = ""; + }; + 54FCC36D2AA5EF9F00CA0025 /* Custom Tor Bridges */ = { + isa = PBXGroup; + children = ( + 54F9C5A22AA7121F00DA87D8 /* Views */, + 54FCC36E2AA5F00900CA0025 /* CustomTorBridgesViewController.swift */, + 54FCC3702AA5FF1100CA0025 /* CustomTorBridgesView.swift */, + 54FCC3722AA5FF1B00CA0025 /* CustomTorBridgesModel.swift */, + 54A148EB2AA88B7B00C68D9D /* CustomTorBridgesConstructor.swift */, + ); + path = "Custom Tor Bridges"; + sourceTree = ""; + }; 71F4BDBF8B1AF18134115485 /* Pods */ = { isa = PBXGroup; children = ( @@ -3106,8 +3172,9 @@ 00E4918E2366E08B007B332D /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1310; - LastUpgradeCheck = 1400; + LastUpgradeCheck = 1500; ORGANIZATIONNAME = "Jason van den Berg"; TargetAttributes = { 00E491952366E08B007B332D = { @@ -3250,6 +3317,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5494BFB32AF122A600791A52 /* RoundedLabeledButton.swift in Sources */, 3A51B1AD28F00A4F0094FDD6 /* Logger.swift in Sources */, 3A7DABA028FDC9F3002CC013 /* LogConstructor.swift in Sources */, 3A7DAB9A28FDC972002CC013 /* LogViewController.swift in Sources */, @@ -3266,9 +3334,11 @@ 54F2E34929EE6BE900A7A15A /* ContactTransactionListConstructor.swift in Sources */, 3A72DC3D26DFA02900E0BC43 /* NetworkManager.swift in Sources */, 3AE1D58227E0A8C8001A46C4 /* TransactionDetailsConstructor.swift in Sources */, + 549F5AF22ACAC32900FF4C6F /* PosixError.swift in Sources */, 54D88EA22A5ADBFA0075DBC1 /* TransactionFormatter.swift in Sources */, 3A03683127DF211D00F304A2 /* TransactionDetailsView.swift in Sources */, 545A9D1A294F6F6B008D24A6 /* ThemeSettingsConstructor.swift in Sources */, + 54FCC3672AA5C2D100CA0025 /* TorBridgesViewController.swift in Sources */, 3A82455827E1EE38003B6B59 /* TransactionDetailsEmojiView.swift in Sources */, 54621F8729E00E04000E9659 /* URL+Tools.swift in Sources */, 3AF97E7027CFCA2000FF6A3F /* ShortcutsManager.swift in Sources */, @@ -3291,8 +3361,10 @@ 3A70C4F7292E51D800212026 /* VersionValidator.swift in Sources */, 3AE138D12804A8B300443D34 /* SuccessToast.swift in Sources */, 5410F5D12951F04B006976DC /* TariWindow.swift in Sources */, + 54D1E1A32ABD92290021A365 /* DataCollectionSettingsConstructor.swift in Sources */, 370E887824FEA54100576F61 /* NetworkTools.m in Sources */, 3A35412C26A72D9F002AB5A8 /* ViewIdentifiable.swift in Sources */, + 548C4C322AC1689B00E376EF /* SwitchMenuCell.swift in Sources */, 3A70433F28E21D3600207D6F /* CompletedTransactionKernel.swift in Sources */, 3A13260127EDE3B900289C8C /* TransactionProgressPresenter.swift in Sources */, 54C416232A1C914200454096 /* LocalNotificationsManager.swift in Sources */, @@ -3305,9 +3377,9 @@ 547A85D529AD23D800D94985 /* MenuTableView.swift in Sources */, 37ABB69424781F8600F08163 /* UILabelWithPadding.swift in Sources */, 3A6A033C2802DD34000432B4 /* PopUpPresenter.swift in Sources */, - 54ADC08E2A3857850082C455 /* AddRecipientSectionHeaderView.swift in Sources */, 3ABBBA8028508AB000A3108D /* UTXOsWalletTextListViewCell.swift in Sources */, 3A8473CA28EC56180015E63A /* TariFeesService.swift in Sources */, + 54D1E1A92ABD92490021A365 /* DataCollectionSettingsView.swift in Sources */, 3A39E57229012C8B000BBEBF /* DropboxBackupService.swift in Sources */, 3AD308642923FDBB009A53CE /* Error+BackupErrors.swift in Sources */, 00E2BCBA236B5BE800C2A105 /* ActionButton.swift in Sources */, @@ -3344,6 +3416,7 @@ 375AFDCF2475387700C62CA1 /* WebBrowserViewController.swift in Sources */, 3A8473D828EC56EF0015E63A /* TariKeyValueService.swift in Sources */, 3A874038278842F500D80823 /* TorManager.swift in Sources */, + 54FCC36F2AA5F00900CA0025 /* CustomTorBridgesViewController.swift in Sources */, 37C8BA47248126B4005BBC05 /* UIScrollView+RefreshControl.swift in Sources */, 3A4949A5283FD603002768AE /* AboutViewController.swift in Sources */, 5444C80C29F17DB700BF3875 /* BluetoothSettingsConstructor.swift in Sources */, @@ -3357,7 +3430,7 @@ 5455969829B08D7B00D6719E /* FormTextField.swift in Sources */, 3A35413126A72DEE002AB5A8 /* SelectBaseNodeCell.swift in Sources */, 3A8473D228EC568A0015E63A /* TariMessageSignService.swift in Sources */, - 543DF19C293F3DA90031EA70 /* BridgesConfigurationFooterView.swift in Sources */, + 543DF19C293F3DA90031EA70 /* TorBridgesFooterView.swift in Sources */, 54103CDA2A555A1A00C456B4 /* TransactionHistoryConstructor.swift in Sources */, 37765D2624A35BA20091AE2A /* AESEncryption.swift in Sources */, 3A82455627E1EE0D003B6B59 /* TransactionDetailsSectionView.swift in Sources */, @@ -3382,8 +3455,8 @@ BFC5532523D9B8E4009130A8 /* UIView+Content.swift in Sources */, 54C36D0F2A5D3EAC00BD973A /* QRCodeScannerView.swift in Sources */, 3A4205A127980FFA00A8D49C /* QRCodeView.swift in Sources */, - 54F306152A3B1E6300B5689D /* UIImage+Utils.swift in Sources */, 3AC05F9B26C3F302002742C6 /* TransactionHistoryHeaderView.swift in Sources */, + 5477518D2AAB227000B78E1D /* CustomTorBridgesHeaderView.swift in Sources */, 3AFC6E032745051400A20287 /* SettingsHeaderView.swift in Sources */, 3AEDBE5127CE477B006B0166 /* DeepLinkEncoder.swift in Sources */, 3AFAEBF226B15A3300B82603 /* KeyboardAvoidingContentView.swift in Sources */, @@ -3399,6 +3472,7 @@ 547A85D729AD248400D94985 /* MenuTableHeaderView.swift in Sources */, 0072BF24247E735700BD28FB /* ReminderNotifications.swift in Sources */, 3A8473C628EC55DB0015E63A /* TariContactsService.swift in Sources */, + 5453E43C2AC9D5CA00C7F40D /* UIImage+Utils.swift in Sources */, 3A2F30B628F594520095E25D /* FileLogger.swift in Sources */, 3A8473D028EC56600015E63A /* TariRecoveryService.swift in Sources */, 5421551029A4AE37000A3F49 /* ContactBookContactListViewController.swift in Sources */, @@ -3425,7 +3499,6 @@ 3708D756247FF81900807D72 /* SettingsParentViewController.swift in Sources */, 5419AA792A4473F90079E745 /* HomeViewController.swift in Sources */, 54DF83C52A4C5A220040E3F4 /* RoundedGlassContentView.swift in Sources */, - 00E491A62366E08B007B332D /* MobileWallet.xcdatamodeld in Sources */, 5460258A29A74DAB00CF5764 /* ContactDetailsView.swift in Sources */, 545A9D18294F6F50008D24A6 /* ThemeSettingsModel.swift in Sources */, 3ABF91A7283FFA6E0001C766 /* AboutView.swift in Sources */, @@ -3436,7 +3509,6 @@ 3AC6F11228E9C6590068E6FF /* WalletCallbacksManager.swift in Sources */, 3AEDBE4D27CE45C0006B0166 /* DeepLinkFormatter.swift in Sources */, 540CEA822A54076A00BD26C7 /* HomeBackgroundView.swift in Sources */, - 54ADC08F2A3857850082C455 /* ContactCell.swift in Sources */, 3ACDA70E26B0320900F138B8 /* SeedWordsRecoveryProgressModel.swift in Sources */, 3AE138CF2804A88500443D34 /* ToastPresenter.swift in Sources */, 3A0EA98226AA8E64002612D4 /* TokenInputView.swift in Sources */, @@ -3454,10 +3526,8 @@ 546B032A2983F33600DBED8E /* OnboardingPagerView.swift in Sources */, 001F6CDC238011CA00FA7002 /* PublicKey.swift in Sources */, 5453242029A68310009281A7 /* PageToolbarView.swift in Sources */, - 370E886324FE974100576F61 /* OnionSettings.swift in Sources */, 545A9D16294F6EEA008D24A6 /* ThemeSettingsView.swift in Sources */, 544692A429B6059C0081085D /* ContactsManager.swift in Sources */, - 378EE9A3250126BE009615B5 /* CustomBridgesViewController.swift in Sources */, 542585C52A332F32009D12CD /* RotaryMenuOverlayView.swift in Sources */, 540F02412A6A7F8000691FF5 /* TransactionsConstructor.swift in Sources */, 5444C80829F17DA100BF3875 /* BluetoothSettingsView.swift in Sources */, @@ -3466,11 +3536,12 @@ 3AE138CB28044B1E00443D34 /* CustomDeeplinkPopUpContentView.swift in Sources */, 3A2F30B828F5946B0095E25D /* CrashLogger.swift in Sources */, 3A66109927AD364E0038EB5B /* SendingTariModel.swift in Sources */, + 54FCC36B2AA5C2E400CA0025 /* TorBridgesModel.swift in Sources */, 54621F9729E01443000E9659 /* DeepLinkKeyedEncodingContainer.swift in Sources */, + 5494BFB02AF0E98E00791A52 /* TorConnectionStatus.swift in Sources */, 3A5A3D5A29016E1300B689C6 /* BackupWalletSettingsView.swift in Sources */, 00A66527243C766D0046E730 /* ConnectionMonitor.swift in Sources */, 3AC6F11428E9D8840068E6FF /* CustomBridgesHandable.swift in Sources */, - 54ADC0912A38582B0082C455 /* ContactAvatarView.swift in Sources */, 00E4919A2366E08B007B332D /* AppDelegate.swift in Sources */, 3A62674626D76E6B007F9895 /* SelectNetworkViewController.swift in Sources */, 3AEDBE4F27CE46D6006B0166 /* DeepLinkDecoder.swift in Sources */, @@ -3478,6 +3549,7 @@ 54D419B32995092F00D496B4 /* ContactBookModel.swift in Sources */, 3A66109627AD07F10038EB5B /* SendingTariView.swift in Sources */, 3A4376E9269C0C58006107B0 /* RestoreWalletFromSeedsModel.swift in Sources */, + 54FCC3732AA5FF1B00CA0025 /* CustomTorBridgesModel.swift in Sources */, 54621F8929E00E38000E9659 /* Dictionary+Tools.swift in Sources */, 3ADF96CE2858A93000A3C888 /* ContextualButton.swift in Sources */, 546CE7F929AF523F00264699 /* ContactBookFormView.swift in Sources */, @@ -3502,7 +3574,6 @@ 3A793BF128031F540094DF23 /* PopUpImageHeaderView.swift in Sources */, 54AA5D5E298126CA0031A396 /* OnboardingPageView.swift in Sources */, 00E4919C2366E08B007B332D /* SceneDelegate.swift in Sources */, - 54F306132A3B1E1B00B5689D /* GlassButton.swift in Sources */, 37049270247EA0770034EE5D /* RestoreWalletViewController.swift in Sources */, 3ABBC5E02726D104001BB864 /* YatTransactionConstructor.swift in Sources */, 5482C8D22A714A7300C2C80A /* BaseToolbar.swift in Sources */, @@ -3540,7 +3611,6 @@ 544D9D4E296D9B3300D8ECEF /* UnblindedOutput.swift in Sources */, 544013A429E0003100B5DD6D /* ContactListDeeplink.swift in Sources */, 004277F323E0407900AE7BD9 /* TextButton.swift in Sources */, - 370E886124FE7AE800576F61 /* BridgesConfigurationViewController.swift in Sources */, 3A983A5A27C62A3900F0AB61 /* VerifySeedWordsViewController.swift in Sources */, 54A6E9F2297F1FBA00A60853 /* PopUpStagedWalletSecurityHeaderView.swift in Sources */, 540F02302A6A67DA00691FF5 /* AddRecipientViewController.swift in Sources */, @@ -3559,6 +3629,7 @@ 3AC877E628741680006F327B /* UTXOsWalletLoadingView.swift in Sources */, 37547D5624601BF600EB59CC /* UIView+GlobalFrame.swift in Sources */, BF8316FD23EF7EAA00235403 /* LAContext.swift in Sources */, + 54D835582AC6C99D00D74FE8 /* AccessoryImageMenuCell.swift in Sources */, 54ADC0932A3858C40082C455 /* ErrorView.swift in Sources */, 54885B1B29E802D7009175AC /* BLEConstants.swift in Sources */, 54ED0FBE2A5D315400ED1F7E /* SearchField.swift in Sources */, @@ -3569,6 +3640,7 @@ 5488068929D4244800C2A0F9 /* ContactBookShareBar.swift in Sources */, 3A8005CB28EAF3A90022A38A /* TransactionValidationData.swift in Sources */, 005387242405136700901A68 /* AddNoteViewController.swift in Sources */, + 5453E43E2AC9D95900C7F40D /* TorBridgesConstructor.swift in Sources */, 3AE9F4E42795A260006101D1 /* RequestTariAmountModel.swift in Sources */, 3AA2E2212885755A00D30B62 /* NetworkMonitor.swift in Sources */, 3A03A600288932DE00788A02 /* SplashView.swift in Sources */, @@ -3609,6 +3681,7 @@ 00A994332396434B007D9990 /* PulseButton.swift in Sources */, 5460258629A74D8200CF5764 /* ContactDetailsModel.swift in Sources */, 545A9D21294F9CCE008D24A6 /* UserSettings.swift in Sources */, + 5494BFB22AF0E9B500791A52 /* TorError.swift in Sources */, 3A237C7B27D8D0F7005FA6AB /* SeedWordsListModel.swift in Sources */, 3A84CDD42846088D005F2F8D /* UTXOsWalletView.swift in Sources */, 5430B97D29B7402600C80AA2 /* LinkContactsConstructor.swift in Sources */, @@ -3619,6 +3692,7 @@ 3A6F3FF8283BF980005D1793 /* TariFeePerGramStats.swift in Sources */, 3A5A3D5829016DE300B689C6 /* BackupWalletSettingsViewController.swift in Sources */, 3A94DB21283E7CEF00B0A740 /* TransactionFeesManager.swift in Sources */, + 54D1E1A52ABD92380021A365 /* DataCollectionSettingsModel.swift in Sources */, 3ACC4F602876D2F100632F64 /* NSAttributedString+Format.swift in Sources */, 3A72DC4126DFA1E100E0BC43 /* NetworkSettings.swift in Sources */, 3ADA73DD270A02B900D50E3B /* WalletSettings.swift in Sources */, @@ -3640,12 +3714,12 @@ 54AA5D5C2981267C0031A396 /* OnboardingPageViewController.swift in Sources */, 3A2B95682864801D0085BE21 /* UTXOsWalletTileListLayout.swift in Sources */, 54F2E34E29EEAEBB00A7A15A /* ContactTransactionListPlaceholder.swift in Sources */, + 54C7EB602AC6B22C00F387DF /* TrackingConsentManager.swift in Sources */, 373CCDC52490F3B600D0A2C9 /* FileManager+SecureCopy.swift in Sources */, 3ACFA429278EC9BF00EBED98 /* ProfileView.swift in Sources */, 540CB6F329C1DE26003FACEF /* AddContactViewController.swift in Sources */, 370E887224FEA32600576F61 /* Ipv6Tester.swift in Sources */, 540CEA842A5407A700BD26C7 /* WaveView.swift in Sources */, - 543DF19A293F37E70031EA70 /* CustomBridgesHeaderView.swift in Sources */, 5455969B29B0A6B500D6719E /* InternalContactsManager.swift in Sources */, 49996E1923F164BA002B6696 /* AnimatedBalanceLabel.swift in Sources */, 54A6E9F0297F1F9900A60853 /* StagedWalletSecurityManager.swift in Sources */, @@ -3675,6 +3749,7 @@ 3A8473BF28EC51780015E63A /* TransactionValidationStatus.swift in Sources */, 542585CA2A3346C0009D12CD /* RotaryMenuButton.swift in Sources */, 5419AA7B2A4474040079E745 /* HomeView.swift in Sources */, + 54F9C5A42AA712F100DA87D8 /* CustomTorBridgesInputCell.swift in Sources */, 37B444A9248949B800592D92 /* Checkbox.swift in Sources */, 546CE7F629AF517900264699 /* FormOverlayView.swift in Sources */, BFF1ED9D2408111000CC9EF6 /* SendingTariViewController.swift in Sources */, @@ -3691,6 +3766,7 @@ 3AE138D52804ACE100443D34 /* WebBrowserPresenter.swift in Sources */, 3A8A0558285370B200D48FCE /* TickButton.swift in Sources */, 3730E15124C884D500827DFA /* MenuTabBarController.swift in Sources */, + 54D1E1A72ABD92420021A365 /* DataCollectionSettingsViewController.swift in Sources */, 3A6A033A2802DCE7000432B4 /* TariPopUp.swift in Sources */, 371A0818247290C000F97713 /* TransitionLabel.swift in Sources */, 3A4205922798001A00A8D49C /* AmountKeyboardView.swift in Sources */, @@ -3715,6 +3791,7 @@ 546CE7F429AF514A00264699 /* FormOverlay.swift in Sources */, 542585C32A332F08009D12CD /* RotaryMenuOverlay.swift in Sources */, 3A3D70BB291A637100A8CD7A /* BaseMenuTableView.swift in Sources */, + 54FCC3712AA5FF1100CA0025 /* CustomTorBridgesView.swift in Sources */, 3A21FC5128FFD7B8004B09A0 /* PopUpTableView.swift in Sources */, 544D9D4C296D9B2000D8ECEF /* UnblindedOutputs.swift in Sources */, BFAB5D1123FDEA69009E8563 /* EmojiIdView.swift in Sources */, @@ -3725,10 +3802,11 @@ 54CB24F829FA65230008632D /* PaymentInfo.swift in Sources */, 3A13260327EDE42A00289C8C /* GradientView.swift in Sources */, 37BB696B245705D20013AC4D /* RadialGradientView.swift in Sources */, + 54FCC3692AA5C2DB00CA0025 /* TorBridgesView.swift in Sources */, 54D88EA42A5AEAEC0075DBC1 /* TransactionDynamicModel.swift in Sources */, 3A7E06DA287831BD00D15190 /* UTXOsEstimationLabel.swift in Sources */, 0042780323E8421200AE7BD9 /* String.swift in Sources */, - 54A9C88A29F6A54B009B0653 /* ContactBookShareButton.swift in Sources */, + 54A148EC2AA88B7B00C68D9D /* CustomTorBridgesConstructor.swift in Sources */, 540CB6F529C1DE42003FACEF /* AddContactView.swift in Sources */, 54D4747929EDC21D003D14E6 /* FormOverlayPresenter.swift in Sources */, 3A793BF3280354830094DF23 /* PopUpHeaderWithSubtitle.swift in Sources */, @@ -3736,6 +3814,7 @@ 3A420594279802E500A8D49C /* BaseButton.swift in Sources */, 3ABBBA7E2850815C00A3108D /* UTXOsWalletTileListView.swift in Sources */, 546B03232983B49400DBED8E /* StylizedLabel.swift in Sources */, + 549565E02AA1D21E0092A10F /* Task+Utils.swift in Sources */, 54DE32D02930BE3D0060108A /* ColorTheme.swift in Sources */, 54621F9929E01465000E9659 /* DeepLinkUnkeyedEncodingContainer.swift in Sources */, 376B502324924BB700278ACC /* PendingView.swift in Sources */, @@ -3932,11 +4011,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/MobileWallet/TariLib", - ); - MARKETING_VERSION = 0.23.0; + LIBRARY_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 0.24.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3967,11 +4043,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/MobileWallet/TariLib", - ); - MARKETING_VERSION = 0.23.0; + LIBRARY_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 0.24.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4066,19 +4139,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCVersionGroup section */ - 00E491A42366E08B007B332D /* MobileWallet.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - 00E491A52366E08B007B332D /* MobileWallet.xcdatamodel */, - ); - currentVersion = 00E491A52366E08B007B332D /* MobileWallet.xcdatamodel */; - path = MobileWallet.xcdatamodeld; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; - }; -/* End XCVersionGroup section */ }; rootObject = 00E4918E2366E08B007B332D /* Project object */; } diff --git a/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme b/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme index e26c51ae..46cd561f 100644 --- a/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme +++ b/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme @@ -1,6 +1,6 @@ Bool { extensionPointIdentifier != .keyboard } - - // MARK: - Core Data stack - - lazy var persistentContainer: NSPersistentCloudKitContainer = { - /* - The persistent container for the application. This implementation - creates and returns a container, having loaded the store for the - application to it. This property is optional since there are legitimate - error conditions that could cause the creation of the store to fail. - */ - let container = NSPersistentCloudKitContainer(name: "MobileWallet") - container.loadPersistentStores(completionHandler: { (_, error) in - if let error = error as NSError? { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. - // You should not use this function in a shipping application, although it may be useful during development. - - /* - Typical reasons for an error here include: - * The parent directory does not exist, cannot be created, or disallows writing. - * The persistent store is not accessible, due to permissions or data protection when the device is locked. - * The device is out of space. - * The store could not be migrated to the current model version. - Check the error message to determine what the actual problem was. - */ - fatalError("Unresolved error \(error), \(error.userInfo)") - } - }) - return container - }() - - // MARK: - Core Data Saving support - - func saveContext () { - let context = persistentContainer.viewContext - if context.hasChanges { - do { - try context.save() - } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nserror = error as NSError - fatalError("Unresolved error \(nserror), \(nserror.userInfo)") - } - } - } - } diff --git a/MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/ico-yat.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/ico-yat.pdf deleted file mode 100644 index b906cd7a..00000000 Binary files a/MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/ico-yat.pdf and /dev/null differ diff --git a/MobileWallet/Assets.xcassets/Icons/Analytics.imageset/Analytics.pdf b/MobileWallet/Assets.xcassets/Icons/Analytics.imageset/Analytics.pdf new file mode 100644 index 00000000..7bca087a Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Analytics.imageset/Analytics.pdf differ diff --git a/MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Analytics.imageset/Contents.json similarity index 70% rename from MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Icons/Analytics.imageset/Contents.json index ac20844e..db16a881 100644 --- a/MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/Contents.json +++ b/MobileWallet/Assets.xcassets/Icons/Analytics.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ico-yat.pdf", + "filename" : "Analytics.pdf", "idiom" : "universal" } ], @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/MobileWallet/Assets.xcassets/Images/Security/Onboarding/background.imageset/background.pdf b/MobileWallet/Assets.xcassets/Images/Security/Onboarding/Background.imageset/Background.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Images/Security/Onboarding/background.imageset/background.pdf rename to MobileWallet/Assets.xcassets/Images/Security/Onboarding/Background.imageset/Background.pdf diff --git a/MobileWallet/Assets.xcassets/Images/Security/Onboarding/background.imageset/Contents.json b/MobileWallet/Assets.xcassets/Images/Security/Onboarding/Background.imageset/Contents.json similarity index 53% rename from MobileWallet/Assets.xcassets/Images/Security/Onboarding/background.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Images/Security/Onboarding/Background.imageset/Contents.json index 323999d5..3d2e078f 100644 --- a/MobileWallet/Assets.xcassets/Images/Security/Onboarding/background.imageset/Contents.json +++ b/MobileWallet/Assets.xcassets/Images/Security/Onboarding/Background.imageset/Contents.json @@ -1,15 +1,12 @@ { "images" : [ { - "filename" : "background.pdf", + "filename" : "Background.pdf", "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" } } diff --git a/MobileWallet/Assets.xcassets/Images/TabBar/Send.imageset/Contents.json b/MobileWallet/Assets.xcassets/Images/TabBar/Send.imageset/Contents.json index ec28bb59..42a1c30a 100644 --- a/MobileWallet/Assets.xcassets/Images/TabBar/Send.imageset/Contents.json +++ b/MobileWallet/Assets.xcassets/Images/TabBar/Send.imageset/Contents.json @@ -3,6 +3,16 @@ { "filename" : "TabBarSend.pdf", "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "send-dark.pdf", + "idiom" : "universal" } ], "info" : { diff --git a/MobileWallet/Assets.xcassets/Images/TabBar/Send.imageset/send-dark.pdf b/MobileWallet/Assets.xcassets/Images/TabBar/Send.imageset/send-dark.pdf new file mode 100644 index 00000000..06aa6875 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Images/TabBar/Send.imageset/send-dark.pdf differ diff --git a/MobileWallet/Common/AppRouter.swift b/MobileWallet/Common/AppRouter.swift index 190da703..dd188709 100644 --- a/MobileWallet/Common/AppRouter.swift +++ b/MobileWallet/Common/AppRouter.swift @@ -118,7 +118,10 @@ enum AppRouter { } static func moveToProfile() { - present(controller: ProfileViewController(backButtonType: .close)) + let controller = ProfileViewController(backButtonType: .close) + let navigationController = AlwaysPoppableNavigationController(rootViewController: controller) + navigationController.isNavigationBarHidden = true + tabBar?.presentOnFullScreen(navigationController) } // MARK: - Modal Actions @@ -184,8 +187,8 @@ enum AppRouter { } static func presentCustomTorBridgesForm(bridges: String?) { - let controller = CustomBridgesViewController(bridgesConfiguration: BridgesConfiguration(bridges: .none, customBridges: nil), initialValue: bridges) - present(controller: controller) + let controller = CustomTorBridgesConstructor.buildScene(bridges: bridges) + presentOnTop(controller: controller) } // MARK: - External Apps diff --git a/MobileWallet/Common/Errors/PosixError.swift b/MobileWallet/Common/Errors/PosixError.swift new file mode 100644 index 00000000..03b25fc7 --- /dev/null +++ b/MobileWallet/Common/Errors/PosixError.swift @@ -0,0 +1,56 @@ +// PosixError.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 02/10/2023 + Using Swift 5.0 + Running on macOS 13.5 + + 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. +*/ + +struct PosixError: CoreError { + + static var connectionRefused: Self { Self(code: 61) } + + let code: Int + var domain: String { "POSIX" } +} + +extension Error { + + var posixError: PosixError? { + let error = self as NSError + guard error.domain == NSPOSIXErrorDomain else { return nil } + return PosixError(code: error.code) + } +} diff --git a/MobileWallet/Common/Extensions/Task+Utils.swift b/MobileWallet/Common/Extensions/Task+Utils.swift new file mode 100644 index 00000000..9054d137 --- /dev/null +++ b/MobileWallet/Common/Extensions/Task+Utils.swift @@ -0,0 +1,47 @@ +// Task+Utils.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 01/09/2023 + Using Swift 5.0 + Running on macOS 13.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. +*/ + +extension Task where Failure == Never, Success == Never { + + static func sleep(seconds: Double) async throws { + let nanoseconds = UInt64(1000000000 * seconds) + try await Task.sleep(nanoseconds: nanoseconds) + } +} diff --git a/MobileWallet/Common/Extensions/UIImage+Utils.swift b/MobileWallet/Common/Extensions/UIImage+Utils.swift index 28bdd907..9d06fbe9 100644 --- a/MobileWallet/Common/Extensions/UIImage+Utils.swift +++ b/MobileWallet/Common/Extensions/UIImage+Utils.swift @@ -2,9 +2,9 @@ /* Package MobileWallet - Created by Adrian Truszczyński on 15/06/2023 + Created by Adrian Truszczyński on 01/10/2023 Using Swift 5.0 - Running on macOS 13.4 + Running on macOS 13.5 Copyright 2019 The Tari Project @@ -42,18 +42,16 @@ import UIKit extension UIImage { - var invertedMask: UIImage? { + func makeCIImage() -> CIImage? { - guard let ciImage = CIImage(image: self), - let backgroundFilter = CIFilter(name: "CIConstantColorGenerator", parameters: [kCIInputColorKey: CIColor.black]), - let inputColorFilter = CIFilter(name: "CIConstantColorGenerator", parameters: [kCIInputColorKey: CIColor.clear]), - let inputImage = inputColorFilter.outputImage, - let backgroundImage = backgroundFilter.outputImage, - let blendFilter = CIFilter(name: "CIBlendWithAlphaMask", parameters: [kCIInputImageKey: inputImage, kCIInputBackgroundImageKey: backgroundImage, kCIInputMaskImageKey: ciImage]), - let filterOutput = blendFilter.outputImage, - let outputImage = CIContext().createCGImage(filterOutput, from: ciImage.extent) - else { return nil } + if let ciImage { + return ciImage + } - return UIImage(cgImage: outputImage) - } + if let cgImage { + return CIImage(cgImage: cgImage) + } + + return nil + } } diff --git a/MobileWallet/Common/Managers/ErrorMessageManager.swift b/MobileWallet/Common/Managers/ErrorMessageManager.swift index e915f726..fa670a7f 100644 --- a/MobileWallet/Common/Managers/ErrorMessageManager.swift +++ b/MobileWallet/Common/Managers/ErrorMessageManager.swift @@ -57,8 +57,6 @@ enum ErrorMessageManager { return model(internalWalletError: error) case let error as SeedWords.InternalError: return model(seedWordsError: error) - case let error as TorManager.TorError: - return model(torError: error) default: return genericErrorModel } @@ -98,22 +96,6 @@ enum ErrorMessageManager { return MessageModel(title: localized("restore_from_seed_words.error.title"), message: message.appending(signature: seedWordsError.signature), type: .error) } - private static func model(torError: TorManager.TorError) -> MessageModel { - - let message: String - - switch torError { - case .connectionFailed: - message = localized("Onion_Error.error.invalid_bridges") - case .missingCookie: - message = localized("Onion_Error.error.missing_cookie_file") - case .connectionTimeout: - message = localized("Onion_Error.error.connectionError") - } - - return MessageModel(title: localized("Onion_Error.error.title.onionError"), message: message, type: .error) - } - private static func model(internalWalletError: FFIWalletManager.GeneralError) -> MessageModel { let message: String diff --git a/MobileWallet/Common/Managers/MigrationManager.swift b/MobileWallet/Common/Managers/MigrationManager.swift index f880ac9d..4e2c3e52 100644 --- a/MobileWallet/Common/Managers/MigrationManager.swift +++ b/MobileWallet/Common/Managers/MigrationManager.swift @@ -42,7 +42,7 @@ enum MigrationManager { // MARK: - Properties - private static let minValidVersion = "0.50.0-hotfix.1" + private static let minValidVersion = "0.52.0" // MARK: - Actions @@ -61,9 +61,21 @@ enum MigrationManager { } } - private static func isWalletHasValidVersion() async -> Bool { + private static func isWalletHasValidVersion(retryCount: Int = 0) async -> Bool { - if let version = try? await Tari.shared.walletVersion() { + 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) + } + + if let version { let isValid = VersionValidator.compare(version, isHigherOrEqualTo: minValidVersion) Logger.log(message: "Min. Valid Wallet Version: \(minValidVersion), Local Wallet Version: \(version), isValid: \(isValid)", domain: .general, level: .info) return isValid diff --git a/MobileWallet/Common/Managers/TrackingConsentManager.swift b/MobileWallet/Common/Managers/TrackingConsentManager.swift new file mode 100644 index 00000000..e872e80f --- /dev/null +++ b/MobileWallet/Common/Managers/TrackingConsentManager.swift @@ -0,0 +1,70 @@ +// TrackingConsentManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 29/09/2023 + Using Swift 5.0 + Running on macOS 13.5 + + 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 TrackingConsentManager { + + @MainActor static func handleTrackingConsent() { + guard GroupUserDefaults.isTrackingEnabled == nil else { return } + showDialog() + } + + @MainActor private static func showDialog() { + + let model = PopUpDialogModel( + title: localized("tracking.pop_up.consent.title"), + message: localized("tracking.pop_up.consent.message"), + buttons: [ + PopUpDialogButtonModel(title: localized("tracking.pop_up.consent.button.yes"), type: .normal, callback: { turnOnTracking() }), + PopUpDialogButtonModel(title: localized("tracking.pop_up.consent.button.no"), type: .text, callback: { turnOffTracking() }) + ], + hapticType: .error + ) + + PopUpPresenter.showPopUp(model: model) + } + + private static func turnOnTracking() { + AppConfigurator.shared.isCrashLoggerEnabled = true + } + + private static func turnOffTracking() { + AppConfigurator.shared.isCrashLoggerEnabled = false + } +} diff --git a/MobileWallet/Common/Managers/WalletTransactionsManager.swift b/MobileWallet/Common/Managers/WalletTransactionsManager.swift index 15c57271..b9206bb2 100644 --- a/MobileWallet/Common/Managers/WalletTransactionsManager.swift +++ b/MobileWallet/Common/Managers/WalletTransactionsManager.swift @@ -125,13 +125,13 @@ final class WalletTransactionsManager { result(.success) return } - startListeningForWalletEvents(transactionID: transactionID, recipientHex: address, result: result) + try startListeningForWalletEvents(transactionID: transactionID, publicKey: tariAddress.publicKey, result: result) } catch { result(.failure(.transactionError(error: error))) } } - private func startListeningForWalletEvents(transactionID: UInt64, recipientHex: String, result: @escaping (Result) -> Void) { + private func startListeningForWalletEvents(transactionID: UInt64, publicKey: String, result: @escaping (Result) -> Void) { WalletCallbacksManager.shared.transactionSendResult .filter { $0.identifier == transactionID } @@ -142,7 +142,7 @@ final class WalletTransactionsManager { return } - self?.sendPushNotificationToRecipient(recipientHex: recipientHex) + self?.sendPushNotificationToRecipient(publicKey: publicKey) Logger.log(message: "Transaction send successful", domain: .general, level: .info) result(.success) @@ -150,11 +150,11 @@ final class WalletTransactionsManager { .store(in: &cancellables) } - private func sendPushNotificationToRecipient(recipientHex: String) { + private func sendPushNotificationToRecipient(publicKey: String) { do { try NotificationManager.shared.sendToRecipient( - recipientHex: recipientHex, + publicKey: publicKey, onSuccess: { Logger.log(message: "Recipient has been notified", domain: .general, level: .info) }, onError: { Logger.log(message: "Failed to notify recipient: \($0.localizedDescription)", domain: .general, level: .error) } ) diff --git a/MobileWallet/Common/NotificationManager.swift b/MobileWallet/Common/NotificationManager.swift index 29864489..9461ada5 100644 --- a/MobileWallet/Common/NotificationManager.swift +++ b/MobileWallet/Common/NotificationManager.swift @@ -81,12 +81,9 @@ final class NotificationManager { static let shared = NotificationManager() - private static let hasRegisteredTokenKey = "hasRegisteredPushToken" - private let notificationCenter = UNUserNotificationCenter.current() private let options: UNAuthorizationOptions = [.alert, .sound, .badge] - private var hasRegisteredToken: Bool { UserDefaults.standard.bool(forKey: NotificationManager.hasRegisteredTokenKey) } private var cancellables = Set() private init() { @@ -107,12 +104,6 @@ final class NotificationManager { return } - guard !hasRegisteredToken else { - Logger.log(message: "Already registered for push notifications", domain: .general, level: .info) - completionHandler?(true) - return - } - notificationCenter.requestAuthorization(options: options) { [weak self] _, error in guard let error else { self?.registerWithAPNS(completionHandler) @@ -198,7 +189,6 @@ final class NotificationManager { path: "/register/\(messageData.hex)", requestPayload: requestPayload, onSuccess: { - UserDefaults.standard.set(true, forKey: NotificationManager.hasRegisteredTokenKey) Logger.log(message: "Registered device token", domain: .general, level: .info) }) { (error) in Logger.log(message: "Failed to register device token: \(error.localizedDescription)", domain: .general, level: .error) @@ -208,8 +198,8 @@ final class NotificationManager { } } - func sendToRecipient(recipientHex: String, onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) throws { - let messageData = try sign(message: recipientHex) + func sendToRecipient(publicKey: String, onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) throws { + let messageData = try sign(message: publicKey) let requestPayload = try JSONEncoder().encode( SendNotificationServerRequest( @@ -219,7 +209,7 @@ final class NotificationManager { ) ) - pushServerRequest(path: "/send/\(recipientHex)", requestPayload: requestPayload, onSuccess: onSuccess, onError: onError) + pushServerRequest(path: "/send/\(publicKey)", requestPayload: requestPayload, onSuccess: onSuccess, onError: onError) } // TODO remove this is local push notifications work better @@ -237,7 +227,7 @@ final class NotificationManager { } private func sign(message: String) throws -> (hex: String, metadata: MessageMetadata) { - let hex = try Tari.shared.walletAddress.byteVector.hex + let hex = try Tari.shared.walletAddress.publicKey guard let apiKey = TariSettings.shared.pushServerApiKey else { throw PushNotificationServerError.missingApiKey } let metadata = try Tari.shared.messageSign.sign(message: "\(apiKey)\(hex)\(message)") return (hex: hex, metadata: metadata) diff --git a/MobileWallet/Common/Persistant Data/UserDefaults.swift b/MobileWallet/Common/Persistant Data/UserDefaults.swift index dc98e5d2..8ed3af39 100644 --- a/MobileWallet/Common/Persistant Data/UserDefaults.swift +++ b/MobileWallet/Common/Persistant Data/UserDefaults.swift @@ -38,11 +38,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +// MARK: - Generic User Defaults + private enum UserDefaultName: String, CaseIterable { case selectedNetworkName case networksSettings case walletSettings case userSettings + case isTrackingEnabled } enum GroupUserDefaults { @@ -50,10 +53,31 @@ enum GroupUserDefaults { @UserDefault(key: UserDefaultName.networksSettings.rawValue, suiteName: TariSettings.groupIndentifier) static var networksSettings: [NetworkSettings]? @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? +} + +// MARK: - Tor Manager User Defaults + +enum TorManagerUserDefaults { + + private enum Name: String, CaseIterable { + case isUsingCustomBridges + case torBridges + } + + @UserDefault(key: Name.isUsingCustomBridges.rawValue) static var isUsingCustomBridges: Bool? + @UserDefault(key: Name.torBridges.rawValue) static var torBridges: String? + + static func removeAll() { + Name.allCases.forEach { UserDefaults.standard.removeObject(forKey: $0.rawValue) } + } } +// MARK: - Extensions + extension UserDefaults { func removeAll() { UserDefaultName.allCases.forEach { UserDefaults.standard.removeObject(forKey: $0.rawValue) } + TorManagerUserDefaults.removeAll() } } diff --git a/MobileWallet/Common/Theme.swift b/MobileWallet/Common/Theme.swift index 9a633616..70d85308 100644 --- a/MobileWallet/Common/Theme.swift +++ b/MobileWallet/Common/Theme.swift @@ -109,7 +109,6 @@ struct Images { let settingsUserAgreementIcon = UIImage(named: "icon-user-agreement") let settingsVisitTariIcon = UIImage(named: "icon-visit-tari") let settingsWalletBackupsIcon = UIImage(named: "icon-wallet-backups") - let settingsYatIcon = UIImage(named: "icon-yat") let settingColorThemeIcon = UIImage(named: "icon-theme") // Connection Details Icons @@ -277,7 +276,6 @@ extension UIImage { static var contactBook: ContactBookImages.Type { ContactBookImages.self } static var icons: IconsImages.Type { IconsImages.self } static var tabBar: TabBarImages.Type { TabBarImages.self } - static var legacy: LegacyImages.Type { LegacyImages.self } } // MARK: - Security @@ -326,12 +324,6 @@ enum TabBarImages { static var send: UIImage? { UIImage(named: "Images/TabBar/Send") } } -// MARK: - Legacy - -enum LegacyImages { - static var unknownUser = UIImage(named: "unknownUser") -} - // MARK: - Icons enum IconsImages { @@ -343,6 +335,7 @@ enum IconsImages { 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") } diff --git a/MobileWallet/Common/Toasts/SuccessToast.swift b/MobileWallet/Common/Toasts/SuccessToast.swift index 7c8f8d00..516c3045 100644 --- a/MobileWallet/Common/Toasts/SuccessToast.swift +++ b/MobileWallet/Common/Toasts/SuccessToast.swift @@ -72,10 +72,10 @@ final class SuccessToast: UIView { addSubview(label) let constraints = [ + label.topAnchor.constraint(equalTo: topAnchor, constant: 15.0), label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 30.0), label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -30.0), - label.centerYAnchor.constraint(equalTo: centerYAnchor), - heightAnchor.constraint(equalToConstant: 40.0) + label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -15.0) ] NSLayoutConstraint.activate(constraints) diff --git a/MobileWallet/Common/Toasts/ToastPresenter.swift b/MobileWallet/Common/Toasts/ToastPresenter.swift index de5904f2..010d5fda 100644 --- a/MobileWallet/Common/Toasts/ToastPresenter.swift +++ b/MobileWallet/Common/Toasts/ToastPresenter.swift @@ -42,7 +42,7 @@ import SwiftEntryKit enum ToastPresenter { - static func show(title: String) { + static func show(title: String, duration: TimeInterval = 2.0) { guard let toastColor: UIColor = .static.purple else { return } @@ -52,9 +52,10 @@ enum ToastPresenter { var attributes = EKAttributes.topToast attributes.entryBackground = .color(color: EKColor(toastColor)) attributes.screenBackground = .clear - attributes.displayDuration = 2.0 + attributes.displayDuration = duration attributes.hapticFeedbackType = .success attributes.screenInteraction = .forward + attributes.precedence = .enqueue(priority: .normal) SwiftEntryKit.display(entry: toast, using: attributes) UIApplication.shared.hideKeyboard() diff --git a/MobileWallet/Libraries/TariLib/Core/App Setup/AppConfigurator.swift b/MobileWallet/Libraries/TariLib/Core/App Setup/AppConfigurator.swift new file mode 100644 index 00000000..1d797835 --- /dev/null +++ b/MobileWallet/Libraries/TariLib/Core/App Setup/AppConfigurator.swift @@ -0,0 +1,121 @@ +// AppConfigurator.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 11/10/2022 + Using Swift 5.0 + Running on macOS 12.6 + + 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 AppConfigurator { + + // MARK: - Properties + + static let shared = AppConfigurator() + + var isCrashLoggerEnabled: Bool { + get { crashLogger.isEnabled ?? false } + set { crashLogger.isEnabled = newValue } + } + + private let crashLogger = CrashLogger() + private var cancellables = Set() + + // MARK: - Initialisers + + private init() {} + + // MARK: - Actions + + func configure() { + configureLoggers() + configureManagers() + configureCallbacks() + } + + private func configureLoggers() { + switch TariSettings.shared.environment { + case .debug: + Logger.attach(logger: ConsoleLogger()) + case .testflight, .production: + break + } + + Logger.attach(logger: FileLogger()) + Logger.attach(logger: crashLogger) + + crashLogger.configure() + } + + private func configureManagers() { + BackupManager.shared.configure() + StatusLoggerManager.shared.configure() + DataFlowManager.shared.configure() + LocalNotificationsManager.shared.configure() + } + + private func configureCallbacks() { + + Tari.shared.$torError + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.handle(torError: $0) } + .store(in: &cancellables) + } + + // MARK: - Handlers + + private func handle(torError: TorError) { + + switch torError { + case let .connectionFailed(error): + guard let posixError = error as? PosixError else { return } + handle(posixError: posixError) + case .authenticationFailed, .missingController, .missingCookie, .unknown: + break + } + } + + private func handle(posixError: PosixError) { + + switch posixError { + case .connectionRefused: + ToastPresenter.show(title: localized("custom_bridges.toast.error.connection_refused"), duration: 5.0) + default: + break + } + } +} diff --git a/MobileWallet/TariLib/Core/Data Models/MessageMetadata.swift b/MobileWallet/Libraries/TariLib/Core/Data Models/MessageMetadata.swift similarity index 100% rename from MobileWallet/TariLib/Core/Data Models/MessageMetadata.swift rename to MobileWallet/Libraries/TariLib/Core/Data Models/MessageMetadata.swift diff --git a/MobileWallet/TariLib/Core/Data Models/WalletBalance.swift b/MobileWallet/Libraries/TariLib/Core/Data Models/WalletBalance.swift similarity index 100% rename from MobileWallet/TariLib/Core/Data Models/WalletBalance.swift rename to MobileWallet/Libraries/TariLib/Core/Data Models/WalletBalance.swift diff --git a/MobileWallet/TariLib/Core/FFI/Balance.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Balance.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Balance.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Balance.swift diff --git a/MobileWallet/TariLib/Core/FFI/BaseNodeConnectivityStatus.swift b/MobileWallet/Libraries/TariLib/Core/FFI/BaseNodeConnectivityStatus.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/BaseNodeConnectivityStatus.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/BaseNodeConnectivityStatus.swift diff --git a/MobileWallet/TariLib/Core/FFI/ByteVector.swift b/MobileWallet/Libraries/TariLib/Core/FFI/ByteVector.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/ByteVector.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/ByteVector.swift diff --git a/MobileWallet/TariLib/Core/FFI/Configs/CommsConfig.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Configs/CommsConfig.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Configs/CommsConfig.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Configs/CommsConfig.swift diff --git a/MobileWallet/TariLib/Core/FFI/Configs/TransportConfig.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Configs/TransportConfig.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Configs/TransportConfig.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Configs/TransportConfig.swift diff --git a/MobileWallet/TariLib/Core/FFI/Contacts/Contact.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Contacts/Contact.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Contacts/Contact.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Contacts/Contact.swift diff --git a/MobileWallet/TariLib/Core/FFI/Contacts/Contacts.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Contacts/Contacts.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Contacts/Contacts.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Contacts/Contacts.swift diff --git a/MobileWallet/TariLib/Core/FFI/Keys/PublicKey.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Keys/PublicKey.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift diff --git a/MobileWallet/TariLib/Core/FFI/Keys/TariAddress.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddress.swift similarity index 94% rename from MobileWallet/TariLib/Core/FFI/Keys/TariAddress.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddress.swift index 26824f3d..5038ff90 100644 --- a/MobileWallet/TariLib/Core/FFI/Keys/TariAddress.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddress.swift @@ -106,8 +106,17 @@ final class TariAddress { } extension TariAddress: Equatable { + static func == (lhs: TariAddress, rhs: TariAddress) -> Bool { guard let leftHex = try? lhs.byteVector.hex, let rightHex = try? rhs.byteVector.hex else { return false } return leftHex == rightHex } + + var publicKey: String { + get throws { try String(byteVector.hex.dropLast(2)) } + } + + var isUnknownUser: Bool { + get throws { try publicKey.filter { $0 == "0" }.count == 64 } + } } diff --git a/MobileWallet/TariLib/Core/FFI/RestoreWalletStatus.swift b/MobileWallet/Libraries/TariLib/Core/FFI/RestoreWalletStatus.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/RestoreWalletStatus.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/RestoreWalletStatus.swift diff --git a/MobileWallet/TariLib/Core/FFI/Seed Words/SeedWords.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Seed Words/SeedWords.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Seed Words/SeedWords.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Seed Words/SeedWords.swift diff --git a/MobileWallet/TariLib/Core/FFI/Seed Words/SeedWordsMnemonicWordList.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Seed Words/SeedWordsMnemonicWordList.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Seed Words/SeedWordsMnemonicWordList.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Seed Words/SeedWordsMnemonicWordList.swift diff --git a/MobileWallet/TariLib/Core/FFI/TariFeePerGramStats.swift b/MobileWallet/Libraries/TariLib/Core/FFI/TariFeePerGramStats.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/TariFeePerGramStats.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/TariFeePerGramStats.swift diff --git a/MobileWallet/TariLib/Core/FFI/TariVectorWrapper.swift b/MobileWallet/Libraries/TariLib/Core/FFI/TariVectorWrapper.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/TariVectorWrapper.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/TariVectorWrapper.swift diff --git a/MobileWallet/TariLib/Core/FFI/TransactionValidationData.swift b/MobileWallet/Libraries/TariLib/Core/FFI/TransactionValidationData.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/TransactionValidationData.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/TransactionValidationData.swift diff --git a/MobileWallet/TariLib/Core/FFI/TransactionValidationStatus.swift b/MobileWallet/Libraries/TariLib/Core/FFI/TransactionValidationStatus.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/TransactionValidationStatus.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/TransactionValidationStatus.swift diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransaction.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Completed/CompletedTransaction.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransaction.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Completed/CompletedTransaction.swift diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactionKernel.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Completed/CompletedTransactionKernel.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactionKernel.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Completed/CompletedTransactionKernel.swift diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactions.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Completed/CompletedTransactions.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactions.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Completed/CompletedTransactions.swift diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransaction.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransaction.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransaction.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransaction.swift diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransactions.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransactions.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransactions.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransactions.swift diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransaction.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransaction.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransaction.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransaction.swift diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransactions.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransactions.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransactions.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransactions.swift diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Transaction.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Transaction.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Transactions/Transaction.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Transactions/Transaction.swift diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/TransactionSendResult.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Transactions/TransactionSendResult.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Transactions/TransactionSendResult.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Transactions/TransactionSendResult.swift diff --git a/MobileWallet/TariLib/Core/FFI/Unblinded Outputs/UnblindedOutput.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Unblinded Outputs/UnblindedOutput.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Unblinded Outputs/UnblindedOutput.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Unblinded Outputs/UnblindedOutput.swift diff --git a/MobileWallet/TariLib/Core/FFI/Unblinded Outputs/UnblindedOutputs.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Unblinded Outputs/UnblindedOutputs.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Unblinded Outputs/UnblindedOutputs.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Unblinded Outputs/UnblindedOutputs.swift diff --git a/MobileWallet/TariLib/Core/FFI/Wallet.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Wallet.swift similarity index 100% rename from MobileWallet/TariLib/Core/FFI/Wallet.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/Wallet.swift diff --git a/MobileWallet/TariLib/Core/FFIWalletManager.swift b/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift similarity index 99% rename from MobileWallet/TariLib/Core/FFIWalletManager.swift rename to MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift index 0e61a128..b384e49a 100644 --- a/MobileWallet/TariLib/Core/FFIWalletManager.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift @@ -239,7 +239,7 @@ final class FFIWalletManager { return result } - func feeEstimate(amount: UInt64, feePerGram: UInt64, kernelsCount: UInt64, outputsCount: UInt64) throws -> UInt64 { + func feeEstimate(amount: UInt64, feePerGram: UInt64, kernelsCount: UInt32, outputsCount: UInt32) throws -> UInt64 { let wallet = try exisingWallet diff --git a/MobileWallet/TariLib/Core/Services/CoreTariService.swift b/MobileWallet/Libraries/TariLib/Core/Services/CoreTariService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/CoreTariService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/CoreTariService.swift diff --git a/MobileWallet/TariLib/Core/Services/TariBalanceService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariBalanceService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/TariBalanceService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariBalanceService.swift diff --git a/MobileWallet/TariLib/Core/Services/TariConnectionService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariConnectionService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/TariConnectionService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariConnectionService.swift diff --git a/MobileWallet/TariLib/Core/Services/TariContactsService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariContactsService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/TariContactsService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariContactsService.swift diff --git a/MobileWallet/TariLib/Core/Services/TariFeesService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariFeesService.swift similarity index 94% rename from MobileWallet/TariLib/Core/Services/TariFeesService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariFeesService.swift index b5eef0ae..f1a18d38 100644 --- a/MobileWallet/TariLib/Core/Services/TariFeesService.swift +++ b/MobileWallet/Libraries/TariLib/Core/Services/TariFeesService.swift @@ -42,7 +42,7 @@ final class TariFeesService: CoreTariService { // MARK: - Actions - func estimateFee(amount: UInt64, feePerGram: UInt64 = Tari.defaultFeePerGram.rawValue, kernelsCount: UInt64 = Tari.defaultKernelCount, outputsCount: UInt64 = Tari.defaultOutputCount) throws -> UInt64 { + func estimateFee(amount: UInt64, feePerGram: UInt64 = Tari.defaultFeePerGram.rawValue, kernelsCount: UInt32 = Tari.defaultKernelCount, outputsCount: UInt32 = Tari.defaultOutputCount) throws -> UInt64 { try walletManager.feeEstimate(amount: amount, feePerGram: feePerGram, kernelsCount: kernelsCount, outputsCount: outputsCount) } diff --git a/MobileWallet/TariLib/Core/Services/TariKeyValueService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariKeyValueService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/TariKeyValueService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariKeyValueService.swift diff --git a/MobileWallet/TariLib/Core/Services/TariMessageSignService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariMessageSignService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/TariMessageSignService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariMessageSignService.swift diff --git a/MobileWallet/TariLib/Core/Services/TariRecoveryService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/TariRecoveryService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift diff --git a/MobileWallet/TariLib/Core/Services/TariTransactionsService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift similarity index 98% rename from MobileWallet/TariLib/Core/Services/TariTransactionsService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift index 4260288d..a9fc66a4 100644 --- a/MobileWallet/TariLib/Core/Services/TariTransactionsService.swift +++ b/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift @@ -167,7 +167,7 @@ final class TariTransactionsService: CoreTariService { } func send(toAddress address: TariAddress, amount: UInt64, feePerGram: UInt64, message: String, isOneSidedPayment: Bool, - kernelsCount: UInt64 = Tari.defaultKernelCount, outputsCount: UInt64 = Tari.defaultOutputCount) throws -> UInt64 { + kernelsCount: UInt32 = Tari.defaultKernelCount, outputsCount: UInt32 = Tari.defaultOutputCount) throws -> UInt64 { let estimatedFee = try walletManager.feeEstimate(amount: amount, feePerGram: feePerGram, kernelsCount: kernelsCount, outputsCount: outputsCount) let total = estimatedFee + amount diff --git a/MobileWallet/TariLib/Core/Services/TariUTXOsService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariUTXOsService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/TariUTXOsService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariUTXOsService.swift diff --git a/MobileWallet/TariLib/Core/Services/TariUnspentOutputsService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariUnspentOutputsService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/TariUnspentOutputsService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariUnspentOutputsService.swift diff --git a/MobileWallet/TariLib/Core/Services/TariValidationService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariValidationService.swift similarity index 100% rename from MobileWallet/TariLib/Core/Services/TariValidationService.swift rename to MobileWallet/Libraries/TariLib/Core/Services/TariValidationService.swift diff --git a/MobileWallet/TariLib/Core/Tari.swift b/MobileWallet/Libraries/TariLib/Core/Tari.swift similarity index 91% rename from MobileWallet/TariLib/Core/Tari.swift rename to MobileWallet/Libraries/TariLib/Core/Tari.swift index 1df78692..c8b2b1c0 100644 --- a/MobileWallet/TariLib/Core/Tari.swift +++ b/MobileWallet/Libraries/TariLib/Core/Tari.swift @@ -46,8 +46,8 @@ final class Tari: MainServiceable { // MARK: - Constants static let defaultFeePerGram = MicroTari(10) - static let defaultKernelCount = UInt64(1) - static let defaultOutputCount = UInt64(2) + static let defaultKernelCount = UInt32(1) + static let defaultOutputCount = UInt32(2) private let databaseName = "tari_wallet" @@ -97,9 +97,13 @@ final class Tari: MainServiceable { } } + var isUsingCustomBridges: Bool { torManager.isUsingCustomBridges } + var torBridges: String? { torManager.bridges } + var isWalletExist: Bool { (try? connectedDatabaseDirectory.checkResourceIsReachable()) ?? false } + @Published private(set) var isWalletConnected: Bool = false - var torBridgesConfiguration: BridgesConfiguration { torManager.usedBridgesConfiguration } + @Published private(set) var torError: TorError? var canAutomaticalyReconnectWallet: Bool = false @Published var isDisconnectionDisabled: Bool = false @@ -117,8 +121,8 @@ final class Tari: MainServiceable { return passphrase } - func update(torBridgesConfiguration: BridgesConfiguration) async throws { - try await torManager.update(bridgesConfiguration: torBridgesConfiguration) + func update(torBridges: String?) { + torManager.update(bridges: torBridges) } // MARK: - Initialisers @@ -173,18 +177,22 @@ final class Tari: MainServiceable { .filter { !$0 && UIApplication.shared.applicationState == .background } .sink { [weak self] _ in self?.disconnect() } .store(in: &cancellables) + + torManager.$error + .assignPublisher(to: \.torError, on: self) + .store(in: &cancellables) } // MARK: - Actions func startWallet() async throws { await waitForTor() - try await startWallet(seedWords: nil) + try startWallet(seedWords: nil) try connection.selectCurrentNode() } - func restoreWallet(seedWords: [String]) async throws { - try await startWallet(seedWords: seedWords) + func restoreWallet(seedWords: [String]) throws { + try startWallet(seedWords: seedWords) } func deleteWallet() { @@ -203,21 +211,21 @@ final class Tari: MainServiceable { } private func connect() { + torManager.start() + guard canAutomaticalyReconnectWallet, !walletManager.isWalletConnected else { return } Task { - try? await torManager.reinitiateConnection() - guard canAutomaticalyReconnectWallet, !walletManager.isWalletConnected else { return } try? await startWallet() } } private func disconnect() { walletManager.disconnectWallet() - Task { await torManager.stop() } + torManager.stop() } - private func startWallet(seedWords: [String]?) async throws { + private func startWallet(seedWords: [String]?) throws { - let commsConfig = try await makeCommsConfig() + let commsConfig = try makeCommsConfig() let selectedNetwork = NetworkManager.shared.selectedNetwork var walletSeedWords: SeedWords? @@ -262,9 +270,9 @@ final class Tari: MainServiceable { NetworkManager.shared.removeSelectedNetworkSettings() } - private func makeCommsConfig() async throws -> CommsConfig { + private func makeCommsConfig() throws -> CommsConfig { - let torCookie = try await torManager.cookie() + let torCookie = try torManager.controlAuthCookie() let transportType = try makeTransportType(torCookie: torCookie) return try CommsConfig( @@ -320,8 +328,8 @@ final class Tari: MainServiceable { // MARK: - Data - func walletVersion() async throws -> String? { - let commsConfig = try await makeCommsConfig() + func walletVersion() throws -> String? { + let commsConfig = try makeCommsConfig() return try walletManager.walletVersion(commsConfig: commsConfig) } } diff --git a/MobileWallet/TariLib/Core/Tor/Helpers/Ipv6Tester.swift b/MobileWallet/Libraries/TariLib/Core/Tor/Helpers/Ipv6Tester.swift similarity index 100% rename from MobileWallet/TariLib/Core/Tor/Helpers/Ipv6Tester.swift rename to MobileWallet/Libraries/TariLib/Core/Tor/Helpers/Ipv6Tester.swift diff --git a/MobileWallet/TariLib/Core/Tor/Helpers/NetworkTools.h b/MobileWallet/Libraries/TariLib/Core/Tor/Helpers/NetworkTools.h similarity index 100% rename from MobileWallet/TariLib/Core/Tor/Helpers/NetworkTools.h rename to MobileWallet/Libraries/TariLib/Core/Tor/Helpers/NetworkTools.h diff --git a/MobileWallet/TariLib/Core/Tor/Helpers/NetworkTools.m b/MobileWallet/Libraries/TariLib/Core/Tor/Helpers/NetworkTools.m similarity index 100% rename from MobileWallet/TariLib/Core/Tor/Helpers/NetworkTools.m rename to MobileWallet/Libraries/TariLib/Core/Tor/Helpers/NetworkTools.m diff --git a/MobileWallet/Libraries/TariLib/Core/Tor/TorConnectionStatus.swift b/MobileWallet/Libraries/TariLib/Core/Tor/TorConnectionStatus.swift new file mode 100644 index 00000000..3863ed4d --- /dev/null +++ b/MobileWallet/Libraries/TariLib/Core/Tor/TorConnectionStatus.swift @@ -0,0 +1,47 @@ +// TorConnectionStatus.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 31/10/2023 + Using Swift 5.0 + Running on macOS 14.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. +*/ + +enum TorConnectionStatus { + case disconnected + case connecting + case portsOpen + case connected + case disconnecting +} diff --git a/MobileWallet/Libraries/TariLib/Core/Tor/TorError.swift b/MobileWallet/Libraries/TariLib/Core/Tor/TorError.swift new file mode 100644 index 00000000..5c536674 --- /dev/null +++ b/MobileWallet/Libraries/TariLib/Core/Tor/TorError.swift @@ -0,0 +1,47 @@ +// TorError.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 31/10/2023 + Using Swift 5.0 + Running on macOS 14.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. +*/ + +enum TorError: Error { + case connectionFailed(error: Error) + case authenticationFailed + case missingController + case missingCookie(error: Error) + case unknown(error: Error) +} diff --git a/MobileWallet/Libraries/TariLib/Core/TorManager.swift b/MobileWallet/Libraries/TariLib/Core/TorManager.swift new file mode 100644 index 00000000..ebe7639d --- /dev/null +++ b/MobileWallet/Libraries/TariLib/Core/TorManager.swift @@ -0,0 +1,461 @@ +// TorManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 07/01/2022 + Using Swift 5.0 + Running on macOS 12.1 + + 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 Tor +import Combine +import IPtProxy + +final class TorManager { + + private enum Action { + case connect + case disconnect + } + + // MARK: - Constants + + private(set) lazy var controlServerAddress = "/ip4/\(socketHost)/tcp/\(port)" + + private let socketHost = "127.0.0.1" + private let port: UInt16 = 39069 + private let workingDirectory = TariSettings.storageDirectory.appendingPathComponent("tor", isDirectory: true) + private lazy var authDirectory = workingDirectory.appendingPathComponent("auth", isDirectory: true) + private lazy var controlAuthCookieURL = workingDirectory.appendingPathComponent("control_auth_cookie") + + // MARK: - Properties + + @Published private(set) var connectionStatus: TorConnectionStatus = .disconnected + @Published private(set) var bootstrapProgress: Int = 0 + @Published private(set) var error: TorError? + + var isUsingCustomBridges: Bool { TorManagerUserDefaults.isUsingCustomBridges ?? false } + var bridges: String? { TorManagerUserDefaults.torBridges } + + private var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid + private var controller: TorController? + private var queuedAction: Action? + private var retryAction: DispatchWorkItem? + private var observers: [Any?] = [] + private var isConnecting = false + private var cancellables = Set() + + private var isThreadRunning: Bool { TorThread.active != nil } + + private var existingController: TorController { + get throws { + guard let controller else { throw TorError.missingController } + return controller + } + } + + private var bridgesChunks: [String] { + (bridges ?? "") + .components(separatedBy: "\n") + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty && !$0.hasPrefix("//") && !$0.hasPrefix("#") } + } + + // MARK: - Init + + init() { + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + + $bootstrapProgress + .sink { [weak self] in + switch $0 { + case 0: + break + case 100: + self?.isConnecting = false + self?.cancelRetry() + self?.runQueuedAction() + default: + self?.isConnecting = true + } + } + .store(in: &cancellables) + + $error + .compactMap { $0 } + .sink { [weak self] in self?.handle(torError: $0) } + .store(in: &cancellables) + } + + // MARK: - Files + + private func createDirectories() throws { + try FileManager.default.createDirectory(at: authDirectory, withIntermediateDirectories: true) + } + + // MARK: - Actions + + func start() { + + Logger.log(message: "Start", domain: .tor, level: .info) + + guard !isConnecting else { + queuedAction = .connect + return + } + + Task { + do { + try await disconnect() + try await connect() + } catch { + handle(error: error) + } + } + } + + func stop() { + + Logger.log(message: "Stop", domain: .tor, level: .info) + + guard !isConnecting else { + queuedAction = .disconnect + return + } + + Task { + do { + startBackgroundTask() + try await disconnect() + endBackgroundTask() + } catch { + handle(error: error) + } + } + } + + func disconnect() async throws { + Logger.log(message: "Disconnect: Start", domain: .tor, level: .info) + stopController() + connectionStatus = .disconnecting + bootstrapProgress = 0 + try await waitingForThread() + connectionStatus = .disconnected + Logger.log(message: "Disconnect: Done", domain: .tor, level: .info) + } + + func update(bridges: String?) { + Logger.log(message: "Update bridges", domain: .tor, level: .info) + TorManagerUserDefaults.isUsingCustomBridges = bridges != nil + TorManagerUserDefaults.torBridges = bridges + start() + } + + private func connect() async throws { + Logger.log(message: "Connect: Start", domain: .tor, level: .info) + connectionStatus = .connecting + try createDirectories() + try createThread() + startIObfs4Proxy() + createController() + try await startController() + guard try await auth() else { throw TorError.authenticationFailed } + connectionStatus = .portsOpen + try observeConnection() + setupRetry() + Logger.log(message: "Connect: Done", domain: .tor, level: .info) + } + + private func runQueuedAction() { + + guard let queuedAction else { return } + self.queuedAction = nil + + switch queuedAction { + case .connect: + start() + case .disconnect: + stop() + } + } + + // MARK: - Thread + + private func createThread() throws { + let configuration = makeConfiguration() + let thread = TorThread(configuration: configuration) + thread.start() + } + + private func waitingForThread() async throws { + guard isThreadRunning else { return } + Logger.log(message: "Waiting for thread", domain: .tor, level: .info) + try await Task.sleep(seconds: 0.5) + try await waitingForThread() + } + + // MARK: - Proxy + + private func startIObfs4Proxy() { + IPtProxyStartObfs4Proxy(nil, false, false, nil) + } + + // MARK: - Controller + + private func createController() { + controller = TorController(socketHost: socketHost, port: port) + Logger.log(message: "Controller Created", domain: .tor, level: .info) + } + + private func startController(retryCount: Int = 0) async throws { + + let maxRetryCount = 5 + + do { + try existingController.connect() + isConnecting = true + Logger.log(message: "Controller Connected", domain: .tor, level: .info) + } catch { + Logger.log(message: "Waiting for connection: \(retryCount)", domain: .tor, level: .info) + let retryCount = retryCount + 1 + guard retryCount < maxRetryCount else { + guard let posixError = error.posixError else { throw TorError.connectionFailed(error: error) } + isConnecting = false + throw TorError.connectionFailed(error: posixError) + } + + try await Task.sleep(seconds: 0.5) + try await startController(retryCount: retryCount) + } + } + + private func stopController() { + controller?.disconnect() + Logger.log(message: "Controller Disconnected", domain: .tor, level: .info) + } + + private func auth() async throws -> Bool { + let controlAuthCookie = try controlAuthCookie() + let result = try await existingController.authenticate(with: controlAuthCookie) + Logger.log(message: "Authenticated", domain: .tor, level: .info) + return result + } + + private func observeConnection() throws { + + let statsObserver = try existingController.addObserver { [weak self] _, _, action, arguments in + guard action == "BOOTSTRAP", let rawProgress = arguments?["PROGRESS"], let progress = Int(rawProgress) else { return false } + self?.bootstrapProgress = progress + return false + } + + let circlesObserver = try existingController.addObserver { [weak self] isEstablished in + guard isEstablished else { return } + self?.connectionStatus = .connected + self?.removeObservers() + } + + observers += [statsObserver, circlesObserver] + } + + private func removeObservers() { + observers.forEach { self.controller?.removeObserver($0) } + observers.removeAll() + } + + // MARK: - Retry + + private func setupRetry() { + + Logger.log(message: "Retry set", domain: .tor, level: .info) + + let retryAction = DispatchWorkItem { [weak self] in + self?.resetConnection() + } + + DispatchQueue.main.asyncAfter(wallDeadline: .now() + 30.0, execute: retryAction) + self.retryAction = retryAction + } + + private func cancelRetry() { + + Logger.log(message: "Retry cancelled", domain: .tor, level: .info) + + retryAction?.cancel() + retryAction = nil + } + + private func resetConnection() { + + Logger.log(message: "Retry triggered", domain: .tor, level: .info) + + controller?.setConfForKey("DisableNetwork", withValue: "1") + controller?.setConfForKey("DisableNetwork", withValue: "0") + } + + // MARK: - Handlers + + private func handle(error: Error) { + switch error { + case let error as TorError: + self.error = error + default: + self.error = .unknown(error: error) + } + } + + private func handle(torError: TorError) { + + Logger.log(message: "\(torError)", domain: .tor, level: .error) + + switch torError { + case let .connectionFailed(error): + handle(connectionError: error) + case .authenticationFailed, .missingController, .missingCookie, .unknown: + break + } + } + + private func handle(connectionError: Error) { + + guard let posixError = connectionError as? PosixError else { return } + + Logger.log(message: "POSIX Error: \(posixError.code)", domain: .tor, level: .error) + + switch posixError { + case .connectionRefused: + Logger.log(message: "Connection Refused. Custom Tor bridged disabled.", domain: .tor, level: .warning) + TorManagerUserDefaults.isUsingCustomBridges = false + start() + default: + break + } + } + + // MARK: - Cookies + + func controlAuthCookie() throws -> Data { + do { + return try Data(contentsOf: controlAuthCookieURL) + } catch { + throw TorError.missingCookie(error: error) + } + } + + // MARK: - Background Tasks + + private func startBackgroundTask() { + Logger.log(message: "Start Background Task", domain: .tor, level: .info) + backgroundTaskID = UIApplication.shared.beginBackgroundTask { [weak self] in + self?.endBackgroundTask() + } + } + + private func endBackgroundTask() { + guard backgroundTaskID != .invalid else { return } + Logger.log(message: "End Background Task", domain: .tor, level: .info) + UIApplication.shared.endBackgroundTask(backgroundTaskID) + backgroundTaskID = .invalid + } + + // MARK: - Helpers + + private func makeConfiguration() -> TorConfiguration { + + var arguments: [String] = makeBaseArguments() + arguments += makeBridgeArguments() + arguments += makeIpArguments() + + let configuration = TorConfiguration() + configuration.cookieAuthentication = true + configuration.dataDirectory = workingDirectory + configuration.arguments = arguments + + return configuration + } + + 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", + "--ClientOnionAuthDir", authDirectory.path + ] + } + + private func makeBridgeArguments() -> [String] { + guard isUsingCustomBridges else { return [] } + var arguments = bridgesChunks.flatMap { ["--Bridge", $0] } + arguments += ["--UseBridges", "1"] + return arguments + } + + private func makeIpArguments() -> [String] { + + var arguments: [String] = [] + + switch Ipv6Tester.ipv6_status() { + case .torIpv6ConnOnly: + arguments += ["--ClientPreferIPv6ORPort", "1"] + let ipv4argument = isUsingCustomBridges ? "1" : "0" + arguments += ["--ClientUseIPv4", ipv4argument] + case .torIpv6ConnDual, .torIpv6ConnFalse, .torIpv6ConnUnknown: + arguments += [ + "--ClientPreferIPv6ORPort", "auto", + "--ClientUseIPv4", "1" + ] + } + + return arguments + } +} diff --git a/MobileWallet/TariLib/Core/WalletCallbacksManager.swift b/MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift similarity index 100% rename from MobileWallet/TariLib/Core/WalletCallbacksManager.swift rename to MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/AppInfo.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/AppInfo.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/AppInfo.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/AppInfo.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift similarity index 96% rename from MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift index ea17a052..662ca15e 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift @@ -46,7 +46,7 @@ final class ConnectionMonitor { // MARK: - Properties @Published private(set) var networkConnection: NetworkMonitor.Status = .disconnected - @Published private(set) var torConnection: TorManager.ConnectionStatus = .disconnected + @Published private(set) var torConnection: TorConnectionStatus = .disconnected @Published private(set) var torBootstrapProgress: Int = 0 @Published private(set) var isTorBootstrapCompleted: Bool = false @Published private(set) var baseNodeConnection: BaseNodeConnectivityStatus = .offline @@ -57,7 +57,7 @@ final class ConnectionMonitor { // MARK: - Setups - func setupPublishers(torConnectionStatus: AnyPublisher, torBootstrapProgress: AnyPublisher, + func setupPublishers(torConnectionStatus: AnyPublisher, torBootstrapProgress: AnyPublisher, baseNodeConnectionStatus: AnyPublisher, baseNodeSyncStatus: AnyPublisher) { networkMonitor.$status @@ -108,7 +108,7 @@ private extension NetworkMonitor.Status { } } -private extension TorManager.ConnectionStatus { +private extension TorConnectionStatus { var statusName: String { switch self { diff --git a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/NetworkMonitor.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Connection Monitor/NetworkMonitor.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/NetworkMonitor.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Connection Monitor/NetworkMonitor.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/ConsoleLogger.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/ConsoleLogger.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/Loggers/ConsoleLogger.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/ConsoleLogger.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift similarity index 76% rename from MobileWallet/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift index 1eaa2f64..c1790144 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift @@ -42,12 +42,42 @@ import Sentry final class CrashLogger { - init() { - guard let sentryPublicDSN = TariSettings.shared.sentryPublicDSN else { return } + var isEnabled: Bool? = GroupUserDefaults.isTrackingEnabled { + didSet { updateState() } + } + + // MARK: - Actions + + func configure() { + isEnabled = GroupUserDefaults.isTrackingEnabled + } + + private func start() { + guard let sentryPublicDSN = TariSettings.shared.sentryPublicDSN, !SentrySDK.isEnabled else { return } let options = Options() options.dsn = sentryPublicDSN options.environment = TariSettings.shared.environment.name SentrySDK.start(options: options) + Logger.log(message: "Data Collection Enabled", domain: .general, level: .info) + } + + private func stop() { + guard SentrySDK.isEnabled else { return } + SentrySDK.close() + Logger.log(message: "Data Collection Disabled", domain: .general, level: .info) + } + + // MARK: - Handlers + + private func updateState() { + + GroupUserDefaults.isTrackingEnabled = isEnabled + + if isEnabled == true { + start() + } else { + stop() + } } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/FileLogger.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/FileLogger.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/Loggers/FileLogger.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/FileLogger.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/LogFormatter.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/LogFormatter.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/Loggers/LogFormatter.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/LogFormatter.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/Logger.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/Logger.swift similarity index 98% rename from MobileWallet/TariLib/Wrappers/Utils/Loggers/Logger.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/Logger.swift index 002ec0e3..7d488c68 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Loggers/Logger.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/Logger.swift @@ -64,6 +64,7 @@ final class Logger { case bleCentral case blePeripherial case localNotification + case tor case debug } @@ -108,6 +109,8 @@ extension Logger.Domain { return "Debug" case .localNotification: return "L. Notification" + case .tor: + return "Tor" } } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/StatusLoggerManager.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/StatusLoggerManager.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/Loggers/StatusLoggerManager.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/StatusLoggerManager.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/MicroTari.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/MicroTari.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/MicroTari.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/MicroTari.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/Network/BaseNode.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/BaseNode.swift similarity index 87% rename from MobileWallet/TariLib/Wrappers/Utils/Network/BaseNode.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/BaseNode.swift index 66c9125e..813aea2c 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Network/BaseNode.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/BaseNode.swift @@ -76,9 +76,10 @@ struct BaseNode: Equatable { // MARK: - Actions private func validateData() throws { - let regex = try NSRegularExpression(pattern: "[a-z0-9]{64}::\\/onion3\\/[a-z0-9]{56}:[0-9]{2,6}") + 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 regex.matches(in: peer, options: [], range: range).count == 1 else { throw InternalError.invalidPeerString } + guard onionRegex.matches(in: peer, options: [], range: range).count == 1 || ip4Regex.matches(in: peer, range: range).count == 1 else { throw InternalError.invalidPeerString } } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Network/NetworkManager.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkManager.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/Network/NetworkManager.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkManager.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/Network/NetworkSettings.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkSettings.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/Network/NetworkSettings.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/NetworkSettings.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/Network/TariNetwork.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift similarity index 63% rename from MobileWallet/TariLib/Wrappers/Utils/Network/TariNetwork.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift index a7f713fb..c39b140c 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Network/TariNetwork.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift @@ -97,16 +97,20 @@ extension TariNetwork { presentedName: "StageNet (\(localized("common.recommended")))", isMainNet: false, rawBaseNodes: [ - "StageNet 01": "a062ae2345b0db0df9fb1504b99511e23d98f8513f9b5503efcc6dad8eca7e47::/onion3/rhoqxfbzz3uidp23erxu4mkwwexc2gg4q45rcxfpbhb35ycdv4ex2fid:18141", - "StageNet 02": "b2c5db3a2858451d241d4e88677536f9e82a760111962785fb6a3cddc41f766e::/onion3/q32yxdg7l7os2zzx64e3f5u4mzib3lxlkdyguybkhtkd4pwfkpunjcyd:18141", - "StageNet 03": "1cdf34d27bee5e1edbc343a17f7d79a8a1974fe3f790e899d8987c1f11697e41::/onion3/fwvmhhcifr7yh7neqsweyjvu4bnlmljg6a6fsjdwify3b4aals2oc3yd:18141", - "StageNet 04": "a42eea2088e0ef663b8d29a9d039b0e5d51c1ddc1cf5ae28feb05ed52ead5a69::/onion3/k5khqg7fwkq7ujxievps22r42i5ykuieai64ze3kj5snsjkew3v7piid:18141", - "StageNet 05": "d49df057e1f1ae399ffabdeb59e7ad542439ab2b0bbd9ed23042175a93e4d03c::/onion3/hjeczose7rjo6o6qhsszkuzhrm6qfs7s4yeqzeuz2m67rkpijrtrwsad:18141", - "StageNet 06": "e65f18cb4a362b33667e0d39b3c93f06c0e822af09906914bdc65907b7cdc130::/onion3/uuv6j3vwq4dac6z3dblc2mjcoecrnxfka3wg43bcass5avbzix4nmzyd:18141", - "StageNet 07": "0a755298f4bee8e6db64e345fe8f937e3882693a48c71b942b88744761b02067::/onion3/ksdogedobmqoud6ampvrjrhoozftgfkolhxqvnt7mo7ajqczku5tyyqd:18141", - "StageNet 08": "0c22d3dc3983c74131d7dfb0c4c8ae9fb90c434826cf8ccc71e793bb72d36213::/onion3/so2be7uyg4kf5l7ys3fbk4eqovsfi63yuaqj7pahkkw2crujq43jdnyd:18141", - "StageNet 09": "2ade610a2e95f1c686873944096f5a1f2c7ffcf47d67112b472c9208cc6e9532::/onion3/rmiknlrf7ngfvgpayf5qzuer2c547rzmyqbkbw45w5uessi3jodasdqd:18141", - "StageNet 10": "369ae9a89c3fc2804d6ec07e20bf10e5d0e72f565a71821fc7c611ae5bee0116::/onion3/crvsrmoyrk5uatvnafsmoykiqgywdqowupn3auq25iz7zxyf7xusjxid:18141" + "StageNet 01": "1a294e0312ba507899a3f3eadc390d492ab620ce29cad94ba496b4d4fd78aa16::/ip4/34.252.174.111/tcp/18189", + "StageNet 02": "1a294e0312ba507899a3f3eadc390d492ab620ce29cad94ba496b4d4fd78aa16::/onion3/mfqsxcw4fsq7djzbbgcgfvx3tl6zcciqxzdrl7w5axozbs654zojijyd:18141", + "StageNet 03": "34cfce7c91290a7eb82127a76d7608d18df3992ec66fbcf9d6f97ca85fe10c17::/onion3/2tmbjqsvnaelbxv66tm3wze3oudwzdlfifwqptofgcmuvgvhdw563oid:18141", + "StageNet 04": "38feed1438270fc8c9c7211d3a5faf0aeab1b65d5bce2083476be6483b35b217::/onion3/2cufeex4t2rr3466cw5ijhexsapyg2hwkoc6mlwfiusiyg5dr7wkxdad:18141", + "StageNet 05": "3cfd696d646c0b2b2ab19de2f76727553d34e4bf1c5be41a1b9079430f0ea331::/ip4/54.77.66.39/tcp/18189", + "StageNet 06": "3cfd696d646c0b2b2ab19de2f76727553d34e4bf1c5be41a1b9079430f0ea331::/onion3/b6gtdaogqgs26jy32nb2u3elpalfzcnaa5krkpczeltpod5i54anh2id:18141", + "StageNet 07": "502f6306c80293e0455b1dd9b05aefaa32002630e7d1f628f09a652479f26727::/onion3/t76s5ngz3j6vxt2isjlp5utwrwjw2wl56ffknar62q24y33ucuzswhid:18141", + "StageNet 08": "6222a5627a0d53cc2fcc8d829ea79b7e07af6e305133bb71e8e72475f34cea29::/onion3/fc5apmwrtt3nmuw7srp4khcnpedzhfwynxmf4nwceqrn5xwutvtkg6ad:18141", + "StageNet 09": "70a5a84c75a7950cc805c17c6cf99160e8df6786df82cb93a044df1456ff566d::/onion3/whwgiuqkc23jtx266nbbjilzz6lrc7enljtef6tjlgvxijunkwmwh6yd:18141", + "StageNet 10": "b040d2e4cabad05fa0bb671bcfc822bd26fb526a3b116bfe74d0d516c4589b41::/ip4/63.35.51.217/tcp/18189", + "StageNet 11": "b040d2e4cabad05fa0bb671bcfc822bd26fb526a3b116bfe74d0d516c4589b41::/onion3/6idckzqo5hejteuitzbzthstf22ux5schi5f5wjia5w3pvbtt7hrb3ad:18141", + "StageNet 12": "c81a8e314b7c06fd136d7e836b26fe821de36ebb42a3c47f671190eb9fc5695d::/ip4/54.73.25.246/tcp/18189", + "StageNet 13": "c81a8e314b7c06fd136d7e836b26fe821de36ebb42a3c47f671190eb9fc5695d::/onion3/yhvolqnqbznwc2fet7laxmusqcoeaie3bfc3vnmq26udz67ayscqgyqd:18141", + "StageNet 14": "d80b5ccaea9d85f868ba55c243f8fc5c7c8a31ead0c28f072e42ac8ae862dd00::/onion3/dskj4ecpegae2ypzq7icpmhcu7ylbbo7siqqdul6537zi3ykx3hzqsqd:18141" ] ) } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Settings/TariSettings.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/TariSettings.swift similarity index 99% rename from MobileWallet/TariLib/Wrappers/Utils/Settings/TariSettings.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/TariSettings.swift index 7c22bf8c..4b213879 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Settings/TariSettings.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/TariSettings.swift @@ -78,6 +78,7 @@ struct TariSettings { 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 diff --git a/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettings.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettings.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettings.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettings.swift diff --git a/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift similarity index 100% rename from MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift rename to MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift diff --git a/MobileWallet/MobileWallet-bridging-header.h b/MobileWallet/MobileWallet-bridging-header.h index a531d61a..1601b257 100644 --- a/MobileWallet/MobileWallet-bridging-header.h +++ b/MobileWallet/MobileWallet-bridging-header.h @@ -3,5 +3,5 @@ // MobileWallet // -#import "./TariLib/libtari_wallet_ffi_ios.xcframework/ios-arm64/HEADERS" -#import "./TariLib/Core/Tor/Helpers/NetworkTools.h" +#import "./Libraries/TariLib/libminotari_wallet_ffi_ios.xcframework/ios-arm64/HEADERS" +#import "./Libraries/TariLib/Core/Tor/Helpers/NetworkTools.h" diff --git a/MobileWallet/MobileWallet.xcdatamodeld/.xccurrentversion b/MobileWallet/MobileWallet.xcdatamodeld/.xccurrentversion deleted file mode 100644 index d912bc21..00000000 --- a/MobileWallet/MobileWallet.xcdatamodeld/.xccurrentversion +++ /dev/null @@ -1,8 +0,0 @@ - - - - - _XCCurrentVersionName - MobileWallet.xcdatamodel - - diff --git a/MobileWallet/MobileWallet.xcdatamodeld/MobileWallet.xcdatamodel/contents b/MobileWallet/MobileWallet.xcdatamodeld/MobileWallet.xcdatamodel/contents deleted file mode 100644 index 23c0b105..00000000 --- a/MobileWallet/MobileWallet.xcdatamodeld/MobileWallet.xcdatamodel/contents +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/MobileWallet/SceneDelegate.swift b/MobileWallet/SceneDelegate.swift index 91017052..ee9755c4 100644 --- a/MobileWallet/SceneDelegate.swift +++ b/MobileWallet/SceneDelegate.swift @@ -101,10 +101,6 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { LogFilesManager.cleanupLogs() } - func sceneDidEnterBackground(_ scene: UIScene) { - (UIApplication.shared.delegate as? AppDelegate)?.saveContext() - } - func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { ShortcutsManager.handle(shortcut: shortcutItem) } diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/BridgesConfigurationViewController.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/BridgesConfigurationViewController.swift deleted file mode 100644 index 77786fef..00000000 --- a/MobileWallet/Screens/AdvancedSettings/Bridges/BridgesConfigurationViewController.swift +++ /dev/null @@ -1,203 +0,0 @@ -// BridgesConfigurationViewController.swift - -/* - Package MobileWallet - Created by S.Shovkoplyas on 01.09.2020 - Using Swift 5.0 - Running on macOS 10.15 - - 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 BridgesConfigurationViewController: SettingsParentTableViewController, CustomBridgesHandable { - - typealias BridgesType = OnionSettings.BridgesType - - private lazy var bridgesConfiguration: BridgesConfiguration = { - OnionSettings.currentlyUsedBridgesConfiguration - }() - - private enum Section: Int { - case chooseBridge = 1 - } - - private var cancellables = Set() - - private enum BridgesConfigurationItemTitle: CaseIterable { - case requestBridgesFromTorproject - case noBridges - case custom - - var rawValue: String { - switch self { - case .requestBridgesFromTorproject: return localized("bridges_configuration.item.request_bridges_from_torproject") - - case .noBridges: return localized("bridges_configuration.item.noBridges") - case .custom: return localized("bridges_configuration.item.custom") - } - } - } - - private lazy var chooseBridgeSectionItems: [SystemMenuTableViewCellItem] = { - getBridgeSectionItems() - }() - - private func getBridgeSectionItems() -> [SystemMenuTableViewCellItem] { - [ - SystemMenuTableViewCellItem(title: BridgesConfigurationItemTitle.noBridges.rawValue, mark: Tari.shared.torBridgesConfiguration.bridgesType == BridgesType.none ? .scheduled : .none, hasArrow: false), - SystemMenuTableViewCellItem(title: BridgesConfigurationItemTitle.custom.rawValue, mark: Tari.shared.torBridgesConfiguration.bridgesType == BridgesType.custom ? .scheduled : .none) - ] - } - - override func viewDidLoad() { - super.viewDidLoad() - tableView.delegate = self - tableView.dataSource = self - - setupCustomBridgeProgressHandler() - .store(in: &cancellables) - } - - override func viewWillAppear(_ animated: Bool) { - chooseBridgeSectionItems = getBridgeSectionItems() - tableView.reloadData() - } -} - -// MARK: Setup subviews -extension BridgesConfigurationViewController { - - override func setupNavigationBar() { - super.setupNavigationBar() - - navigationBar.title = localized("bridges_configuration.title") - - navigationBar.update(rightButton: NavigationBar.ButtonModel(title: localized("bridges_configuration.connect"), callback: { [weak self] in - self?.handleConnectAction() - })) - - navigationBar.rightButton(index: 0)?.isEnabled = false - } - - private func handleConnectAction() { - navigationBar.progress = 0.0 - view.isUserInteractionEnabled = false - - Task { [weak self] in - do { - guard let self = self else { return } - try await Tari.shared.update(torBridgesConfiguration: self.bridgesConfiguration) - self.onCustomBridgeSuccessAction() - } catch { - self?.onCustomBridgeFailureAction(error: error) - } - } - } -} - -extension BridgesConfigurationViewController: UITableViewDelegate, UITableViewDataSource { - func numberOfSections(in tableView: UITableView) -> Int { - return 2 - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard let section = Section(rawValue: section) else { return 0 } - switch section { - case .chooseBridge: - return chooseBridgeSectionItems.count - } - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(type: SystemMenuTableViewCell.self, indexPath: indexPath) - guard let section = Section(rawValue: indexPath.section) else { return cell } - - switch section { - case .chooseBridge: cell.configure(chooseBridgeSectionItems[indexPath.row]) - } - - cell.preservesSuperviewLayoutMargins = false - cell.separatorInset = .zero - cell.layoutMargins = .zero - return cell - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - 65 - } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - return nil - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - 0.0 - } - - func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { - guard let section = Section(rawValue: section), section == .chooseBridge else { return nil } - return BridgesConfigurationFooterView() - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: false) - guard let section = Section(rawValue: indexPath.section) else { return } - - switch section { - case .chooseBridge: - let raw = BridgesConfigurationItemTitle.allCases[indexPath.section + indexPath.row] - - switch raw { - case .noBridges: - bridgesConfiguration.bridgesType = BridgesType.none - bridgesConfiguration.customBridges = nil - - case .custom: - bridgesConfiguration.bridgesType = OnionSettings.currentlyUsedBridgesConfiguration.bridgesType - bridgesConfiguration.customBridges = OnionSettings.currentlyUsedBridgesConfiguration.customBridges - navigationController?.pushViewController(CustomBridgesViewController(bridgesConfiguration: bridgesConfiguration), animated: true) - default: - return - } - - navigationBar.rightButton(index: 0)?.isEnabled = OnionSettings.currentlyUsedBridgesConfiguration.bridgesType != bridgesConfiguration.bridgesType && bridgesConfiguration.bridgesType != .custom - - chooseBridgeSectionItems.forEach { (item) in - item.mark = .none - } - chooseBridgeSectionItems[indexPath.row].mark = .scheduled - } - } -} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesConstructor.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesConstructor.swift new file mode 100644 index 00000000..391da06a --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesConstructor.swift @@ -0,0 +1,47 @@ +// CustomTorBridgesConstructor.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 06/09/2023 + Using Swift 5.0 + Running on macOS 13.5 + + 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 CustomTorBridgesConstructor { + + static func buildScene(bridges: String?) -> CustomTorBridgesViewController { + let model = CustomTorBridgesModel(torBridges: bridges) + return CustomTorBridgesViewController(model: model) + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesModel.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesModel.swift new file mode 100644 index 00000000..8b185bc4 --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesModel.swift @@ -0,0 +1,78 @@ +// CustomTorBridgesModel.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 04/09/2023 + Using Swift 5.0 + Running on macOS 13.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. +*/ + +final class CustomTorBridgesModel { + + // MARK: - View Model + + @Published private(set) var isConnectionPossible = false + @Published private(set) var torBridges: String? + @Published private(set) var endFlow: Bool = false + + // MARK: - Properties + + private var isUsingCustomBridges: Bool { Tari.shared.isUsingCustomBridges } + private var usedTorBridges: String? { Tari.shared.torBridges } + + // MARK: - Initialisers + + init(torBridges: String?) { + self.torBridges = torBridges + updateValues() + } + + // MARK: - Actions + + func update(torBridges: String) { + self.torBridges = torBridges + updateValues() + } + + func connect() { + Tari.shared.update(torBridges: torBridges) + updateValues() + endFlow = true + } + + private func updateValues() { + let isTorBridgesEmpty = torBridges?.isEmpty ?? true + isConnectionPossible = !isUsingCustomBridges || (!isTorBridgesEmpty && self.usedTorBridges != torBridges) + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesView.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesView.swift new file mode 100644 index 00000000..a4ac62d6 --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesView.swift @@ -0,0 +1,186 @@ +// CustomTorBridgesView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 04/09/2023 + Using Swift 5.0 + Running on macOS 13.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. +*/ + +import TariCommon + +final class CustomTorBridgesView: BaseNavigationContentView { + + enum Row: UInt { + case input + case requestBridges + case scanQRCode + case uploadQRCode + } + + // MARK: - Subviews + + @View private var tableView: BaseMenuTableView = { + let view = BaseMenuTableView() + view.register(type: CustomTorBridgesInputCell.self) + view.register(type: MenuCell.self) + view.register(headerFooterType: CustomTorBridgesHeaderView.self) + return view + }() + + // MARK: - Properties + + private var text: String? + + var isConnectButtonEnabled: Bool { + get { navigationBar.rightButton(index: 0)?.isEnabled ?? false } + set { navigationBar.rightButton(index: 0)?.isEnabled = newValue } + } + + var onSelectRow: ((Row) -> Void)? + var onConnectButtonTap: (() -> Void)? + var onTextUpdate: ((String) -> Void)? + + private var dataSource: UITableViewDiffableDataSource? + + // MARK: - Initialisers + + override init() { + super.init() + setupViews() + setupConstraints() + setupCallbacks() + setupRows() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupViews() { + navigationBar.title = localized("custom_bridges.title") + navigationBar.update(rightButton: NavigationBar.ButtonModel(title: localized("custom_bridges.connect"), callback: { [weak self] in + self?.onConnectButtonTap?() + })) + } + + private func setupConstraints() { + + addSubview(tableView) + + let constraints = [ + tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + 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, model in + guard let self else { return UITableViewCell() } + return self.cell(tableView: tableView, indexPath: indexPath, row: model) + } + + tableView.delegate = self + } + + private func setupRows() { + + var snapshot = NSDiffableDataSourceSnapshot() + + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([.input], toSection: 0) + snapshot.appendItems([.requestBridges], toSection: 1) + snapshot.appendItems([.scanQRCode, .uploadQRCode], toSection: 2) + + dataSource?.apply(snapshot: snapshot) + } + + // MARK: - Updates + + func update(torBridgesText: String?) { + guard text != torBridgesText else { return } + text = torBridgesText + setupRows() + } + + // MARK: - Constructors + + private func cell(tableView: UITableView, indexPath: IndexPath, row: Row) -> UITableViewCell { + + switch row { + case .input: + + let cell = tableView.dequeueReusableCell(type: CustomTorBridgesInputCell.self, indexPath: indexPath) + cell.text = text + cell.onTextUpdate = { [weak self] in + self?.text = $0 + self?.onTextUpdate?($0) + } + + return cell + case .requestBridges: + let cell = tableView.dequeueReusableCell(type: MenuCell.self, indexPath: indexPath) + cell.viewModel = MenuCell.ViewModel(id: row.rawValue, title: localized("custom_bridges.item.request_bridges_from_torproject"), isArrowVisible: true, isDestructive: false) + return cell + case .scanQRCode: + let cell = tableView.dequeueReusableCell(type: MenuCell.self, indexPath: indexPath) + cell.viewModel = MenuCell.ViewModel(id: row.rawValue, title: localized("custom_bridges.item.scan_QR_code"), isArrowVisible: true, isDestructive: false) + return cell + case .uploadQRCode: + let cell = tableView.dequeueReusableCell(type: MenuCell.self, indexPath: indexPath) + cell.viewModel = MenuCell.ViewModel(id: row.rawValue, title: localized("custom_bridges.item.upload_QR_code"), isArrowVisible: true, isDestructive: false) + return cell + } + } +} + +extension CustomTorBridgesView: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let cell = tableView.cellForRow(at: indexPath) as? MenuCell, let identifier = cell.viewModel?.id, let row = Row(rawValue: identifier) else { return } + onSelectRow?(row) + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard section == 0 else { return nil } + return tableView.dequeueReusableHeaderFooterView(type: CustomTorBridgesHeaderView.self) + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesViewController.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesViewController.swift new file mode 100644 index 00000000..1c67da2c --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/CustomTorBridgesViewController.swift @@ -0,0 +1,171 @@ +// CustomTorBridgesViewController.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 04/09/2023 + Using Swift 5.0 + Running on macOS 13.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. +*/ + +import UIKit +import Combine + +final class CustomTorBridgesViewController: UIViewController { + + // 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() + + // MARK: - Initialisers + + init(model: CustomTorBridgesModel) { + 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 loadView() { + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupCallbacks() + hideKeyboardWhenTappedAroundOrSwipedDown() + } + + // MARK: - Setups + + private func setupCallbacks() { + + model.$torBridges + .sink { [weak self] in self?.mainView.update(torBridgesText: $0) } + .store(in: &cancellables) + + model.$isConnectionPossible + .sink { [weak self] in self?.mainView.isConnectButtonEnabled = $0 } + .store(in: &cancellables) + + model.$endFlow + .filter { $0 } + .sink { [weak self] _ in self?.navigationController?.popViewController(animated: true) } + .store(in: &cancellables) + + mainView.onConnectButtonTap = { [weak self] in + self?.model.connect() + } + + mainView.onSelectRow = { [weak self] in + self?.handle(selectedRow: $0) + } + + mainView.onTextUpdate = { [weak self] in + self?.model.update(torBridges: $0) + } + } + + // MARK: - Actions + + private func update(torBridges: String) { + model.update(torBridges: torBridges) + } + + private func showQRCodeScanner() { + AppRouter.presentQrCodeScanner(expectedDataTypes: [.torBridges]) { [weak self] data in + guard case let .bridges(bridges) = data else { return } + self?.update(torBridges: bridges) + } + } + + private func showImagePicker() { + let controller = UIImagePickerController() + controller.delegate = self + controller.modalPresentationStyle = UIDevice.current.userInterfaceIdiom == .pad ? .automatic :.popover + present(controller, animated: true, completion: nil) + } + + // MARK: - Handlers + + private func handle(selectedRow: CustomTorBridgesView.Row) { + + switch selectedRow { + case .input: + return + case .requestBridges: + guard let url = URL(string: TariSettings.shared.torBridgesUrl) else { return } + WebBrowserPresenter.open(url: url) + case .scanQRCode: + showQRCodeScanner() + case .uploadQRCode: + showImagePicker() + } + } +} + +extension CustomTorBridgesViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + + picker.dismiss(animated: true) + + guard let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage, let ciImage = image.makeCIImage() else { return } + guard let features = imageDetector?.features(in: ciImage) else { return } + + let torBridges = features + .compactMap { $0 as? CIQRCodeFeature } + .compactMap { $0.messageString } + .joined() + .findBridges() + + guard let torBridges else { + PopUpPresenter.show(message: MessageModel(title: localized("custom_bridges.error.image_decode.title"), message: localized("custom_bridges.error.image_decode.description"), type: .error)) + return + } + + update(torBridges: torBridges) + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true) + } +} diff --git a/MobileWallet/Screens/Legacy/AddRecipient/Views/AddRecipientSectionHeaderView.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/Views/CustomTorBridgesHeaderView.swift similarity index 69% rename from MobileWallet/Screens/Legacy/AddRecipient/Views/AddRecipientSectionHeaderView.swift rename to MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/Views/CustomTorBridgesHeaderView.swift index bf5d0bc0..8f870e34 100644 --- a/MobileWallet/Screens/Legacy/AddRecipient/Views/AddRecipientSectionHeaderView.swift +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/Views/CustomTorBridgesHeaderView.swift @@ -1,10 +1,10 @@ -// AddRecipientSectionHeaderView.swift +// CustomTorBridgesHeaderView.swift /* Package MobileWallet - Created by Adrian Truszczynski on 18/10/2021 + Created by Adrian Truszczyński on 08/09/2023 Using Swift 5.0 - Running on macOS 12.0 + Running on macOS 13.5 Copyright 2019 The Tari Project @@ -38,27 +38,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import TariCommon -final class AddRecipientSectionHeaderView: DynamicThemeHeaderFooterView { +final class CustomTorBridgesHeaderView: DynamicThemeHeaderFooterView { // MARK: - Subviews @View private var label: UILabel = { let view = UILabel() - view.font = Theme.shared.fonts.txDateValueLabel + view.font = .Avenir.medium.withSize(14.0) + view.text = localized("custom_bridges.item.paste_bridges") return view }() - // MARK: - Properties - - var text: String? { - get { label.text } - set { label.text = newValue } - } - - // MARK: - Initializers + // MARK: - Initialisers override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) @@ -73,23 +66,22 @@ final class AddRecipientSectionHeaderView: DynamicThemeHeaderFooterView { private func setupConstraints() { - addSubview(label) + contentView.addSubview(label) - let constratins = [ - label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 22.0), - label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -22.0), - label.bottomAnchor.constraint(equalTo: bottomAnchor), - label.heightAnchor.constraint(equalToConstant: label.font.pointSize * 1.3), - heightAnchor.constraint(equalToConstant: 35.0) + let constraints = [ + label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 25.0), + label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), + label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), + label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -25.0) ] - NSLayoutConstraint.activate(constratins) + NSLayoutConstraint.activate(constraints) } // MARK: - Updates override func update(theme: ColorTheme) { super.update(theme: theme) - label.textColor = theme.text.lightText + label.textColor = theme.text.heading } } diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/Views/CustomTorBridgesInputCell.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/Views/CustomTorBridgesInputCell.swift new file mode 100644 index 00000000..be9c68b2 --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Custom Tor Bridges/Views/CustomTorBridgesInputCell.swift @@ -0,0 +1,155 @@ +// CustomTorBridgesInputCell.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 05/09/2023 + Using Swift 5.0 + Running on macOS 13.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. +*/ + +import TariCommon + +final class CustomTorBridgesInputCell: UITableViewCell { + + // MARK: - Constants + + private static let placeholderText = """ + Available formates: + • obfs4 : cert= iat-mode= + example: + obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1 + + • : + example: + 78.156.103.189:9301 2BD90810282F8B331FC7D47705167166253E1442 + """ + + // MARK: - Subviews + + @View private var textView: UITextView = { + let view = UITextView() + view.font = .Avenir.medium.withSize(12.0) + return view + }() + + @View private var placeholderLabel: UILabel = { + let view = UILabel() + view.numberOfLines = 0 + view.text = CustomTorBridgesInputCell.placeholderText + view.font = .Avenir.medium.withSize(11.0) + return view + }() + + // MARK: - Properties + + var text: String? { + get { textView.text } + set { + textView.text = newValue + updatePlaceholder() + } + } + + var onTextUpdate: ((String) -> Void)? + + // MARK: - Initilisers + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupConstraints() + setupCallbacks() + updatePlaceholder() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup + + private func setupConstraints() { + + [textView, placeholderLabel].forEach(contentView.addSubview) + + let constraints = [ + textView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 15.0), + textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20.0), + textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20.0), + textView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -15.0), + placeholderLabel.topAnchor.constraint(equalTo: textView.topAnchor), + placeholderLabel.leadingAnchor.constraint(equalTo: textView.leadingAnchor), + placeholderLabel.trailingAnchor.constraint(equalTo: textView.trailingAnchor), + placeholderLabel.bottomAnchor.constraint(lessThanOrEqualTo: textView.bottomAnchor), + contentView.heightAnchor.constraint(equalToConstant: 250.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + textView.delegate = self + } + + // MARK: - Updates + + private func updatePlaceholder() { + placeholderLabel.alpha = (!textView.isFirstResponder && textView.text.isEmpty) ? 1.0 : 0.0 + } + + // MARK: - Reuse + + override func prepareForReuse() { + super.prepareForReuse() + updatePlaceholder() + } +} + +extension CustomTorBridgesInputCell: UITextViewDelegate { + + func textViewDidChange(_ textView: UITextView) { + onTextUpdate?(textView.text ?? "") + } + + func textViewDidBeginEditing(_ textView: UITextView) { + UIView.animate(withDuration: 0.3) { + self.updatePlaceholder() + } + } + + func textViewDidEndEditing(_ textView: UITextView) { + UIView.animate(withDuration: 0.3) { + self.updatePlaceholder() + } + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesHeaderView.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesHeaderView.swift deleted file mode 100644 index 67dc0883..00000000 --- a/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesHeaderView.swift +++ /dev/null @@ -1,158 +0,0 @@ -// CustomBridgesHeaderView.swift - -/* - Package MobileWallet - Created by Browncoat on 06/12/2022 - Using Swift 5.0 - Running on macOS 13.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 TariCommon - -final class CustomBridgesHeaderView: DynamicThemeHeaderFooterView { - - // MARK: - Subiews - - @View private var label: UILabel = { - let view = UILabel() - view.font = Theme.shared.fonts.settingsTableViewLastBackupDate - view.text = localized("custom_bridges.item.paste_bridges") - return view - }() - - @View private var textView: UITextView = { - let view = UITextView() - view.textContainerInset = UIEdgeInsets(top: 15.0, left: 20.0, bottom: 15.0, right: 20.0) - return view - }() - - private let toolbar = UIToolbar() - - // MARK: - Properties - - var text: String? { - get { textView.attributedText.string } - set { updateTextViewToAttrbutedText(with: newValue) } - } - - var textViewDelegate: UITextViewDelegate? { - get { textView.delegate } - set { textView.delegate = newValue } - } - - var isTextViewActive: Bool = false { - didSet { updateTextViewColors() } - } - - private var inactiveTextColor: UIColor? - private var activeTextColor: UIColor? - - // MARK: - Initialisers - - override init(reuseIdentifier: String?) { - super.init(reuseIdentifier: reuseIdentifier) - setupViews() - setupConstraints() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupViews() { - - toolbar.items = [ - UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: self, action: nil), - UIBarButtonItem(title: localized("settings.done"), style: UIBarButtonItem.Style.done, target: textView, action: #selector(UITextField.resignFirstResponder)) - ] - - toolbar.sizeToFit() - - textView.inputAccessoryView = toolbar - } - - private func setupConstraints() { - - [label, textView].forEach(contentView.addSubview) - - let constraints = [ - label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), - label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), - label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 25.0), - label.heightAnchor.constraint(equalToConstant: 18.0), - textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - textView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 15.0), - textView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -35.0) - ] - - NSLayoutConstraint.activate(constraints) - } - - // MARK: - Updates - - override func update(theme: ColorTheme) { - super.update(theme: theme) - - label.textColor = theme.text.heading - textView.backgroundColor = theme.backgrounds.primary - inactiveTextColor = theme.text.lightText - activeTextColor = theme.text.heading - - updateTextViewColors() - } - - private func updateTextViewColors() { - textView.textColor = isTextViewActive ? activeTextColor : inactiveTextColor - } - - private func updateTextViewToAttrbutedText(with string: String?) { - - let string = string ?? "" - let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: string) - - let paragraph = NSMutableParagraphStyle() - paragraph.lineBreakMode = .byCharWrapping - - attributedString.addAttribute(.paragraphStyle, value: paragraph, range: NSRange(location: 0, length: string.count)) - - textView.attributedText = attributedString - textViewDelegate?.textViewDidChange?(textView) - - updateTextViewColors() - } -} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesViewController.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesViewController.swift deleted file mode 100644 index 1c092e85..00000000 --- a/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesViewController.swift +++ /dev/null @@ -1,328 +0,0 @@ -// CustomBridgesViewController.swift - -/* - Package MobileWallet - Created by S.Shovkoplyas on 03.09.2020 - Using Swift 5.0 - Running on macOS 10.15 - - 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 CustomBridgesViewController: SettingsParentTableViewController, CustomBridgesHandable { - - private enum Section: Int, CaseIterable { - case requestBridges - case QRcode - } - - private enum CustomBridgesTitle: CaseIterable { - case requestBridgesFromTorproject - case scanQRCode - case uploadQRCode - - var rawValue: String { - switch self { - case .requestBridgesFromTorproject: return localized("custom_bridges.item.request_bridges_from_torproject") - case .scanQRCode: return localized("custom_bridges.item.scan_QR_code") - case .uploadQRCode: return localized("custom_bridges.item.upload_QR_code") - } - } - } - - private var bridgesConfiguration: BridgesConfiguration? - private let examplePlaceHolderString = """ - Available formates: - • obfs4 : cert= iat-mode= - example: - obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1 - - • : - example: - 78.156.103.189:9301 2BD90810282F8B331FC7D47705167166253E1442 - """ - private lazy var detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) - - private let headerView = CustomBridgesHeaderView() - - private let requestBridgesSectionItems: [SystemMenuTableViewCellItem] = [ - SystemMenuTableViewCellItem(title: CustomBridgesTitle.requestBridgesFromTorproject.rawValue) - ] - - private let qrSectionItems: [SystemMenuTableViewCellItem] = [ - SystemMenuTableViewCellItem(title: CustomBridgesTitle.scanQRCode.rawValue), - SystemMenuTableViewCellItem(title: CustomBridgesTitle.uploadQRCode.rawValue) - ] - - private var initialValue: String? - private var cancellables = Set() - - init(bridgesConfiguration: BridgesConfiguration, initialValue: String? = nil) { - self.bridgesConfiguration = bridgesConfiguration - self.initialValue = initialValue - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - tableView.delegate = self - tableView.dataSource = self - - setupCustomBridgeProgressHandler() - .store(in: &cancellables) - } -} - -// MARK: Setup subviews -extension CustomBridgesViewController { - - override func setupNavigationBar() { - super.setupNavigationBar() - - navigationBar.title = localized("custom_bridges.title") - navigationBar.update(rightButton: NavigationBar.ButtonModel(title: localized("custom_bridges.connect"), callback: { [weak self] in - self?.connectAction() - })) - navigationBar.rightButton(index: 0)?.isEnabled = false - } - - private func onTorConnDifficulties(error: Error) { - applyConnectingStatus() // for returning to previous bridges configuration - let backupConfiguration = OnionSettings.backupBridgesConfiguration - let currentBridgesStr = backupConfiguration.customBridges?.joined(separator: "\n") - bridgesConfiguration?.customBridges = backupConfiguration.customBridges - bridgesConfiguration?.bridgesType = backupConfiguration.bridgesType - headerView.text = (currentBridgesStr?.isEmpty ?? true) ? examplePlaceHolderString : currentBridgesStr - onCustomBridgeFailureAction(error: error) - } - - private func openScannerVC() { - AppRouter.presentQrCodeScanner(expectedDataTypes: [.torBridges]) { [weak self] data in - guard case let .bridges(bridges) = data else { return } - self?.update(torBridges: bridges) - } - } - - private func openImagePickerVC() { - let vc = UIImagePickerController() - vc.delegate = self - vc.modalPresentationStyle = UIDevice.current.userInterfaceIdiom == .pad ? .automatic :.popover - present(vc, animated: true, completion: nil) - } - - private func connectAction() { - - guard let bridgesConfiguration = bridgesConfiguration else { return } - applyConnectingStatus() - - bridgesConfiguration.customBridges = (headerView.text ?? "") - .components(separatedBy: "\n") - .map({ bridge in bridge.trimmingCharacters(in: .whitespacesAndNewlines) }) - .filter({ bridge in !bridge.isEmpty && !bridge.hasPrefix("//") && !bridge.hasPrefix("#") }) - - bridgesConfiguration.bridgesType = bridgesConfiguration.customBridges?.isEmpty == true ? .none : .custom - - headerView.resignFirstResponder() - - Task { - do { - try await Tari.shared.update(torBridgesConfiguration: bridgesConfiguration) - onCustomBridgeSuccessAction() - } catch { - onTorConnDifficulties(error: error) - } - } - } - - private func applyConnectingStatus() { - navigationBar.progress = 0.0 - navigationBar.rightButton(index: 0)?.isEnabled = false - view.isUserInteractionEnabled = false - } - - private func update(torBridges: String) { - headerView.text = torBridges - } -} - -extension CustomBridgesViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { - picker.dismiss(animated: true) - - var raw = "" - - if let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage, - let ciImage = image.ciImage ?? (image.cgImage != nil ? CIImage(cgImage: image.cgImage!) : nil) { - - let features = detector?.features(in: ciImage) - - for feature in features as? [CIQRCodeFeature] ?? [] { - raw += feature.messageString ?? "" - } - } - - if let bridges = raw.findBridges() { - update(torBridges: bridges) - } else { - PopUpPresenter.show(message: MessageModel(title: localized("custom_bridges.error.image_decode.title"), message: localized("custom_bridges.error.image_decode.description"), type: .error)) - } - } - - func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - picker.dismiss(animated: true) - } -} - -extension CustomBridgesViewController: UITableViewDelegate, UITableViewDataSource { - func numberOfSections(in tableView: UITableView) -> Int { - return Section.allCases.count - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard let section = Section(rawValue: section) else { return 0 } - switch section { - case .requestBridges: - return requestBridgesSectionItems.count - case .QRcode: - return qrSectionItems.count - } - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(type: SystemMenuTableViewCell.self, indexPath: indexPath) - guard let section = Section(rawValue: indexPath.section) else { return cell } - - switch section { - case .requestBridges: cell.configure(requestBridgesSectionItems[indexPath.row]) - case .QRcode: cell.configure(qrSectionItems[indexPath.row]) - } - - cell.preservesSuperviewLayoutMargins = false - cell.separatorInset = .zero - cell.layoutMargins = .zero - - return cell - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - 65 - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - if section != 0 { return 35 } - return 210 - } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - if section != 0 { return nil } - - headerView.textViewDelegate = self - - if let initialValue { - headerView.text = initialValue - self.initialValue = nil - } else { - let customBridgesText = bridgesConfiguration?.customBridges?.joined(separator: "\n") - headerView.text = (customBridgesText?.isEmpty ?? true) ? examplePlaceHolderString : customBridgesText - } - - return headerView - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: false) - guard let section = Section(rawValue: indexPath.section) else { return } - switch section { - case .requestBridges: - WebBrowserPresenter.open(url: OnionSettings.torBridgesLink) - case .QRcode: - if CustomBridgesTitle.allCases[indexPath.row + indexPath.section] == .scanQRCode { - openScannerVC() - return - } - if CustomBridgesTitle.allCases[indexPath.row + indexPath.section] == .uploadQRCode { - openImagePickerVC() - return - } - - } - } -} - -extension CustomBridgesViewController: UITextViewDelegate { - func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - if text.count == 0 { - return true - } - let string = textView.attributedText.string as NSString - let newString = string.replacingCharacters(in: range, with: text) - - if let currentPosition = textView.selectedTextRange?.start, - let newPosition = textView.position(from: currentPosition, offset: text.count) { - headerView.text = newString - textView.selectedTextRange = textView.textRange(from: newPosition, to: newPosition) - } else { - headerView.text = newString - } - return false - } - - func textViewDidChange(_ textView: UITextView) { - let isTextFieldNotEmpty = !textView.text.isEmpty - let isNewBridge = textView.text.trimmingCharacters(in: .whitespacesAndNewlines) != bridgesConfiguration?.customBridges?.joined(separator: "\n") - let isNotPlaceholder = textView.text != examplePlaceHolderString - navigationBar.rightButton(index: 0)?.isEnabled = isTextFieldNotEmpty && isNewBridge && isNotPlaceholder - } - - func textViewDidBeginEditing(_ textView: UITextView) { - if textView.text == examplePlaceHolderString && !headerView.isTextViewActive { - textView.text = "" - } - - headerView.isTextViewActive = true - } - - func textViewDidEndEditing(_ textView: UITextView) { - if textView.text == "" { - textView.text = examplePlaceHolderString - } - - headerView.isTextViewActive = false - } -} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesConstructor.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesConstructor.swift new file mode 100644 index 00000000..740b64ce --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesConstructor.swift @@ -0,0 +1,47 @@ +// TorBridgesConstructor.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 01/10/2023 + Using Swift 5.0 + Running on macOS 13.5 + + 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 TorBridgesConstructor { + + static func buildScene() -> TorBridgesViewController { + let model = TorBridgesModel() + return TorBridgesViewController(model: model) + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesModel.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesModel.swift new file mode 100644 index 00000000..d69c1788 --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesModel.swift @@ -0,0 +1,90 @@ +// TorBridgesModel.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 04/09/2023 + Using Swift 5.0 + Running on macOS 13.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. +*/ + +final class TorBridgesModel { + + enum BridgesType { + case noBridges + case customBridges + } + + enum Action { + case setupCustomBridges(bridges: String?) + } + + // MARK: - View Model + + @Published private(set) var isConnectionPossible = false + @Published private(set) var areCustomBridgesSelected = false + @Published private(set) var selectedBridgesType: BridgesType = .noBridges + @Published private(set) var action: Action? + + // MARK: - Properties + + private var isUsingCustomBridges: Bool { Tari.shared.isUsingCustomBridges } + + // MARK: - Actions + + func updateStatus() { + updateStatus(areCustomBridgesSelected: isUsingCustomBridges) + } + + func update(areCustomBridgesSelected: Bool) { + self.areCustomBridgesSelected = areCustomBridgesSelected + updateStatus(areCustomBridgesSelected: areCustomBridgesSelected) + guard areCustomBridgesSelected else { return } + action = .setupCustomBridges(bridges: Tari.shared.torBridges) + } + + func connect() { + switch selectedBridgesType { + case .noBridges: + Tari.shared.update(torBridges: nil) + update(areCustomBridgesSelected: false) + case .customBridges: + break + } + } + + private func updateStatus(areCustomBridgesSelected: Bool) { + selectedBridgesType = areCustomBridgesSelected ? .customBridges : .noBridges + isConnectionPossible = areCustomBridgesSelected != isUsingCustomBridges + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesView.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesView.swift new file mode 100644 index 00000000..964c494d --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesView.swift @@ -0,0 +1,181 @@ +// TorBridgesView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 04/09/2023 + Using Swift 5.0 + Running on macOS 13.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. +*/ + +import TariCommon + +final class TorBridgesView: BaseNavigationContentView { + + enum Row: Int, CaseIterable { + case noBridges + case customBridges + } + + // MARK: - Subviews + + @View private var tableView: BaseMenuTableView = { + let view = BaseMenuTableView() + view.register(type: AccessoryImageMenuCell.self) + view.register(headerFooterType: TorBridgesFooterView.self) + return view + }() + + // MARK: - Properties + + var isConnectButtonEnabled: Bool { + get { navigationBar.rightButton(index: 0)?.isEnabled ?? false } + set { navigationBar.rightButton(index: 0)?.isEnabled = newValue } + } + + var onSelectedRow: ((Row) -> Void)? + var onConnectButtonTap: (() -> Void)? + + private var dataSource: UITableViewDiffableDataSource? + + // MARK: - Initialisers + + override init() { + super.init() + setupNavigationBar() + setupConstraints() + setupCallbacks() + setupCells(selectedRow: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupNavigationBar() { + navigationBar.title = localized("bridges_configuration.title") + navigationBar.update(rightButton: NavigationBar.ButtonModel(title: localized("bridges_configuration.connect"), callback: { [weak self] in + self?.onConnectButtonTap?() + })) + } + + private func setupConstraints() { + + addSubview(tableView) + + let constraints = [ + tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + 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, cellProvider: { tableView, indexPath, viewModel in + let cell = tableView.dequeueReusableCell(type: AccessoryImageMenuCell.self, indexPath: indexPath) + cell.update(viewModel: viewModel) + return cell + }) + + tableView.delegate = self + } + + private func setupCells(selectedRow: Row?) { + + let items = Row + .allCases + .map { + AccessoryImageMenuCell.ViewModel( + baseModel: MenuCell.ViewModel( + id: UInt($0.rawValue), + title: $0.title, + isArrowVisible: $0.isArrorVisible, + isDestructive: false + ), + accessoryImage: selectedRow == $0 ? Theme.shared.images.scheduledIcon : nil + ) + } + + var snapshot = NSDiffableDataSourceSnapshot() + + snapshot.appendSections([0]) + snapshot.appendItems(items) + + dataSource?.apply(snapshot: snapshot) + } + + // MARK: - Actions + + func select(row: Row) { + setupCells(selectedRow: row) + } +} + +extension TorBridgesView: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let row = Row(rawValue: indexPath.row) else { return } + onSelectedRow?(row) + } + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + tableView.dequeueReusableHeaderFooterView(type: TorBridgesFooterView.self) + } +} + +private extension TorBridgesView.Row { + + var title: String { + switch self { + case .noBridges: + return localized("bridges_configuration.item.noBridges") + case .customBridges: + return localized("bridges_configuration.item.custom") + } + } + + var isArrorVisible: Bool { + switch self { + case .noBridges: + return false + case .customBridges: + return true + } + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesViewController.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesViewController.swift new file mode 100644 index 00000000..4006614b --- /dev/null +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/TorBridgesViewController.swift @@ -0,0 +1,139 @@ +// TorBridgesViewController.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 04/09/2023 + Using Swift 5.0 + Running on macOS 13.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. +*/ + +import UIKit +import Combine + +final class TorBridgesViewController: UIViewController { + + // MARK: - Properties + + private let model: TorBridgesModel + private let mainView = TorBridgesView() + + private var cancellables = Set() + + // MARK: - Initialisers + + init(model: TorBridgesModel) { + 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 loadView() { + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupCallbacks() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + model.updateStatus() + } + + // MARK: - Setups + + private func setupCallbacks() { + + model.$isConnectionPossible + .sink { [weak self] in self?.mainView.isConnectButtonEnabled = $0 } + .store(in: &cancellables) + + model.$selectedBridgesType + .sink { [weak self] in self?.handle(selectedBridgesType: $0) } + .store(in: &cancellables) + + model.$action + .compactMap { $0 } + .sink { [weak self] in self?.handle(action: $0) } + .store(in: &cancellables) + + mainView.onSelectedRow = { [weak self] in + self?.handle(selectedRowByUser: $0) + } + + mainView.onConnectButtonTap = { [weak self] in + self?.model.connect() + } + } + + // MARK: - Actions + + private func moveToCustomTorBridgesScene(bridges: String?) { + let controller = CustomTorBridgesConstructor.buildScene(bridges: bridges) + navigationController?.pushViewController(controller, animated: true) + } + + // MARK: - Handlers + + private func handle(selectedRowByUser: TorBridgesView.Row) { + switch selectedRowByUser { + case .noBridges: + model.update(areCustomBridgesSelected: false) + case .customBridges: + model.update(areCustomBridgesSelected: true) + } + } + + private func handle(selectedBridgesType: TorBridgesModel.BridgesType) { + switch selectedBridgesType { + case .noBridges: + mainView.select(row: .noBridges) + case .customBridges: + mainView.select(row: .customBridges) + } + } + + private func handle(action: TorBridgesModel.Action) { + switch action { + case let .setupCustomBridges(bridges): + moveToCustomTorBridgesScene(bridges: bridges) + } + } +} diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/BridgesConfigurationFooterView.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/Views/TorBridgesFooterView.swift similarity index 96% rename from MobileWallet/Screens/AdvancedSettings/Bridges/BridgesConfigurationFooterView.swift rename to MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/Views/TorBridgesFooterView.swift index c5e075c6..d1c540a1 100644 --- a/MobileWallet/Screens/AdvancedSettings/Bridges/BridgesConfigurationFooterView.swift +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/Tor Bridges List/Views/TorBridgesFooterView.swift @@ -38,10 +38,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit -import TariCommon + import TariCommon -final class BridgesConfigurationFooterView: DynamicThemeHeaderFooterView { + final class TorBridgesFooterView: DynamicThemeHeaderFooterView { // MARK: - Subviews @@ -86,4 +85,4 @@ final class BridgesConfigurationFooterView: DynamicThemeHeaderFooterView { super.update(theme: theme) label.textColor = theme.text.lightText } -} + } diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift b/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift index 3f3b97ac..7a77cfb3 100644 --- a/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift +++ b/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift @@ -75,6 +75,11 @@ final class SplashViewController: UIViewController { setupCallbacks() } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + TrackingConsentManager.handleTrackingConsent() + } + // MARK: - Setups private func setupCallbacks() { diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift index 484b275e..dca2e043 100644 --- a/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift +++ b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift @@ -172,6 +172,7 @@ final class SplashViewModel { Tari.shared.deleteWallet() Tari.shared.canAutomaticalyReconnectWallet = false status = StatusModel(status: .idle, statusRepresentation: .content) + isWalletExist = Tari.shared.isWalletExist } private func connectToWallet(isWalletConnected: Bool) async throws { diff --git a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift index ba84d93f..f5fa3853 100644 --- a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift +++ b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift @@ -235,7 +235,7 @@ extension ContactBookContactListView: UITableViewDelegate { guard let title = viewModels[section].title else { return nil } let headerView = tableView.dequeueReusableHeaderFooterView(type: MenuTableHeaderView.self) - headerView.label.text = title + headerView.title = title return headerView } diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift b/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift index 0712ea67..eef4062b 100644 --- a/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift +++ b/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift @@ -96,7 +96,6 @@ final class ContactBookCell: DynamicThemeCell { } private(set) var elementID: UUID? - private(set) var isExpanded: Bool = false private var isSelectable: Bool = false private var normalModeConstraint: NSLayoutConstraint? diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactBookShareBar.swift b/MobileWallet/Screens/Contact Book/List/Views/ContactBookShareBar.swift index 6fae262f..a5e707fa 100644 --- a/MobileWallet/Screens/Contact Book/List/Views/ContactBookShareBar.swift +++ b/MobileWallet/Screens/Contact Book/List/Views/ContactBookShareBar.swift @@ -107,8 +107,8 @@ final class ContactBookShareBar: UIView { selectedIdentifier = 0 } - private func makeButton(model: ViewModel, isSelected: Bool) -> ContactBookShareButton { - @View var button = ContactBookShareButton() + private func makeButton(model: ViewModel, isSelected: Bool) -> RoundedLabeledButton { + @View var button = RoundedLabeledButton() button.buttonSize = 46.0 button.padding = 10.0 button.isSelected = isSelected @@ -120,7 +120,7 @@ final class ContactBookShareBar: UIView { private func updateButtons() { stackView.arrangedSubviews - .compactMap { $0 as? ContactBookShareButton } + .compactMap { $0 as? RoundedLabeledButton } .forEach { $0.isSelected = $0.tag == self.selectedIdentifier } } } diff --git a/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift b/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift index 2bf5a421..d2ec7305 100644 --- a/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift +++ b/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift @@ -97,7 +97,7 @@ final class ContactsManager { .map { $0.firstOrEmpty } .joined() } else { - name = internalModel?.alias ?? internalModel?.emojiID.obfuscatedText ?? "" + name = internalModel?.alias ?? internalModel?.defaultAlias ?? internalModel?.emojiID.obfuscatedText ?? "" nameComponents = [name] avatar = internalModel?.emojiID.firstOrEmpty ?? "" } diff --git a/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift b/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift index d8b1e8d4..63ce3b42 100644 --- a/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift +++ b/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift @@ -43,6 +43,7 @@ final class InternalContactsManager { struct ContactModel: Hashable { let alias: String? + let defaultAlias: String? let emojiID: String let hex: String let isFavorite: Bool @@ -60,8 +61,11 @@ final class InternalContactsManager { var models: [ContactModel] = [] - models += try fetchWalletContacts().map { try ContactModel(alias: $0.alias, emojiID: $0.address.emojis, hex: $0.address.byteVector.hex, isFavorite: $0.isFavorite) } - models += try fetchTariAddresses().map { try ContactModel(alias: nil, emojiID: $0.emojis, hex: $0.byteVector.hex, isFavorite: false) } + models += try fetchWalletContacts().map { try ContactModel(alias: $0.alias, defaultAlias: nil, emojiID: $0.address.emojis, hex: $0.address.byteVector.hex, isFavorite: $0.isFavorite) } + models += try fetchTariAddresses().map { + let placeholder = try $0.isUnknownUser ? localized("transaction.unknown_source") : nil + return try ContactModel(alias: nil, defaultAlias: placeholder, emojiID: $0.emojis, hex: $0.byteVector.hex, isFavorite: false) + } return models .reduce(into: [ContactModel]()) { collection, model in @@ -92,7 +96,7 @@ final class InternalContactsManager { func create(name: String, isFavorite: Bool, address: TariAddress) throws -> ContactModel { let contact = try Contact(alias: name, isFavorite: isFavorite, addressPointer: address.pointer) try Tari.shared.contacts.upsert(contact: contact) - return try ContactModel(alias: name, emojiID: address.emojis, hex: address.byteVector.hex, isFavorite: isFavorite) + return try ContactModel(alias: name, defaultAlias: nil, emojiID: address.emojis, hex: address.byteVector.hex, isFavorite: isFavorite) } func update(name: String, isFavorite: Bool, hex: String) throws { diff --git a/MobileWallet/Screens/Home/Home/HomeModel.swift b/MobileWallet/Screens/Home/Home/HomeModel.swift index 51ce3691..0a87713c 100644 --- a/MobileWallet/Screens/Home/Home/HomeModel.swift +++ b/MobileWallet/Screens/Home/Home/HomeModel.swift @@ -126,7 +126,7 @@ final class HomeModel { // MARK: - Handlers - private func handle(networkConnection: NetworkMonitor.Status, torConnection: TorManager.ConnectionStatus, baseNodeConnection: BaseNodeConnectivityStatus, syncStatus: TariValidationService.SyncStatus) { + private func handle(networkConnection: NetworkMonitor.Status, torConnection: TorConnectionStatus, baseNodeConnection: BaseNodeConnectivityStatus, syncStatus: TariValidationService.SyncStatus) { switch (networkConnection, torConnection, baseNodeConnection, syncStatus) { case (.disconnected, _, _, _), diff --git a/MobileWallet/Screens/Home/Home/HomeView.swift b/MobileWallet/Screens/Home/Home/HomeView.swift index 8a3a51ad..9e0b982f 100644 --- a/MobileWallet/Screens/Home/Home/HomeView.swift +++ b/MobileWallet/Screens/Home/Home/HomeView.swift @@ -192,6 +192,7 @@ final class HomeView: UIView { var onTransactionCellTap: ((_ identifier: UInt64) -> Void)? private var transactionsDataSource: UITableViewDiffableDataSource? + private var avatarConstraints: [NSLayoutConstraint] = [] // MARK: - Initialisers @@ -215,6 +216,11 @@ final class HomeView: UIView { [pulseView, avatarView, avatarButton].forEach(avatarContentView.addSubview) [waveBackgroundView, buttonsStackView, balanceContentView, availableBalanceContentView, avatarContentView, viewAllTransactionsButton, transactionTableView, transactionPlaceholderView].forEach(addSubview) + avatarConstraints = [ + avatarView.widthAnchor.constraint(equalToConstant: 0.0), + avatarView.heightAnchor.constraint(equalToConstant: 0.0) + ] + let constraints = [ waveBackgroundView.topAnchor.constraint(equalTo: topAnchor), waveBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor), @@ -257,20 +263,18 @@ final class HomeView: UIView { amountHelpButton.centerYAnchor.constraint(equalTo: availableBalanceContentView.centerYAnchor), amountHelpButton.widthAnchor.constraint(equalToConstant: 22.0), amountHelpButton.heightAnchor.constraint(equalToConstant: 22.0), - avatarContentView.topAnchor.constraint(equalTo: availableBalanceContentView.bottomAnchor), + avatarContentView.topAnchor.constraint(equalTo: availableBalanceContentView.bottomAnchor, constant: 30.0), avatarContentView.leadingAnchor.constraint(equalTo: leadingAnchor), avatarContentView.trailingAnchor.constraint(equalTo: trailingAnchor), avatarView.centerXAnchor.constraint(equalTo: avatarContentView.centerXAnchor), avatarView.centerYAnchor.constraint(equalTo: avatarContentView.centerYAnchor), - avatarView.widthAnchor.constraint(equalToConstant: Self.avatarWidth), - avatarView.heightAnchor.constraint(equalToConstant: Self.avatarWidth), avatarButton.topAnchor.constraint(equalTo: avatarView.topAnchor), avatarButton.leadingAnchor.constraint(equalTo: avatarView.leadingAnchor), avatarButton.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), avatarButton.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor), viewAllTransactionsButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -55.0), viewAllTransactionsButton.centerXAnchor.constraint(equalTo: centerXAnchor), - transactionTableView.topAnchor.constraint(equalTo: avatarContentView.bottomAnchor), + transactionTableView.topAnchor.constraint(equalTo: avatarContentView.bottomAnchor, constant: 20.0), transactionTableView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 26.0), transactionTableView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -26.0), transactionTableView.bottomAnchor.constraint(equalTo: viewAllTransactionsButton.topAnchor, constant: -20.0), @@ -283,7 +287,7 @@ final class HomeView: UIView { pulseView.centerYAnchor.constraint(equalTo: avatarView.centerYAnchor) ] - NSLayoutConstraint.activate(constraints) + NSLayoutConstraint.activate(constraints + avatarConstraints) } private func setupCallbacks() { @@ -390,6 +394,14 @@ final class HomeView: UIView { waveBackgroundView.stopAnimation() pulseView.stopAnimation() } + + // MARK: - Layout + + override func layoutSubviews() { + super.layoutSubviews() + let avatarWidth = min(avatarContentView.bounds.height, Self.avatarWidth) + avatarConstraints.forEach { $0.constant = avatarWidth } + } } extension HomeView: UITableViewDelegate { diff --git a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryView.swift b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryView.swift index 8cc7a6f7..07d8288c 100644 --- a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryView.swift +++ b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryView.swift @@ -67,7 +67,7 @@ final class TransactionHistoryView: BaseNavigationContentView { view.sectionHeaderTopPadding = .zero } view.register(type: TransactionHistoryCell.self) - view.register(headerFooterType: TransactionHistoryHeaderView.self) + view.register(headerFooterType: MenuTableHeaderView.self) return view }() @@ -75,7 +75,6 @@ final class TransactionHistoryView: BaseNavigationContentView { var searchText: AnyPublisher { searchTextSubject.eraseToAnyPublisher() } - var onWalletButtonTap: (() -> Void)? var onCellTap: ((_ id: UInt64) -> Void)? private let searchTextSubject = CurrentValueSubject("") @@ -100,9 +99,6 @@ final class TransactionHistoryView: BaseNavigationContentView { private func setupNavigationBar() { navigationBar.title = localized("transaction_history.title") - navigationBar.update(rightButton: NavigationBar.ButtonModel(image: .icons.wallet, callback: { [weak self] in - self?.onWalletButtonTap?() - })) } private func setupConstraints() { @@ -177,7 +173,7 @@ extension TransactionHistoryView: UITableViewDelegate { } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let view = tableView.dequeueReusableHeaderFooterView(type: TransactionHistoryHeaderView.self) + let view = tableView.dequeueReusableHeaderFooterView(type: MenuTableHeaderView.self) view.title = viewModels[section].sectionTitle return view } diff --git a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift index 8214140a..0fc14125 100644 --- a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift +++ b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift @@ -91,10 +91,6 @@ final class TransactionHistoryViewController: UIViewController { .sink { [weak self] in self?.moveToTransactionDetails(transaction: $0) } .store(in: &cancellables) - mainView.onWalletButtonTap = { [weak self] in - self?.moveToUTXOsWallet() - } - mainView.searchText .sink { [weak self] in self?.model.searchText = $0 } .store(in: &cancellables) @@ -127,11 +123,6 @@ final class TransactionHistoryViewController: UIViewController { // MARK: - Actions - private func moveToUTXOsWallet() { - let controller = UTXOsWalletConstructor.buildScene() - navigationController?.pushViewController(controller, animated: true) - } - private func moveToTransactionDetails(transaction: Transaction) { let controller = TransactionDetailsConstructor.buildScene(transaction: transaction) navigationController?.pushViewController(controller, animated: true) diff --git a/MobileWallet/Screens/Legacy/AddRecipient/Views/ContactAvatarView.swift b/MobileWallet/Screens/Legacy/AddRecipient/Views/ContactAvatarView.swift deleted file mode 100644 index 5918fdbe..00000000 --- a/MobileWallet/Screens/Legacy/AddRecipient/Views/ContactAvatarView.swift +++ /dev/null @@ -1,115 +0,0 @@ -// ContactAvatarView.swift - -/* - Package MobileWallet - Created by Adrian Truszczynski on 17/10/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 TariCommon - -final class ContactAvatarView: DynamicThemeView { - - // MARK: - Subviews - - @View private var placeholderImageView: UIImageView = { - let view = UIImageView() - view.image = .legacy.unknownUser - view.contentMode = .scaleAspectFit - return view - }() - - @View private var label: UILabel = { - let view = UILabel() - view.font = .Avenir.heavy.withSize(24.0) - return view - }() - - override func update(theme: ColorTheme) { - super.update(theme: theme) - - backgroundColor = theme.neutral.tertiary - placeholderImageView.tintColor = theme.text.body - label.textColor = theme.text.body - } - - // MARK: - Properties - - var text: String = "" { - didSet { - label.text = text - label.isHidden = text.isEmpty - placeholderImageView.isHidden = !text.isEmpty - } - } - - // MARK: - Initializers - - override init() { - super.init() - setupViews() - setupConstraints() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupViews() { - layer.cornerRadius = 12.0 - clipsToBounds = true - } - - private func setupConstraints() { - - [placeholderImageView, label].forEach(addSubview) - - let constraints = [ - placeholderImageView.centerXAnchor.constraint(equalTo: centerXAnchor), - placeholderImageView.centerYAnchor.constraint(equalTo: centerYAnchor), - placeholderImageView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5), - placeholderImageView.widthAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5), - label.centerXAnchor.constraint(equalTo: centerXAnchor), - label.centerYAnchor.constraint(equalTo: centerYAnchor), - heightAnchor.constraint(equalToConstant: 44.0), - widthAnchor.constraint(equalToConstant: 44.0) - ] - - NSLayoutConstraint.activate(constraints) - } -} diff --git a/MobileWallet/Screens/Legacy/AddRecipient/Views/ContactCell.swift b/MobileWallet/Screens/Legacy/AddRecipient/Views/ContactCell.swift deleted file mode 100644 index 90643c83..00000000 --- a/MobileWallet/Screens/Legacy/AddRecipient/Views/ContactCell.swift +++ /dev/null @@ -1,131 +0,0 @@ -// ContactCell.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2020/02/11 - Using Swift 5.0 - Running on macOS 10.15 - - 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 TariCommon - -final class ContactCell: DynamicThemeCell { - - // MARK: - Views - - @View private var contactAvatarView = ContactAvatarView() - - @View private var aliasLabel: UILabel = { - let view = UILabel() - view.font = .Avenir.heavy.withSize(15.0) - return view - }() - - // MARK: - Properties - - var initial: String { - get { contactAvatarView.text } - set { contactAvatarView.text = newValue } - } - - var aliasText: String? { - get { aliasLabel.text } - set { aliasLabel.text = newValue } - } - - var isEmojiID: Bool = false { - didSet { updateAliasLabelColors(theme: theme) } - } - - // MARK: - Initializers - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupViews() - setupConstraints() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupViews() { - backgroundColor = .clear - selectionStyle = .none - } - - private func setupConstraints() { - - translatesAutoresizingMaskIntoConstraints = false - - [contactAvatarView, aliasLabel].forEach(contentView.addSubview) - - let constraints = [ - contactAvatarView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - contactAvatarView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 22.0), - aliasLabel.topAnchor.constraint(equalTo: contentView.topAnchor), - aliasLabel.leadingAnchor.constraint(equalTo: contactAvatarView.trailingAnchor, constant: 10.0), - aliasLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - aliasLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - aliasLabel.heightAnchor.constraint(equalToConstant: aliasLabel.font.pointSize * 1.15), - contentView.heightAnchor.constraint(equalToConstant: 70.0) - ] - - NSLayoutConstraint.activate(constraints) - } - - // MARK: - Updates - - override func update(theme: ColorTheme) { - super.update(theme: theme) - updateAliasLabelColors(theme: theme) - } - - private func updateAliasLabelColors(theme: ColorTheme) { - aliasLabel.textColor = isEmojiID ? theme.text.lightText : theme.text.heading - } - - // MARK: - States - - override func setHighlighted(_ highlighted: Bool, animated: Bool) { - super.setHighlighted(highlighted, animated: true) - - UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseIn) { - self.contentView.subviews.forEach { $0.alpha = self.isHighlighted ? 0.6 : 1.0 } - } - } -} diff --git a/MobileWallet/Screens/Profile/ProfileModel.swift b/MobileWallet/Screens/Profile/ProfileModel.swift index 4e3d8172..ee61e900 100644 --- a/MobileWallet/Screens/Profile/ProfileModel.swift +++ b/MobileWallet/Screens/Profile/ProfileModel.swift @@ -71,8 +71,7 @@ final class ProfileModel { @Published var name: String? @Published private(set) var emojiData: EmojiData? - @Published private(set) var description: String? - @Published private(set) var isReconnectButtonVisible: Bool = false + @Published private(set) var isYatOutOfSync: Bool = false @Published private(set) var errorMessage: MessageModel? @Published private(set) var yatButtonState: YatButtonState = .hidden @Published private(set) var yatAddress: String? @@ -84,13 +83,8 @@ final class ProfileModel { private var walletAddress: TariAddress? private var yat: String? - private var isYatOutOfSync = false private var bleTask: BLECentralTask? - private var walletDescription: String { - localized("profile_view.error.qr_code.description.with_param", arguments: NetworkManager.shared.selectedNetwork.tickerSymbol) - } - // MARK: - Initialisers init() { @@ -134,7 +128,6 @@ final class ProfileModel { do { self.walletAddress = try Tari.shared.walletAddress - updateYatIdData() } catch { emojiData = nil errorMessage = MessageModel(title: localized("profile_view.error.qr_code.title"), message: localized("wallet.error.failed_to_access"), type: .error) @@ -215,13 +208,9 @@ final class ProfileModel { case .hidden, .loading, .off: guard let walletAddress = walletAddress else { return } emojiData = EmojiData(emojiID: try walletAddress.emojis, hex: try walletAddress.byteVector.hex, copyText: localized("emoji.copy"), tooltipText: localized("emoji.hex_tip")) - description = walletDescription - isReconnectButtonVisible = false case .on: guard let yat = self.yat else { return } emojiData = EmojiData(emojiID: yat, hex: nil, copyText: localized("emoji.yat.copy"), tooltipText: nil) - description = isYatOutOfSync ? localized("profile_view.error.yat_mismatch") : walletDescription - isReconnectButtonVisible = isYatOutOfSync } } diff --git a/MobileWallet/Screens/Profile/ProfileView.swift b/MobileWallet/Screens/Profile/ProfileView.swift index ec7eecb9..5c1762e2 100644 --- a/MobileWallet/Screens/Profile/ProfileView.swift +++ b/MobileWallet/Screens/Profile/ProfileView.swift @@ -38,7 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import TariCommon import Lottie @@ -46,6 +45,13 @@ final class ProfileView: BaseNavigationContentView { // MARK: - Subviews + @View private var usernameLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.heavy.withSize(16.0) + view.textAlignment = .center + return view + }() + @View private var emojiIdView = EmojiIdView() @View var yatButton: BaseButton = BaseButton() @@ -57,48 +63,91 @@ final class ProfileView: BaseNavigationContentView { return view }() - @View var middleLabel: UILabel = { + @View private var yatOutOfSyncLabel: StylizedLabel = { + let view = StylizedLabel() + view.normalFont = .Avenir.medium.withSize(13.0) + view.boldFont = .Avenir.heavy.withSize(13.0) + view.separator = " " + view.textAlignment = .center + view.numberOfLines = 0 + view.alpha = 0.0 + view.textComponents = [ + StylizedLabel.StylizedText(text: localized("profile_view.label.out_of_sync.part.1"), style: .normal), + StylizedLabel.StylizedText(text: localized("profile_view.label.out_of_sync.part.2.bold"), style: .bold), + StylizedLabel.StylizedText(text: localized("profile_view.label.out_of_sync.part.3"), style: .normal) + ] + return view + }() + + @View private var auroraButtonsStackView: UIStackView = { + let view = UIStackView() + view.spacing = 40.0 + return view + }() + + @View private var walletButton: RoundedLabeledButton = { + let view = RoundedLabeledButton() + view.buttonSize = 46.0 + view.padding = 12.0 + view.update(image: .icons.wallet, text: localized("profile_view.button.wallet")) + return view + }() + + @View private var connectYatButton: RoundedLabeledButton = { + 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")) + return view + }() + + @View private var shareSectionSeparator = UIView() + + @View private var shareSectionTitleLabel: UILabel = { let view = UILabel() - view.font = Theme.shared.fonts.profileMiddleLabel + view.font = .Avenir.heavy.withSize(16.0) view.textAlignment = .center - view.numberOfLines = 2 - view.adjustsFontSizeToFitWidth = true + view.text = localized("profile_view.label.title.share") return view }() - @View var reconnectYatButton: TextButton = { - let view = TextButton() - view.setTitle(localized("profile_view.button.recconect_yat"), for: .normal) + @View private var shareSectionDescriptionLabel: UILabel = { + let view = UILabel() + view.font = Theme.shared.fonts.profileMiddleLabel + view.textAlignment = .center + view.numberOfLines = 2 + view.adjustsFontSizeToFitWidth = true + view.text = localized("profile_view.error.qr_code.description.with_param", arguments: NetworkManager.shared.selectedNetwork.tickerSymbol) return view }() - @View private var buttonsStackView: UIStackView = { + @View private var shareButtonsStackView: UIStackView = { let view = UIStackView() view.spacing = 40.0 return view }() - @View private var qrCodeButton: ContactBookShareButton = { - let view = ContactBookShareButton() - view.buttonSize = 80.0 - view.padding = 15.0 + @View private var qrCodeButton: RoundedLabeledButton = { + let view = RoundedLabeledButton() + view.buttonSize = 46.0 + view.padding = 12.0 view.update(image: .icons.qr, text: localized("contact_book.share_bar.buttons.qr")) return view }() - @View private var linkCodeButton: ContactBookShareButton = { - let view = ContactBookShareButton() + @View private var linkCodeButton: RoundedLabeledButton = { + let view = RoundedLabeledButton() view.update(image: .icons.link, text: localized("contact_book.share_bar.buttons.link")) - view.buttonSize = 80.0 - view.padding = 15.0 + view.buttonSize = 46.0 + view.padding = 12.0 return view }() - @View private var bleCodeButton: ContactBookShareButton = { - let view = ContactBookShareButton() + @View private var bleCodeButton: RoundedLabeledButton = { + let view = RoundedLabeledButton() view.update(image: .icons.bluetooth, text: localized("contact_book.share_bar.buttons.ble")) - view.buttonSize = 80.0 - view.padding = 15.0 + view.buttonSize = 46.0 + view.padding = 12.0 return view }() @@ -113,7 +162,16 @@ final class ProfileView: BaseNavigationContentView { didSet { updateYatButton(isOn: isYatButtonOn) } } + var isOutOfSyncLabelVisible = false { + didSet { + guard isOutOfSyncLabelVisible != oldValue else { return } + updateOutOfSyncStatus() + } + } + var onEditButtonTap: (() -> Void)? + var onWalletButtonTap: (() -> Void)? + var onConnectYatButtonTap: (() -> Void)? var onQrCodeButtonTap: (() -> Void)? var onLinkButtonTap: (() -> Void)? var onBleButtonTap: (() -> Void)? @@ -121,6 +179,9 @@ final class ProfileView: BaseNavigationContentView { private var yatButtonOnTintColor: UIColor? private var yatButtonOffTintColor: UIColor? + private var auroraButtonsTopConstraintOnYatLabelHidden: NSLayoutConstraint? + private var auroraButtonsTopConstraintOnYatLabelShown: NSLayoutConstraint? + // MARK: - Initialisers override init() { @@ -148,11 +209,19 @@ final class ProfileView: BaseNavigationContentView { private func setupConstraints() { - [emojiIdView, yatButton, yatSpinnerView, middleLabel, reconnectYatButton, buttonsStackView].forEach(addSubview) - [qrCodeButton, linkCodeButton, bleCodeButton].forEach(buttonsStackView.addArrangedSubview) + [usernameLabel, emojiIdView, yatButton, yatSpinnerView, yatOutOfSyncLabel, shareSectionSeparator, shareSectionTitleLabel, shareSectionDescriptionLabel, auroraButtonsStackView, shareButtonsStackView].forEach(addSubview) + [walletButton, connectYatButton].forEach(auroraButtonsStackView.addArrangedSubview) + [qrCodeButton, linkCodeButton, bleCodeButton].forEach(shareButtonsStackView.addArrangedSubview) + + let auroraButtonsTopConstraintOnYatLabelHidden = auroraButtonsStackView.topAnchor.constraint(equalTo: emojiIdView.bottomAnchor, constant: 20.0) + self.auroraButtonsTopConstraintOnYatLabelHidden = auroraButtonsTopConstraintOnYatLabelHidden + auroraButtonsTopConstraintOnYatLabelShown = auroraButtonsStackView.topAnchor.constraint(equalTo: yatOutOfSyncLabel.bottomAnchor, constant: 20.0) let constraints = [ - emojiIdView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 25.0), + usernameLabel.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 50.0), + usernameLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + usernameLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), + emojiIdView.topAnchor.constraint(equalTo: usernameLabel.bottomAnchor, constant: 20.0), emojiIdView.widthAnchor.constraint(equalToConstant: 185.0), emojiIdView.heightAnchor.constraint(equalToConstant: 38.0), emojiIdView.centerXAnchor.constraint(equalTo: centerXAnchor), @@ -164,13 +233,23 @@ final class ProfileView: BaseNavigationContentView { yatSpinnerView.centerYAnchor.constraint(equalTo: yatButton.centerYAnchor), yatSpinnerView.heightAnchor.constraint(equalToConstant: 28.0), yatSpinnerView.widthAnchor.constraint(equalToConstant: 28.0), - middleLabel.topAnchor.constraint(equalTo: emojiIdView.bottomAnchor, constant: 40.0), - middleLabel.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 40.0), - middleLabel.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -40.0), - reconnectYatButton.topAnchor.constraint(equalTo: middleLabel.bottomAnchor), - reconnectYatButton.centerXAnchor.constraint(equalTo: centerXAnchor), - buttonsStackView.topAnchor.constraint(equalTo: reconnectYatButton.bottomAnchor, constant: 20.0), - buttonsStackView.centerXAnchor.constraint(equalTo: centerXAnchor) + yatOutOfSyncLabel.topAnchor.constraint(equalTo: emojiIdView.bottomAnchor, constant: 20.0), + yatOutOfSyncLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + yatOutOfSyncLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), + auroraButtonsTopConstraintOnYatLabelHidden, + auroraButtonsStackView.centerXAnchor.constraint(equalTo: centerXAnchor), + shareSectionSeparator.topAnchor.constraint(equalTo: auroraButtonsStackView.bottomAnchor, constant: 20.0), + shareSectionSeparator.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + shareSectionSeparator.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), + shareSectionSeparator.heightAnchor.constraint(equalToConstant: 1.0), + shareSectionTitleLabel.topAnchor.constraint(equalTo: shareSectionSeparator.bottomAnchor, constant: 20.0), + shareSectionTitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + shareSectionTitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), + shareSectionDescriptionLabel.topAnchor.constraint(equalTo: shareSectionTitleLabel.bottomAnchor, constant: 20.0), + shareSectionDescriptionLabel.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 25.0), + shareSectionDescriptionLabel.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -25.0), + shareButtonsStackView.topAnchor.constraint(equalTo: shareSectionDescriptionLabel.bottomAnchor, constant: 20.0), + shareButtonsStackView.centerXAnchor.constraint(equalTo: centerXAnchor) ] NSLayoutConstraint.activate(constraints) @@ -178,6 +257,14 @@ final class ProfileView: BaseNavigationContentView { private func setupCallbacks() { + walletButton.onTap = { [weak self] in + self?.onWalletButtonTap?() + } + + connectYatButton.onTap = { [weak self] in + self?.onConnectYatButtonTap?() + } + qrCodeButton.onTap = { [weak self] in self?.onQrCodeButtonTap?() } @@ -197,14 +284,37 @@ final class ProfileView: BaseNavigationContentView { super.update(theme: theme) backgroundColor = theme.backgrounds.secondary - middleLabel.textColor = theme.text.body - reconnectYatButton.setTitleColor(theme.text.links, for: .normal) + usernameLabel.textColor = theme.text.heading + yatOutOfSyncLabel.textColor = theme.text.body + shareSectionSeparator.backgroundColor = theme.neutral.tertiary + shareSectionTitleLabel.textColor = theme.text.heading + shareSectionDescriptionLabel.textColor = theme.text.body yatButtonOnTintColor = theme.icons.active yatButtonOffTintColor = theme.icons.inactive updateYatButton(isOn: isYatButtonOn) } + func update(username: String?) { + usernameLabel.text = username + } + + private func updateOutOfSyncStatus() { + + if isOutOfSyncLabelVisible { + auroraButtonsTopConstraintOnYatLabelHidden?.isActive = false + auroraButtonsTopConstraintOnYatLabelShown?.isActive = true + } else { + auroraButtonsTopConstraintOnYatLabelShown?.isActive = false + auroraButtonsTopConstraintOnYatLabelHidden?.isActive = true + } + + UIView.animate(withDuration: 0.3) { + self.yatOutOfSyncLabel.alpha = self.isOutOfSyncLabelVisible ? 1.0 : 0.0 + self.layoutIfNeeded() + } + } + func update(emojiID: String, hex: String?, copyText: String, tooltopText: String?) { emojiIdView.copyText = copyText emojiIdView.tooltipText = tooltopText diff --git a/MobileWallet/Screens/Profile/ProfileViewController.swift b/MobileWallet/Screens/Profile/ProfileViewController.swift index 23a9e954..823e1f90 100644 --- a/MobileWallet/Screens/Profile/ProfileViewController.swift +++ b/MobileWallet/Screens/Profile/ProfileViewController.swift @@ -74,8 +74,8 @@ final class ProfileViewController: UIViewController { setupBindings() } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) model.updateYatIdData() } @@ -83,21 +83,20 @@ final class ProfileViewController: UIViewController { private func setupBindings() { - model.$emojiData - .compactMap { $0 } + model.$name .receive(on: DispatchQueue.main) - .sink { [weak self] in self?.mainView.update(emojiID: $0.emojiID, hex: $0.hex, copyText: $0.copyText, tooltopText: $0.tooltipText) } + .sink { [weak self] in self?.mainView.update(username: $0) } .store(in: &cancellables) - model.$description + model.$emojiData + .compactMap { $0 } .receive(on: DispatchQueue.main) - .assign(to: \.text, on: mainView.middleLabel) + .sink { [weak self] in self?.mainView.update(emojiID: $0.emojiID, hex: $0.hex, copyText: $0.copyText, tooltopText: $0.tooltipText) } .store(in: &cancellables) - model.$isReconnectButtonVisible - .map { !$0 } + model.$isYatOutOfSync .receive(on: DispatchQueue.main) - .assign(to: \.isHidden, on: mainView.reconnectYatButton) + .assign(to: \.isOutOfSyncLabelVisible, on: mainView) .store(in: &cancellables) model.$errorMessage @@ -127,14 +126,18 @@ final class ProfileViewController: UIViewController { self?.model.toggleVisibleData() } - mainView.reconnectYatButton.onTap = { [weak self] in - self?.model.reconnectYat() - } - mainView.onEditButtonTap = { [weak self] in self?.showEditNameForm() } + mainView.onWalletButtonTap = { [weak self] in + self?.moveToUTXOsWallet() + } + + mainView.onConnectYatButtonTap = { [weak self] in + self?.model.reconnectYat() + } + mainView.onQrCodeButtonTap = { [weak self] in self?.model.generateQrCode() } @@ -173,6 +176,11 @@ final class ProfileViewController: UIViewController { ]) } + private func moveToUTXOsWallet() { + let controller = UTXOsWalletConstructor.buildScene() + navigationController?.pushViewController(controller, animated: true) + } + private func handle(action: ProfileModel.Action) { switch action { diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift index 26a90141..66e87bb8 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift @@ -125,9 +125,7 @@ final class RestoreWalletFromSeedsModel { .filter { $0.state != .editing } .map { $0.title } - Task { - await restoreWallet(seedWords: seedWords) - } + restoreWallet(seedWords: seedWords) } func removeSeedWord(row: Int) { @@ -149,9 +147,9 @@ final class RestoreWalletFromSeedsModel { viewModel.updatedInputText = "" } - private func restoreWallet(seedWords: [String]) async { + private func restoreWallet(seedWords: [String]) { do { - try await Tari.shared.restoreWallet(seedWords: seedWords) + try Tari.shared.restoreWallet(seedWords: seedWords) viewModel.isEmptyWalletCreated = true } catch let error as SeedWords.InternalError { handle(seedWordsError: error) diff --git a/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift b/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift index f8110f23..23c9496b 100644 --- a/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift +++ b/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift @@ -215,13 +215,13 @@ final class AddNoteViewController: DynamicThemeViewController, UIScrollViewDeleg @objc private func moveSendButtonUp(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { - let keyboardHeight = keyboardSize.height + let bottomOffset = -keyboardSize.height - 14.0 sendButtonBottomConstraint.isActive = false showGiphyCarousel() UIView.animate(withDuration: 0.46, delay: 0.008, options: .curveEaseIn, animations: { [weak self] in guard let self = self else { return } - self.sendButtonBottomConstraint = self.sendButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -keyboardHeight) + self.sendButtonBottomConstraint = self.sendButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: bottomOffset) self.sendButtonBottomConstraint.isActive = true self.view.layoutIfNeeded() }) diff --git a/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift b/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift index 28930673..aead5547 100644 --- a/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift +++ b/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift @@ -299,7 +299,7 @@ extension AddRecipientView: UITableViewDelegate { guard let title = viewModels[section].title else { return nil } let headerView = tableView.dequeueReusableHeaderFooterView(type: MenuTableHeaderView.self) - headerView.label.text = title + headerView.title = title return headerView } diff --git a/MobileWallet/Screens/Legacy/AddRecipient/Views/ErrorView.swift b/MobileWallet/Screens/Send/AddRecipient/Views/ErrorView.swift similarity index 100% rename from MobileWallet/Screens/Legacy/AddRecipient/Views/ErrorView.swift rename to MobileWallet/Screens/Send/AddRecipient/Views/ErrorView.swift diff --git a/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsView.swift b/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsView.swift index 6cf85b2a..18aaea92 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsView.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsView.swift @@ -45,7 +45,11 @@ final class BackupWalletSettingsView: BaseNavigationContentView { // MARK: - Subviews - @View private var tableView = BaseMenuTableView() + @View private var tableView: BaseMenuTableView = { + let view = BaseMenuTableView() + view.register(type: SystemMenuTableViewCell.self) + return view + }() // MARK: - Properties diff --git a/MobileWallet/Screens/Settings/Bug Reporting/BugReportingModel.swift b/MobileWallet/Screens/Settings/Bug Reporting/BugReportingModel.swift index 8161bf10..519713dc 100644 --- a/MobileWallet/Screens/Settings/Bug Reporting/BugReportingModel.swift +++ b/MobileWallet/Screens/Settings/Bug Reporting/BugReportingModel.swift @@ -40,9 +40,14 @@ final class BugReportingModel { + enum Action { + case showDataCollectionConsentDialog + case endFlow + } + // MARK: - View Model - @Published private(set) var shouldEndFlow: Bool = false + @Published private(set) var action: Action? @Published private(set) var errorMessage: MessageModel? // MARK: - Properties @@ -52,10 +57,25 @@ final class BugReportingModel { // MARK: - Actions func sendReport(name: String?, email: String?, message: String?) { + + guard AppConfigurator.shared.isCrashLoggerEnabled else { + action = .showDataCollectionConsentDialog + return + } + + performSendReport(name: name, email: email, message: message) + } + + func turnOnDataCollection() { + AppConfigurator.shared.isCrashLoggerEnabled = true + } + + private func performSendReport(name: String?, email: String?, message: String?) { + Task { do { try await bugReportService.send(name: name ?? "", email: email ?? "", message: message ?? "") - shouldEndFlow = true + action = .endFlow } catch { errorMessage = ErrorMessageManager.errorModel(forError: error) } diff --git a/MobileWallet/Screens/Settings/Bug Reporting/BugReportingViewController.swift b/MobileWallet/Screens/Settings/Bug Reporting/BugReportingViewController.swift index a638bd57..692a542b 100644 --- a/MobileWallet/Screens/Settings/Bug Reporting/BugReportingViewController.swift +++ b/MobileWallet/Screens/Settings/Bug Reporting/BugReportingViewController.swift @@ -83,10 +83,10 @@ final class BugReportingViewController: UIViewController { self?.showLogs() } - model.$shouldEndFlow - .filter { $0 } + model.$action + .compactMap { $0 } .receive(on: DispatchQueue.main) - .sink { [weak self] _ in self?.endFlow() } + .sink { [weak self] in self?.handle(action: $0) } .store(in: &cancellables) model.$errorMessage @@ -114,8 +114,38 @@ final class BugReportingViewController: UIViewController { dismiss(animated: true) } + private func handle(action: BugReportingModel.Action) { + + switch action { + case .showDataCollectionConsentDialog: + showDataCollectionConsentPopUp() + case .endFlow: + endFlow() + } + } + private func handle(errorMessage: MessageModel) { PopUpPresenter.show(message: errorMessage) mainView.isProcessing = false } + + private func showDataCollectionConsentPopUp() { + + let model = PopUpDialogModel( + title: localized("bug_reporting.popup.data_collection.label.title"), + message: localized("bug_reporting.popup.data_collection.label.message"), + buttons: [ + PopUpDialogButtonModel(title: localized("bug_reporting.popup.data_collection.button.ok"), type: .normal, callback: { [weak self] in + self?.model.turnOnDataCollection() + self?.sendReport() + }), + PopUpDialogButtonModel(title: localized("bug_reporting.popup.data_collection.button.cancel"), type: .text, callback: { [weak self] in + self?.mainView.isProcessing = false + }) + ], + hapticType: .none + ) + + PopUpPresenter.showPopUp(model: model) + } } diff --git a/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsConstructor.swift b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsConstructor.swift new file mode 100644 index 00000000..64e1d8c7 --- /dev/null +++ b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsConstructor.swift @@ -0,0 +1,47 @@ +// DataCollectionSettingsConstructor.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 22/09/2023 + Using Swift 5.0 + Running on macOS 13.5 + + 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 DataCollectionSettingsConstructor { + + static func buildScene() -> DataCollectionViewController { + let model = DataCollectionSettingsModel() + return DataCollectionViewController(model: model) + } +} diff --git a/MobileWallet/TariLib/Core/App Setup/AppConfigurator.swift b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsModel.swift similarity index 67% rename from MobileWallet/TariLib/Core/App Setup/AppConfigurator.swift rename to MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsModel.swift index 68fcdc46..b08c864e 100644 --- a/MobileWallet/TariLib/Core/App Setup/AppConfigurator.swift +++ b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsModel.swift @@ -1,10 +1,10 @@ -// AppConfigurator.swift +// DataCollectionSettingsModel.swift /* Package MobileWallet - Created by Adrian Truszczynski on 11/10/2022 + Created by Adrian Truszczyński on 22/09/2023 Using Swift 5.0 - Running on macOS 12.6 + Running on macOS 13.5 Copyright 2019 The Tari Project @@ -38,29 +38,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -enum AppConfigurator { +import Combine - static func configure() { - configureLoggers() - configureManagers() - } +final class DataCollectionSettingsModel { + + // MARK: - View Model + + @Published var isDataCollectionTurnedOn = AppConfigurator.shared.isCrashLoggerEnabled + + // MARK: - Properties - private static func configureLoggers() { - switch TariSettings.shared.environment { - case .debug: - Logger.attach(logger: ConsoleLogger()) - case .testflight, .production: - break - } + private var cancellables = Set() - Logger.attach(logger: FileLogger()) - Logger.attach(logger: CrashLogger()) + // MARK: - Initialisers + + init() { + setupCallbacks() } - private static func configureManagers() { - BackupManager.shared.configure() - StatusLoggerManager.shared.configure() - DataFlowManager.shared.configure() - LocalNotificationsManager.shared.configure() + // MARK: - Setups + + private func setupCallbacks() { + + $isDataCollectionTurnedOn + .removeDuplicates() + .sink { AppConfigurator.shared.isCrashLoggerEnabled = $0 } + .store(in: &cancellables) } } diff --git a/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsView.swift b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsView.swift new file mode 100644 index 00000000..415ad4b6 --- /dev/null +++ b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsView.swift @@ -0,0 +1,146 @@ +// DataCollectionSettingsView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 22/09/2023 + Using Swift 5.0 + Running on macOS 13.5 + + 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 DataCollectionSettingsView: BaseNavigationContentView { + + // MARK: - Subviews + + @View private var descriptionLabel: UILabel = { + let view = UILabel() + view.text = localized("data_collection_settings.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("data_collection_settings.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("data_collection_settings.label.consent"), + isArrowVisible: false, + isDestructive: false + ) + cell.dynamicModel = self?.dynamicModel + return cell + } + + dynamicModel.$switchValue + .removeDuplicates() + .sink { [weak self] in self?.onSwitchValueChange?($0) } + .store(in: &cancellables) + } + + private func setupCells() { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([0]) + snapshot.appendItems([0]) + dataSource?.apply(snapshot: 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/Settings/Data Collection Settings/DataCollectionSettingsViewController.swift b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsViewController.swift new file mode 100644 index 00000000..c8811717 --- /dev/null +++ b/MobileWallet/Screens/Settings/Data Collection Settings/DataCollectionSettingsViewController.swift @@ -0,0 +1,88 @@ +// DataCollectionSettingsViewController.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 22/09/2023 + Using Swift 5.0 + Running on macOS 13.5 + + 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 DataCollectionViewController: UIViewController { + + // MARK: - Properties + + private let model: DataCollectionSettingsModel + private let mainView = DataCollectionSettingsView() + private var cancellables = Set() + + // MARK: - Initialisers + + init(model: DataCollectionSettingsModel) { + 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 loadView() { + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + + model.$isDataCollectionTurnedOn + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.mainView.update(switchValue: $0) } + .store(in: &cancellables) + + mainView.onSwitchValueChange = { [weak self] in + self?.model.isDataCollectionTurnedOn = $0 + } + } +} diff --git a/MobileWallet/Screens/Settings/SettingsViewController.swift b/MobileWallet/Screens/Settings/SettingsViewController.swift index 337294dc..7219b1ea 100644 --- a/MobileWallet/Screens/Settings/SettingsViewController.swift +++ b/MobileWallet/Screens/Settings/SettingsViewController.swift @@ -38,7 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import LocalAuthentication import YatLib import Combine @@ -71,7 +70,9 @@ final class SettingsViewController: SettingsParentTableViewController { } private enum SettingsItemTitle: CaseIterable { + case backUpWallet + case dataCollection case about case reportBug @@ -82,8 +83,6 @@ final class SettingsViewController: SettingsParentTableViewController { case disclaimer case blockExplorer - case connectYats - case selectTheme case bluetoothConfiguration case torBridgeConfiguration @@ -94,6 +93,7 @@ final class SettingsViewController: SettingsParentTableViewController { var rawValue: String { switch self { 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") @@ -102,8 +102,6 @@ final class SettingsViewController: SettingsParentTableViewController { case .selectBaseNode: return localized("settings.item.select_base_node") case .deleteWallet: return localized("settings.item.delete_wallet") - case .connectYats: return localized("settings.item.connect_yats") - case .about: return localized("settings.item.about") case .reportBug: return localized("settings.item.report_bug") case .visitTari: return localized("settings.item.visit_tari") @@ -118,7 +116,10 @@ final class SettingsViewController: SettingsParentTableViewController { private let backUpWalletItem = SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsWalletBackupsIcon, title: SettingsItemTitle.backUpWallet.rawValue, disableCellInProgress: false) - private lazy var securitySectionItems: [SystemMenuTableViewCellItem] = [backUpWalletItem] + private lazy var securitySectionItems: [SystemMenuTableViewCellItem] = [ + backUpWalletItem, + SystemMenuTableViewCellItem(icon: .icons.analytics, title: SettingsItemTitle.dataCollection.rawValue) + ] private let advancedSettingsSectionItems: [SystemMenuTableViewCellItem] = [ SystemMenuTableViewCellItem(icon: Theme.shared.images.settingColorThemeIcon, title: SettingsItemTitle.selectTheme.rawValue), @@ -147,10 +148,6 @@ final class SettingsViewController: SettingsParentTableViewController { return items }() - private let yatSectionItems: [SystemMenuTableViewCellItem] = [ - SystemMenuTableViewCellItem(icon: Theme.shared.images.settingsYatIcon, title: SettingsItemTitle.connectYats.rawValue) - ] - private let links: [SettingsItemTitle: URL?] = [ .visitTari: URL(string: TariSettings.shared.tariUrl), .contributeToTariAurora: URL(string: TariSettings.shared.contributeUrl), @@ -191,6 +188,11 @@ final class SettingsViewController: SettingsParentTableViewController { } } + private func onDataCollectionAction() { + let controller = DataCollectionSettingsConstructor.buildScene() + navigationController?.pushViewController(controller, animated: true) + } + private func onAboutAction() { let controller = AboutViewController() navigationController?.pushViewController(controller, animated: true) @@ -212,8 +214,8 @@ final class SettingsViewController: SettingsParentTableViewController { } private func onBridgeConfigurationAction() { - let bridgesConfigurationViewController = BridgesConfigurationViewController() - navigationController?.pushViewController(bridgesConfigurationViewController, animated: true) + let controller = TorBridgesConstructor.buildScene() + navigationController?.pushViewController(controller, animated: true) } private func onSelectNetworkAction() { @@ -291,7 +293,7 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let section = Section(rawValue: section) else { return 0 } switch section { - case .profile: return yatSectionItems.count + 1 + case .profile: return 1 case .security: return securitySectionItems.count case .more: return moreSectionItems.count case .advancedSettings: return advancedSettingsSectionItems.count @@ -318,7 +320,7 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { guard let section = Section(rawValue: indexPath.section) else { return cell } switch section { case .profile: - cell.configure(yatSectionItems[indexPath.row - 1]) + break case .security: cell.configure(securitySectionItems[indexPath.row]) case .more: @@ -345,7 +347,7 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { } let header = MenuTableHeaderView() - header.label.text = SettingsHeaderTitle.allCases[section].rawValue + header.title = SettingsHeaderTitle.allCases[section].rawValue return header } @@ -375,10 +377,16 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { default: break } - case .security: onBackupWalletAction() + case .security: + switch indexPath.row { + case 0: + onBackupWalletAction() + case 1: + onDataCollectionAction() + default: + break + } case .more: - var indexPath = indexPath - indexPath.row -= 1 switch SettingsItemTitle.allCases[indexPath.row + indexPath.section] { case .about: onAboutAction() diff --git a/MobileWallet/Screens/Settings/Views/BaseMenuTableView.swift b/MobileWallet/Screens/Settings/Views/BaseMenuTableView.swift index c0fd1623..e10d17da 100644 --- a/MobileWallet/Screens/Settings/Views/BaseMenuTableView.swift +++ b/MobileWallet/Screens/Settings/Views/BaseMenuTableView.swift @@ -46,7 +46,6 @@ final class BaseMenuTableView: DynamicThemeTableView { super.init(frame: .zero, style: .grouped) showsVerticalScrollIndicator = false rowHeight = UITableView.automaticDimension - register(type: SystemMenuTableViewCell.self) } required init?(coder: NSCoder) { diff --git a/MobileWallet/TariLib/Core/Tor/OnionSettings.swift b/MobileWallet/TariLib/Core/Tor/OnionSettings.swift deleted file mode 100644 index ac67d926..00000000 --- a/MobileWallet/TariLib/Core/Tor/OnionSettings.swift +++ /dev/null @@ -1,117 +0,0 @@ -// OnionSettings.swift - -/* - Package MobileWallet - Created by S.Shovkoplyas on 01.09.2020 - Using Swift 5.0 - Running on macOS 10.15 - - 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 BridgesConfiguration: NSObject, Codable { - - enum CodingKeys: CodingKey { - case bridges, customBridges - } - - var bridgesType: OnionSettings.BridgesType - var customBridges: [String]? - - init(bridges: OnionSettings.BridgesType, customBridges: [String]?) { - self.bridgesType = bridges - self.customBridges = customBridges - } - -// MARK: Codable confirmation - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - bridgesType = OnionSettings.BridgesType(rawValue: try container.decode(Int.self, forKey: .bridges)) ?? .none - customBridges = try container.decode([String]?.self, forKey: .customBridges) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(bridgesType.rawValue, forKey: .bridges) - try container.encode(customBridges, forKey: .customBridges) - } -} - -final class OnionSettings: NSObject { - static let torBridgesLink = URL(string: "https://bridges.torproject.org/bridges")! - - enum BridgesType: Int { - case none - case custom - } - - private enum BridgesConfigurationKey: String { - case backup = "backup_bridges" - case current = "use_bridges" - } - - static let defaultBridgesConfiguration: BridgesConfiguration = BridgesConfiguration(bridges: .none, customBridges: nil) - - class var backupBridgesConfiguration: BridgesConfiguration { - get { - return getBridgesConfiguration(for: .backup) - } - set { - setBridgesConfiguration(newValue, for: .backup) - } - } - - class var currentlyUsedBridgesConfiguration: BridgesConfiguration { - get { - return getBridgesConfiguration(for: .current) - } - set { - setBridgesConfiguration(newValue, for: .current) - } - } - - private class func setBridgesConfiguration(_ configuration: BridgesConfiguration, for key: BridgesConfigurationKey) { - let encoder = JSONEncoder() - if let encoded = try? encoder.encode(configuration) { - UserDefaults.standard.set(encoded, forKey: key.rawValue) - } - } - - private class func getBridgesConfiguration(for key: BridgesConfigurationKey) -> BridgesConfiguration { - let decoder = JSONDecoder() - if let configurationData = UserDefaults.standard.object(forKey: key.rawValue) as? Data, - let decodedConfiguration = try? decoder.decode(BridgesConfiguration.self, from: configurationData) { - return decodedConfiguration - } - return defaultBridgesConfiguration - } -} diff --git a/MobileWallet/TariLib/Core/TorManager.swift b/MobileWallet/TariLib/Core/TorManager.swift deleted file mode 100644 index 5f84c903..00000000 --- a/MobileWallet/TariLib/Core/TorManager.swift +++ /dev/null @@ -1,392 +0,0 @@ -// TorManager.swift - -/* - Package MobileWallet - Created by Adrian Truszczynski on 07/01/2022 - Using Swift 5.0 - Running on macOS 12.1 - - 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 Tor -import Combine -import IPtProxy - -final class TorManager { - - enum ConnectionStatus { - case disconnected - case connecting - case portsOpen - case connected - case disconnecting - } - - enum TorError: Error { - case connectionFailed(error: Error) - case connectionTimeout - case missingCookie(error: Error?) - } - - // MARK: - Constants - - private let controlAddress = "127.0.0.1" - private let controlPort: UInt16 = 39069 - private let dataDirectoryUrl = TariSettings.storageDirectory.appendingPathComponent("tor", isDirectory: true) - private lazy var authDirectoryURL = dataDirectoryUrl.appendingPathComponent("auth", isDirectory: true) - private(set) lazy var controlServerAddress = "/ip4/\(controlAddress)/tcp/\(controlPort)" - - // MARK: - Properties - - @Published private(set) var connectionStatus: ConnectionStatus = .disconnected - @Published private(set) var bootstrapProgress: Int = 0 - @Published private(set) var error: Error? - - private var controller: TorController? - - private var configuration: TorConfiguration? - private var torThread: TorThread? - private var retryAction: DispatchWorkItem? - private var needsReconfiguration = false - - private(set) var usedBridgesConfiguration: BridgesConfiguration = OnionSettings.currentlyUsedBridgesConfiguration - - private var backupBridgesConfiguration: BridgesConfiguration { - get { OnionSettings.backupBridgesConfiguration } - set { OnionSettings.backupBridgesConfiguration = newValue } - } - - private var currentBridgesConfiguration: BridgesConfiguration { - get { OnionSettings.currentlyUsedBridgesConfiguration } - set { OnionSettings.currentlyUsedBridgesConfiguration = newValue } - } - - // MARK: - Initialisers - - init() { - configuration = try? createBaseConfiguration() - } - - // MARK: - Actions - - func update(bridgesConfiguration: BridgesConfiguration) async throws { - updateAndValidate(bridgesConfiguration: bridgesConfiguration) - try await reinitiateConnection() - OnionSettings.backupBridgesConfiguration = bridgesConfiguration - OnionSettings.currentlyUsedBridgesConfiguration = bridgesConfiguration - } - - func reinitiateConnection() async throws { - await stop() - try await start() - } - - func stop() async { - controller?.disconnect() - torThread?.cancel() - connectionStatus = .disconnecting - await waitForTorThreadStop() - connectionStatus = .disconnected - bootstrapProgress = 0 - } - - private func start() async throws { - - connectionStatus = .connecting - controller = TorController(socketHost: controlAddress, port: controlPort) - - if torThread?.isCancelled ?? true { - try configureSession() - } else if needsReconfiguration { - reconfigureSession() - } - - try await Task.sleep(nanoseconds: 1000000000) - try startController() - try await observeAuthentication() - setupRetry() - } - - private func configureSession() throws { - - let configuration = try createBaseConfiguration() - var arguments = configuration.arguments ?? [] - - arguments += bridgesArguments() - - switch Ipv6Tester.ipv6_status() { - case .torIpv6ConnOnly: - arguments += ["--ClientPreferIPv6ORPort", "1"] - if usedBridgesConfiguration.bridgesType != .none { - arguments += ["--ClientUseIPv4", "1"] - } else { - arguments += ["--ClientUseIPv4", "0"] - } - case .torIpv6ConnDual, .torIpv6ConnFalse, .torIpv6ConnUnknown: - arguments += [ - "--ClientPreferIPv6ORPort", "auto", - "--ClientUseIPv4", "1" - ] - } - - configuration.arguments = arguments - - torThread = TorThread(configuration: configuration) - needsReconfiguration = false - torThread?.start() - startIObfs4Proxy() - } - - private func waitForTorThreadStop() async { - - return await withCheckedContinuation { [weak self] continuation in - - guard self?.torThread != nil else { - continuation.resume() - return - } - - DispatchQueue.main.async { [weak self] in - Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { [weak self] in - guard self?.torThread == nil || (self?.torThread?.isFinished == true && self?.torThread?.isExecuting == false) else { return } - $0.invalidate() - self?.torThread = nil - continuation.resume() - } - } - } - } - - private func startController() throws { - - guard let controller = controller else { return } - - if !controller.isConnected { - do { - try controller.connect() - } catch { - throw TorError.connectionFailed(error: error) - } - } - } - - private func setupRetry() { - - let retryAction = DispatchWorkItem { [weak self] in - self?.controller?.setConfForKey("DisableNetwork", withValue: "1") - self?.controller?.setConfForKey("DisableNetwork", withValue: "0") - - self?.error = TorError.connectionTimeout - } - - self.retryAction = retryAction - DispatchQueue.main.asyncAfter(deadline: .now() + 30, execute: retryAction) - } - - private func cancelRetry() { - retryAction?.cancel() - retryAction = nil - } - - private func startIObfs4Proxy() { - IPtProxyStartObfs4Proxy(nil, false, false, nil) - } - - private func updateAndValidate(bridgesConfiguration: BridgesConfiguration) { - - defer { - usedBridgesConfiguration = bridgesConfiguration - } - - let currentConfiguration = self.usedBridgesConfiguration - - guard currentConfiguration.bridgesType == bridgesConfiguration.bridgesType else { - needsReconfiguration = true - return - } - - let currentCustomBridges = currentConfiguration.customBridges - let customBridges = bridgesConfiguration.customBridges - - guard let currentCustomBridges = currentCustomBridges, let customBridges = customBridges else { - needsReconfiguration = (currentCustomBridges == nil && customBridges != nil) || (currentCustomBridges != nil && customBridges == nil) - return - } - - needsReconfiguration = currentCustomBridges != customBridges - } - - private func reconfigureSession() { - - let config = createBridgesConfig() - controller?.resetConf(forKey: "Bridge") - - guard !config.isEmpty else { - controller?.setConfForKey("UseBridges", withValue: "0") - return - } - - controller?.setConfs(config) - controller?.setConfForKey("UseBridges", withValue: "1") - } - - private func bridges() -> [String] { - switch usedBridgesConfiguration.bridgesType { - case .custom: - return usedBridgesConfiguration.customBridges ?? [] - default: - return [] - } - } - - private func bridgesArguments() -> [String] { - - var arguments = bridges().flatMap { ["--Bridge", $0] } - - if !arguments.isEmpty { - arguments += ["--UseBridges", "1"] - } - - return arguments - } - - private func createBridgesConfig() -> [[String: String]] { - bridges().map { ["key": "Bridge", "value": "\"\($0)\""] } - } - - // MARK: - Observers - - private func observeAuthentication() async throws { - let cookie = try await cookie() - guard let isSuccess = try await controller?.authenticate(with: cookie) else { return } - connectionStatus = .portsOpen - observeCircuit() - observeStatusEvents() - } - - private func observeCircuit() { - var observer: Any? - observer = controller?.addObserver { [weak self] isCircuitEstablished in - guard let self = self, isCircuitEstablished else { return } - self.connectionStatus = .connected - self.controller?.removeObserver(observer) - self.cancelRetry() - } - } - - func observeStatusEvents() { - var observer: Any? - observer = controller?.addObserver { [weak self] type, _, action, arguments in - guard type == "STATUS_CLIENT", action == "BOOTSTRAP", let rawProgress = arguments?["PROGRESS"], let progress = Int(rawProgress) else { return false } - self?.bootstrapProgress = progress - guard progress >= 100 else { return true } - self?.controller?.removeObserver(observer) - return true - } - } - - // MARK: - Constructors - - func cookie() async throws -> Data { - try await cookie(retryCount: 0) - } - - private func cookie(retryCount: Int) async throws -> Data { - - let maxRetryCount = 5 - - do { - return try self.fetchCookieData() - } catch { - guard retryCount < maxRetryCount else { throw error } - Logger.log(message: "Waiting for cookies: Retry Count: \(retryCount)", domain: .connection, level: .info) - try await Task.sleep(nanoseconds: 100000000) // 0.1s - return try await cookie(retryCount: retryCount + 1) - } - } - - private func fetchCookieData() throws -> Data { - - guard let fileUrl = configuration?.dataDirectory?.appendingPathComponent("control_auth_cookie") else { throw TorError.missingCookie(error: nil) } - do { - return try Data(contentsOf: fileUrl) - } catch { - throw TorError.missingCookie(error: error) - } - } - - private func createBaseConfiguration() throws -> TorConfiguration { - - try createDataDirectoryInNeeded() - try createAuthDirectoryInNeeded() - - let configuration = TorConfiguration() - - configuration.cookieAuthentication = true - configuration.dataDirectory = dataDirectoryUrl - - #if DEBUG - let log_loc = "notice stdout" - #else - let log_loc = "notice file /dev/null" - #endif - - configuration.arguments = [ - "--allow-missing-torrc", - "--ignore-missing-torrc", - "--clientonly", "1", - "--AvoidDiskWrites", "1", - "--socksport", "39059", - "--controlport", "\(controlAddress):\(controlPort)", - "--log", log_loc, - "--clientuseipv6", "1", - "--ClientTransportPlugin", "obfs4 socks5 127.0.0.1:47351", - "--ClientTransportPlugin", "meek_lite socks5 127.0.0.1:47352", - "--ClientOnionAuthDir", authDirectoryURL.path - ] - - return configuration - } - - // MARK: - Helpers - - private func createDataDirectoryInNeeded() throws { - guard !FileManager.default.fileExists(atPath: dataDirectoryUrl.path) else { return } - try FileManager.default.createDirectory(at: dataDirectoryUrl, withIntermediateDirectories: true) - } - - private func createAuthDirectoryInNeeded() throws { - guard !FileManager.default.fileExists(atPath: authDirectoryURL.path) else { return } - try FileManager.default.createDirectory(at: authDirectoryURL, withIntermediateDirectories: true) - } -} diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactBookShareButton.swift b/MobileWallet/UIElements/Buttons/RoundedLabeledButton.swift similarity index 97% rename from MobileWallet/Screens/Contact Book/List/Views/ContactBookShareButton.swift rename to MobileWallet/UIElements/Buttons/RoundedLabeledButton.swift index ed0295cb..8aa18abf 100644 --- a/MobileWallet/Screens/Contact Book/List/Views/ContactBookShareButton.swift +++ b/MobileWallet/UIElements/Buttons/RoundedLabeledButton.swift @@ -1,4 +1,4 @@ -// ContactBookShareButton.swift +// RoundedLabeledButton.swift /* Package MobileWallet @@ -40,7 +40,7 @@ import TariCommon -final class ContactBookShareButton: DynamicThemeView { +final class RoundedLabeledButton: DynamicThemeView { // MARK: - Subviews @@ -48,6 +48,7 @@ final class ContactBookShareButton: DynamicThemeView { let view = RoundedButton() view.contentVerticalAlignment = .fill view.contentHorizontalAlignment = .fill + view.imageView?.contentMode = .scaleAspectFit return view }() diff --git a/MobileWallet/UIElements/Buttons/SlideView.swift b/MobileWallet/UIElements/Buttons/SlideView.swift index 671f9ab3..7b24bd3b 100644 --- a/MobileWallet/UIElements/Buttons/SlideView.swift +++ b/MobileWallet/UIElements/Buttons/SlideView.swift @@ -52,8 +52,6 @@ enum SlideViewVariation { final class SlideView: DynamicThemeView { static private let thumbnailMargin: CGFloat = 10 private let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) - static private let thumbnailShadowRadius: Float = 0.5 - static private let thumbnailCornerRadius: CGFloat = 0 private let pendingAnimationView = AnimationView() var onSlideToEnd: (() -> Void)? @@ -162,7 +160,6 @@ final class SlideView: DynamicThemeView { private var topSliderConstraint: NSLayoutConstraint? private var topThumbnailViewConstraint: NSLayoutConstraint? private var trailingDraggedViewConstraint: NSLayoutConstraint? - private var xPositionInThumbnailView: CGFloat = 0 private var xEndingPoint: CGFloat { self.view.frame.maxX - thumbnailImageView.bounds.width - thumbnailViewStartingDistance } private var isFinished: Bool = false @@ -303,10 +300,6 @@ final class SlideView: DynamicThemeView { thumbnailImageView.apply(shadow: isEnabled ? theme.shadows.box : .none) } - private func isTapOnThumbnailViewWithPoint(_ point: CGPoint) -> Bool { - return self.thumbnailImageView.frame.contains(point) - } - private func updateThumbnailXPosition(_ x: CGFloat) { leadingThumbnailViewConstraint?.constant = x setNeedsLayout() @@ -358,23 +351,6 @@ final class SlideView: DynamicThemeView { } } // Others - func resetStateWithAnimation(_ animated: Bool) { - let action = { - self.leadingThumbnailViewConstraint?.constant = self.thumbnailViewStartingDistance - self.textLabel.alpha = 1 - self.layoutIfNeeded() - // - self.isFinished = false - } - if animated { - UIView.animate(withDuration: animationVelocity) { - action() - } - } else { - action() - } - } - private func updateVariationStyle() { switch variation { case .loading: diff --git a/MobileWallet/UIElements/Buttons/TextButton.swift b/MobileWallet/UIElements/Buttons/TextButton.swift index 2a6d45b3..21d593b5 100644 --- a/MobileWallet/UIElements/Buttons/TextButton.swift +++ b/MobileWallet/UIElements/Buttons/TextButton.swift @@ -125,10 +125,6 @@ final class TextButton: DynamicThemeBaseButton { imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0) } - override func update(theme: ColorTheme) { - super.update(theme: theme) - } - private func updateTextColor(theme: ColorTheme) { switch variation { case .primary: diff --git a/MobileWallet/Common/Views/GlassButton.swift b/MobileWallet/UIElements/Menu/AccessoryImageMenuCell.swift similarity index 65% rename from MobileWallet/Common/Views/GlassButton.swift rename to MobileWallet/UIElements/Menu/AccessoryImageMenuCell.swift index 8ab52781..379cf14f 100644 --- a/MobileWallet/Common/Views/GlassButton.swift +++ b/MobileWallet/UIElements/Menu/AccessoryImageMenuCell.swift @@ -1,10 +1,10 @@ -// GlassButton.swift +// AccessoryImageMenuCell.swift /* Package MobileWallet - Created by Adrian Truszczyński on 15/06/2023 + Created by Adrian Truszczyński on 29/09/2023 Using Swift 5.0 - Running on macOS 13.4 + Running on macOS 13.5 Copyright 2019 The Tari Project @@ -38,33 +38,28 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit +import TariCommon -final class GlassButton: RoundedButton { +final class AccessoryImageMenuCell: MenuCell { + struct ViewModel: Hashable { + let baseModel: MenuCell.ViewModel + let accessoryImage: UIImage? + } // MARK: - Subviews - private let maskImageView: UIImageView = { + @View private var accessoryImageView: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFit return view }() - // MARK: - Properties - - var maskImage: UIImage? { - didSet { maskImageView.image = maskImage?.invertedMask } - } - - var backgroundAlpha: CGFloat = 0.4 { - didSet { backgroundColor = .static.white?.withAlphaComponent(backgroundAlpha) } - } - // MARK: - Initialisers - override init() { - super.init() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) setupViews() + setupConstraints() } required init?(coder: NSCoder) { @@ -74,14 +69,23 @@ final class GlassButton: RoundedButton { // MARK: - Setups private func setupViews() { - mask = maskImageView - backgroundAlpha = 0.4 + replace(accessoryItem: accessoryImageView) + } + + private func setupConstraints() { + + let constraints = [ + accessoryImageView.widthAnchor.constraint(equalToConstant: 22.0), + accessoryImageView.heightAnchor.constraint(equalToConstant: 22.0) + ] + + NSLayoutConstraint.activate(constraints) } - // MARK: - Layout + // MARK: - Actions - override func layoutSubviews() { - super.layoutSubviews() - maskImageView.frame = bounds + func update(viewModel: ViewModel) { + self.viewModel = viewModel.baseModel + accessoryImageView.image = viewModel.accessoryImage } } diff --git a/MobileWallet/UIElements/Menu/MenuCell.swift b/MobileWallet/UIElements/Menu/MenuCell.swift index 132165f4..801cf4d4 100644 --- a/MobileWallet/UIElements/Menu/MenuCell.swift +++ b/MobileWallet/UIElements/Menu/MenuCell.swift @@ -40,7 +40,7 @@ import TariCommon -final class MenuCell: DynamicThemeCell { +class MenuCell: DynamicThemeCell { struct ViewModel: Hashable, Identifiable { let id: UInt @@ -57,13 +57,19 @@ final class MenuCell: DynamicThemeCell { return view }() - @View private var accessoryItemView: UIImageView = { + @View private var arrowView: UIImageView = { let view = UIImageView() view.image = Theme.shared.images.forwardArrow view.contentMode = .scaleAspectFit return view }() + @View private var accessoryStackView: UIStackView = { + let view = UIStackView() + view.spacing = 8.0 + return view + }() + // MARK: - Properties var viewModel: ViewModel? { @@ -90,13 +96,15 @@ final class MenuCell: DynamicThemeCell { private func setupConstraints() { - [titleLabel, accessoryItemView].forEach(contentView.addSubview) + accessoryStackView.addArrangedSubview(arrowView) + [titleLabel, accessoryStackView].forEach(contentView.addSubview) let constraints = [ titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - accessoryItemView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), - accessoryItemView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + accessoryStackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + accessoryStackView.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 8.0), + accessoryStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), contentView.heightAnchor.constraint(equalToConstant: 63.0) ] @@ -117,20 +125,28 @@ final class MenuCell: DynamicThemeCell { let tintColor = isDestructive ? theme.system.red : theme.text.heading titleLabel.textColor = tintColor - accessoryItemView.tintColor = tintColor + arrowView.tintColor = tintColor } private func update(viewModel: ViewModel?) { + titleLabel.text = viewModel?.title + arrowView.isHidden = !(viewModel?.isArrowVisible ?? false) + updateTintColor(theme: theme) + } - defer { updateTintColor(theme: theme) } + // MARK: - Helpers - guard let viewModel else { - titleLabel.text = nil - accessoryItemView.isHidden = true - return - } + func replace(accessoryItem: UIView) { + removeAccessoryItem() + accessoryStackView.insertArrangedSubview(accessoryItem, at: 0) + } - titleLabel.text = viewModel.title - accessoryItemView.isHidden = !viewModel.isArrowVisible + func removeAccessoryItem() { + accessoryStackView.arrangedSubviews + .filter { $0 != arrowView } + .forEach { + accessoryStackView.removeArrangedSubview($0) + $0.removeFromSuperview() + } } } diff --git a/MobileWallet/UIElements/Menu/MenuTableHeaderView.swift b/MobileWallet/UIElements/Menu/MenuTableHeaderView.swift index 942d3cd5..3cd9e344 100644 --- a/MobileWallet/UIElements/Menu/MenuTableHeaderView.swift +++ b/MobileWallet/UIElements/Menu/MenuTableHeaderView.swift @@ -38,21 +38,27 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import TariCommon final class MenuTableHeaderView: DynamicThemeHeaderFooterView { // MARK: - Subviews - @View private(set) var label: UILabel = { + @View private var label: UILabel = { let view = UILabel() - view.font = Theme.shared.fonts.settingsViewHeader + view.font = .Avenir.heavy.withSize(15.0) return view }() @View private var backgroundContentView = UIView() + // MARK: - Properties + + var title: String? { + get { label.text } + set { label.text = newValue } + } + // MARK: - Initialisers override init(reuseIdentifier: String?) { @@ -75,9 +81,9 @@ final class MenuTableHeaderView: DynamicThemeHeaderFooterView { backgroundContentView.leadingAnchor.constraint(equalTo: leadingAnchor), backgroundContentView.trailingAnchor.constraint(equalTo: trailingAnchor), backgroundContentView.bottomAnchor.constraint(equalTo: bottomAnchor), + label.topAnchor.constraint(equalTo: topAnchor, constant: 20.0), label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), - label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -15.0), - heightAnchor.constraint(equalToConstant: 70.0) + label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10.0) ] NSLayoutConstraint.activate(constraints) diff --git a/MobileWallet/UIElements/Menu/MenuTableView.swift b/MobileWallet/UIElements/Menu/MenuTableView.swift index d299f577..1299475d 100644 --- a/MobileWallet/UIElements/Menu/MenuTableView.swift +++ b/MobileWallet/UIElements/Menu/MenuTableView.swift @@ -122,7 +122,7 @@ extension MenuTableView: UITableViewDelegate { guard let title = viewModel[section].title else { return nil } let headerView = tableView.dequeueReusableHeaderFooterView(type: MenuTableHeaderView.self) - headerView.label.text = title + headerView.title = title return headerView } diff --git a/MobileWallet/UIElements/Menu/SwitchMenuCell.swift b/MobileWallet/UIElements/Menu/SwitchMenuCell.swift new file mode 100644 index 00000000..f9e74dda --- /dev/null +++ b/MobileWallet/UIElements/Menu/SwitchMenuCell.swift @@ -0,0 +1,101 @@ +// SwitchMenuCell.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 25/09/2023 + Using Swift 5.0 + Running on macOS 13.5 + + 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 SwitchMenuCell: MenuCell { + + final class DynamicModel { + @Published var switchValue: Bool = false + } + + // MARK: - Subviews + + @View private var switchView = UISwitch() + + // MARK: - Properties + + weak var dynamicModel: DynamicModel? { + didSet { setupDynamicModel() } + } + + private var cancellables = Set() + + // MARK: - Initialisers + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupViews() { + replace(accessoryItem: switchView) + } + + private func setupCallbacks() { + switchView.addTarget(self, action: #selector(onSwitchValueChangeAction), for: .valueChanged) + } + + private func setupDynamicModel() { + + cancellables.forEach { $0.cancel() } + + guard let dynamicModel else { return } + + dynamicModel.$switchValue + .removeDuplicates() + .sink { [weak self] in self?.switchView.isOn = $0 } + .store(in: &cancellables) + } + + // MARK: - Targets + + @objc private func onSwitchValueChangeAction(switchView: UISwitch) { + dynamicModel?.switchValue = switchView.isOn + } +} diff --git a/MobileWallet/UIElements/Navigation/ContentNavigationViewController.swift b/MobileWallet/UIElements/Navigation/ContentNavigationViewController.swift index 41b8b329..a034c878 100644 --- a/MobileWallet/UIElements/Navigation/ContentNavigationViewController.swift +++ b/MobileWallet/UIElements/Navigation/ContentNavigationViewController.swift @@ -79,10 +79,4 @@ final class ContentNavigationViewController: UIViewController { NSLayoutConstraint.activate(constraints) } - - // MARK: - Autolayout - - override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() - } } diff --git a/MobileWallet/en.lproj/Localizable.strings b/MobileWallet/en.lproj/Localizable.strings index 6a9c3646..e89ed5be 100644 --- a/MobileWallet/en.lproj/Localizable.strings +++ b/MobileWallet/en.lproj/Localizable.strings @@ -80,6 +80,7 @@ "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"; +"transaction.unknown_source" = "Unknown Source"; /* Authentication */ "authentication.fail.title" = "Authentication failed"; @@ -214,13 +215,12 @@ "settings.item.select_theme" = "Select Theme"; "settings.item.bluetooth_settings" = "Bluetooth Settings"; "settings.item.wallet_backups" = "Wallet Backups"; +"settings.item.data_collection" = "Data Collection"; "settings.item.bridge_configuration" = "Bridge Configuration"; "settings.item.select_network" = "Select Network"; "settings.item.select_base_node" = "Select Base Node"; "settings.item.delete_wallet" = "Delete Your Wallet"; -"settings.item.connect_yats" = "Connect Yats"; - "settings.item.contribute_to_tari" = "Contribute to Tari Aurora"; "settings.item.disclaimer" = "Disclaimer"; "settings.item.privacy_policy" = "Privacy Policy"; @@ -381,10 +381,17 @@ "profile_view.title" = "My Profile"; "profile_view.error.qr_code.title" = "Failed to generate QR"; "profile_view.error.qr_code.description.with_param" = "Easily receive %@ from others by sharing your profile via one of the methods below:"; -"profile_view.error.yat_mismatch" = "You've previously connected this Yat.\n Do you want to re-connect it to your wallet?"; -"profile_view.button.recconect_yat" = "Reconnect Yat"; +"profile_view.button.wallet" = "Wallet"; +"profile_view.button.connect_yat" = "Connect Yats"; +"profile_view.button.share.qr_code" = "QR Code"; +"profile_view.button.share.link" = "Link"; +"profile_view.button.share.ble" = "BLE"; "profile_view.form.title" = "Edit Name"; "profile_view.form.text_field.name.placeholder" = "Name"; +"profile_view.label.out_of_sync.part.1" = "Your previously connected Yat has lost sync with the app. Click"; +"profile_view.label.out_of_sync.part.2.bold" = "Connect Yats"; +"profile_view.label.out_of_sync.part.3" = "below if you’d like to reconnect it."; +"profile_view.label.title.share" = "Share Profile"; /* Navigation bar */ "navigation_bar.error.show_emoji.title" = "Public key error"; @@ -540,12 +547,7 @@ "custom_bridges.error.image_decode.title" = "QR code decode error"; "custom_bridges.error.image_decode.description" = "Failed to decode QR Code from image"; - -/* Onion error */ -"Onion_Error.error.missing_cookie_file" = "Tor connection error. Missing cookie file"; -"Onion_Error.error.connectionError" = "Tor connection error. Please, check your network connection"; -"Onion_Error.error.invalid_bridges" = "Invalid Bridges. Returning to previous bridges configuration ..."; -"Onion_Error.error.title.onionError" = "Onion Error"; +"custom_bridges.toast.error.connection_refused" = "Unable to connect with Tor. Tor bridges configuration is now disabled. You can re-enable them from the settings menu."; /* Delete wallet */ "delete_wallet.title"="You're about to lose everything!"; @@ -678,6 +680,10 @@ "bug_reporting.label.footer" = "With this bug report you will send us information about your device model, iOS version and recent logs."; "bug_reporting.button.send" = "Send"; "bug_reporting.button.view_logs" = "View logs"; +"bug_reporting.popup.data_collection.label.title" = "Hang on..."; +"bug_reporting.popup.data_collection.label.message" = "We need your permission to access information about your device model, iOS version and recent logs. This will help us pinpoint and fix any issues you’re experiencing.\n\nSo, can we get your nod to send us this data? By the way, this also allows us to gather some non-identifying info through a third-party service, but you can switch that off in the settings menu whenever you feel like it. Cool? 😎"; +"bug_reporting.popup.data_collection.button.ok" = "Yes, I consent"; +"bug_reporting.popup.data_collection.button.cancel" = "No, I decline"; /* Theme Switcher */ @@ -695,6 +701,12 @@ "bluetooth_settings.table.ble.row.title.ble_foreground" = "Enabled while the App is on the screen"; "bluetooth_settings.table.ble.row.title.ble_always_on" = "Always On"; +/* Data Collection Settings */ + +"data_collection_settings.title" = "Data Collection"; +"data_collection_settings.label.description" = "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?"; +"data_collection_settings.label.consent" = "I consent to data collection"; + /* Errors */ "error.generic.title" = "Error"; @@ -867,4 +879,11 @@ "add_recipient.recent_txs" = "Recent Transactions"; "add_recipient.my_contacts" = "My Contacts"; "add_recipient.error.can_not_send_yourself" = "Sorry, you cannot send %@ to yourself"; -"add_recipient.error.invalid_emoji_id" = "Invalid Emoji ID";" +"add_recipient.error.invalid_emoji_id" = "Invalid Emoji ID"; + +/* Tracking */ + +"tracking.pop_up.consent.title" = "Help us improve"; +"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"; diff --git a/Podfile b/Podfile index a021a162..0b2278ab 100644 --- a/Podfile +++ b/Podfile @@ -4,12 +4,11 @@ use_frameworks! inhibit_all_warnings! target 'MobileWallet' do - pod 'Tor', '~> 407.12.1' - pod 'FloatingPanel', '1.7.5' + pod 'Tor', '408.7.2' pod 'lottie-ios' pod 'SwiftEntryKit', '2.0.0' pod 'ReachabilitySwift' - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.1.0' + pod 'Sentry', '8.14.2' pod 'SwiftKeychainWrapper', '3.4.0' pod 'Giphy', '2.1.22' pod 'IPtProxy', '1.10.1' @@ -21,9 +20,26 @@ target 'MobileWallet' do end post_install do |installer| + installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' end end + + installer.aggregate_targets.each do |target| + target.xcconfigs.each do |variant, xcconfig| + xcconfig_path = target.client_root + target.xcconfig_relative_path(variant) + IO.write(xcconfig_path, IO.read(xcconfig_path).gsub("DT_TOOLCHAIN_DIR", "TOOLCHAIN_DIR")) + end + end + + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + if config.base_configuration_reference.is_a? Xcodeproj::Project::Object::PBXFileReference + xcconfig_path = config.base_configuration_reference.real_path + IO.write(xcconfig_path, IO.read(xcconfig_path).gsub("DT_TOOLCHAIN_DIR", "TOOLCHAIN_DIR")) + end + end + end end \ No newline at end of file diff --git a/Podfile.lock b/Podfile.lock index c230bbce..46b5df9d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,6 +1,5 @@ PODS: - Alamofire (5.4.4) - - FloatingPanel (1.7.5) - Giphy (2.1.22): - libwebp - IPtProxy (1.10.1) @@ -16,50 +15,51 @@ PODS: - lottie-ios (3.2.3) - OpenSSL-Universal (1.1.1900) - ReachabilitySwift (5.0.0) - - Sentry (8.1.0): - - Sentry/Core (= 8.1.0) - - SentryPrivate (= 8.1.0) - - Sentry/Core (8.1.0): - - SentryPrivate (= 8.1.0) - - SentryPrivate (8.1.0) + - Sentry (8.14.2): + - Sentry/Core (= 8.14.2) + - SentryPrivate (= 8.14.2) + - Sentry/Core (8.14.2): + - SentryPrivate (= 8.14.2) + - SentryPrivate (8.14.2) - SwiftEntryKit (2.0.0) - SwiftKeychainWrapper (3.4.0) - SwiftyDropbox (8.2.1): - Alamofire (~> 5.4.3) - TariCommon (0.2.0) - - Tor (407.12.1): - - Tor/Core (= 407.12.1) - - Tor/Core (407.12.1) + - Tor (408.7.2): + - Tor/CTor (= 408.7.2) + - Tor/Core (408.7.2) + - Tor/CTor (408.7.2): + - Tor/Core - YatLib (0.3.3): - TariCommon (~> 0.2.0) - Zip (2.1.2) DEPENDENCIES: - - FloatingPanel (= 1.7.5) - Giphy (= 2.1.22) - IPtProxy (= 1.10.1) - lottie-ios - OpenSSL-Universal - ReachabilitySwift - - Sentry (from `https://github.com/getsentry/sentry-cocoa.git`, tag `8.1.0`) + - Sentry (= 8.14.2) - SwiftEntryKit (= 2.0.0) - SwiftKeychainWrapper (= 3.4.0) - SwiftyDropbox (= 8.2.1) - TariCommon (= 0.2.0) - - Tor (~> 407.12.1) + - Tor (= 408.7.2) - YatLib (= 0.3.3) - Zip (= 2.1.2) SPEC REPOS: trunk: - Alamofire - - FloatingPanel - Giphy - IPtProxy - libwebp - lottie-ios - OpenSSL-Universal - ReachabilitySwift + - Sentry - SentryPrivate - SwiftEntryKit - SwiftKeychainWrapper @@ -69,35 +69,24 @@ SPEC REPOS: - YatLib - Zip -EXTERNAL SOURCES: - Sentry: - :git: https://github.com/getsentry/sentry-cocoa.git - :tag: 8.1.0 - -CHECKOUT OPTIONS: - Sentry: - :git: https://github.com/getsentry/sentry-cocoa.git - :tag: 8.1.0 - SPEC CHECKSUMS: Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9 - FloatingPanel: bb17fd6fc7ae519f877f7bd30f4f6115a78acf07 Giphy: 6757d929878ef45f70ed62a02a2c9a03994e7b6c IPtProxy: 17a32bf135d3824e8cbc3fba0fa5bc404490ac84 libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c lottie-ios: c058aeafa76daa4cf64d773554bccc8385d0150e OpenSSL-Universal: 84efb8a29841f2764ac5403e0c4119a28b713346 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 - Sentry: a73976b9f5a5141187b0c15432425a167c90d80f - SentryPrivate: eb7ce6ea7ccf1bbeda1f3f37fa355668849426ae + Sentry: e0ea366f95ebb68f26d6030d8c22d6b2e6d23dd0 + SentryPrivate: 949a21fa59872427edc73b524c3ec8456761d97f SwiftEntryKit: 61b5fa36f34a97dd8013e48a7345bc4c4720be9a SwiftKeychainWrapper: 6fc49fbf7d4a6b0772917acb0e53a1639f6078d6 SwiftyDropbox: f6c55aae36c4ea944fe6b35f807c19897f78b09b TariCommon: 2c8bb97359f59d6b302d2e96c191ff95931da8ac - Tor: 319ca0f3bc42c387736a2d6dc56de75ed0a30eaf + Tor: 7e7ab1fa8b5140b0b5038cff45b9c9472ad73951 YatLib: f56aa60679b20e989a41b6bc92c0081c013b523a Zip: b3fef584b147b6e582b2256a9815c897d60ddc67 -PODFILE CHECKSUM: 8832ff20a801bc9cce75eabcb6665cce00b686f5 +PODFILE CHECKSUM: 78481b66b4ba45f09ba6fe28f4de98e3ab98cccf -COCOAPODS: 1.12.1 +COCOAPODS: 1.13.0 diff --git a/dependencies.env b/dependencies.env index 683e021d..c7a6981b 100644 --- a/dependencies.env +++ b/dependencies.env @@ -1 +1 @@ -FFI_VERSION="0.50.0-hotfix.1" \ No newline at end of file +FFI_VERSION="0.52.0" \ No newline at end of file diff --git a/generate_screenshots.sh b/generate_screenshots.sh deleted file mode 100644 index a585a0ce..00000000 --- a/generate_screenshots.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -if brew ls --versions imagemagick > /dev/null; then - echo "imagemagick already installed" -else - brew install imagemagick -fi - -fastlane snapshot - -fastlane run frameit -sed -i -e "s/.png/_framed.png/g" ./fastlane/screenshots/screenshots.html \ No newline at end of file diff --git a/update_dependencies.sh b/update_dependencies.sh index c3bcff49..ef2813a7 100755 --- a/update_dependencies.sh +++ b/update_dependencies.sh @@ -2,10 +2,10 @@ FILE=env.json WORKING_DIR=Temp -FRAMEWORK_ZIP_FILE_NAME=libtari_wallet_ffi.ios-xcframework.zip -FRAMEWORK_DIRECTORY=libwallet-ios-xcframework -FRAMEWORK_FILE_NAME=libtari_wallet_ffi_ios.xcframework -PROJECT_FRAMEWORK_DIRECTORY=./MobileWallet/TariLib +FRAMEWORK_ZIP_FILE_NAME=libminotari_wallet_ffi.ios-xcframework.zip +FRAMEWORK_DIRECTORY=libminotari_wallet_ffi-ios-xcframework +FRAMEWORK_FILE_NAME=libminotari_wallet_ffi_ios.xcframework +PROJECT_FRAMEWORK_DIRECTORY=./MobileWallet/Libraries/TariLib if test ! -f "$FILE"; then echo "$FILE does not exist. Creating default."