From ed7f9163b3c8c3825ea0e8b2e686568e7af6e6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Truszczy=C5=84ski?= Date: Thu, 12 Sep 2024 10:02:05 +0200 Subject: [PATCH] Release - 0.27.0 (#1139) - Fixed reported issue. Now, the send button on the contact book screen will again start the transaction flow with the selected contact. - Added screenshot prevention pop-up that informs users that screenshots and video recording are currently disabled. - Switched methods executed in Wallet.destroy() method. Now, the app will log the "Wallet destroyed" message after it calls the wallet_destroy(). - Removed RotaryMenuOverlay and cleaned up code around that feature. Now, user when the user taps on the contact it will be navigated to the contact details. - Updated Tor to v408.12.1 - Updated FFI to v1.4.1-rc.0 - Created AddressView UI component. The new component shows the network, features and spend key parts of the address. - Replaced all occurrences of EmojiIdView with AddressView. - Updated the FFI version to have access to the new TariAddress format - PaymentInfo is now using TariAddressComponent instead of the hex string. - Updated code around PaymentInfo to handle the new data type. - Added uniqueIdentifier getter to TariAddressComponents. The new variable should be used to compare addresses. - Used uniqueIdentifier in the project to compare TariAddress objects. - Updated ContactsManager and all entities dependent on this manager to be able to handle the new Tari address format. - Updated ContactBookCell to match new designs. - Converted code related to deep links to be able to handle the new Tari address format. - Removed AvatarView from most of the screens to match designs - AvatarView is now presenting the first emoji from the spendKey - The AddressView no longer has padding and rounded corners - Created RoundedAddressView. The new view is a replacement for the previous implementation of the AddressView. - Updated AddressPoisoningManager code to be able to handle addresses in the new format. - Fixed reported issues with BLE contact shearing. Now, the user will be able to share his new Tari address using BLE. - Now, partial backups will use the new Tari address format. - The App is now using PaymentID for one-sided payments. - Fixed unit tests. - Push notification manager is now using the spend key to send remote push notifications. - Fixed broken animations on the Splash screen on iOS 15 - Adjusted AddressView sizes for "normal" screen sizes - Reverted recent changes in AddressView to get access to the singleLabel - Added "chain tip" label to the "connection status" pop-up - Replaced full obfuscated address (text) with formatted "new" address. - Updated Yat related features to be able to handle new Tari address format. - The user is no longer able to enter empty name when he is editing contact name (including itself). - Fixed issue with the yat data after connecting the Yat. Now the data related to the Yat are updated after the user return to the app after connecting the Yat. - Fixed reported issue. Now, the App will clean up wallet folder after the recovery failure and before the recovery. - Fixed issue with selecting base node for recovery. Now, when user doesn't specify the base node the recovery will be performed on a random one from the fetched peer list. - Disabled navigation to the settings screen when it's unavailable (e.g. when user still doesn't reach the home screen). - Coinbase transaction titles was changed to "Mining Reward!" for inbound transactions and "You paid the Miner" for outbound transactions. - Fixed reported issue. Now, the app will no longer show onboarding on every start when user recover his wallet from seed words. - Fixed reported issue. Now the PopUpPresenter will be enforce SwiftEntryKit.display() to run on the main thread. --- MobileWallet.xcodeproj/project.pbxproj | 138 ++-- MobileWallet/AppDelegate.swift | 2 +- .../Assets/TxFee.imageset/Contents.json | 23 - .../TxFee.imageset/transaction fee helper.png | Bin 456 -> 0 bytes .../transaction fee helper@2x.png | Bin 845 -> 0 bytes .../transaction fee helper@3x.png | Bin 1312 -> 0 bytes .../General/CellArrow.imageset/CellArrow.pdf | Bin 0 -> 907 bytes .../CellArrow.imageset}/Contents.json | 2 +- .../Info.imageset}/Contents.json | 2 +- .../Icons/General/Info.imageset/Info.pdf | Bin 0 -> 1363 bytes .../General/Profile.imageset/Contents.json | 16 - .../General/Profile.imageset/ico-profile.pdf | Bin 1720 -> 0 bytes .../Icons/General/Send.imageset/Contents.json | 16 - .../Send.imageset/ico-send-receive.pdf | Bin 2957 -> 0 bytes .../Star.imageset}/Contents.json | 0 .../Star.imageset}/ico-star-filled.pdf | Bin .../General/Unlink.imageset/Contents.json | 16 - .../General/Unlink.imageset/ico-unlink.pdf | Bin 6261 -> 0 bytes .../Rotary Menu/Close.imageset/close.pdf | Bin 1896 -> 0 bytes .../Icons/Rotary Menu/Contents.json | 9 - .../Switch Side.imageset/Contents.json | 15 - .../Switch Side.imageset/switch.pdf | Bin 7469 -> 0 bytes .../Icons/Star/Border.imageset/ico-star.pdf | Bin 1619 -> 0 bytes .../Assets.xcassets/Icons/Star/Contents.json | 9 - .../Backup/Manager/BackupFilesManager.swift | 4 +- .../Common/Data Models/PaymentInfo.swift | 2 +- .../DeepLinkDefaultActionsHandler.swift | 7 +- .../Models/ContactListDeeplink.swift | 2 +- .../Common/Extensions/CGPoint+Utils.swift | 52 -- .../ContactsManager+Utils.swift} | 49 +- .../Common/Extensions/Data+Utlis.swift | 9 +- MobileWallet/Common/Extensions/String.swift | 7 +- ...{CGFloat+Utils.swift => UInt8+Utils.swift} | 18 +- .../Formatters/TransactionFormatter.swift | 41 +- .../Managers/AddressPoisoningManager.swift | 30 +- .../Managers/BLE/BLEPeripheralManager.swift | 2 +- .../Common/Managers/DataFlowManager.swift | 3 +- .../Common/Managers/ErrorMessageManager.swift | 14 + .../Common/Managers/MigrationManager.swift | 15 +- .../Managers/WalletTransactionsManager.swift | 9 +- MobileWallet/Common/NotificationManager.swift | 16 +- .../Onboarding/OnboardingPageView.swift | 2 +- .../User Settings/UserSettings.swift | 8 +- .../User Settings/UserSettingsManager.swift | 9 - .../CustomDeeplinkPopUpContentView.swift | 2 +- ...opUpAddressDetailsContentSectionView.swift | 109 +++ .../PopUpAddressDetailsContentView.swift | 192 +++++ .../PopUpAddressViewDoubleLabel.swift | 108 +++ .../Pop-up/Components/PopUpButtonsView.swift | 15 +- .../Components/PopUpQRContentView.swift | 10 +- .../Handlers/ScreenshotPopUpHandler.swift | 111 +++ .../Pop-up/PopUpComponentsFactory.swift | 2 +- .../Pop-up/PopUpPresenter+CommonPopUps.swift | 36 +- .../Common/Pop-up/PopUpPresenter.swift | 8 +- MobileWallet/Common/Pop-up/TariPopUp.swift | 13 +- .../AddressPoisoningDataHandler.swift | 28 +- MobileWallet/Common/Theme.swift | 10 - .../Tools/AddressViewDefaultActions.swift | 86 ++ .../Views/Address View/AddressView.swift | 194 +++++ .../Address View/RoundedAddressView.swift} | 54 +- .../Common/Views/ContactSearchView.swift | 10 +- .../Core/App Setup/AppConfigurator.swift | 1 + .../TariLib/Core/FFI/ByteVector.swift | 4 - .../TariLib/Core/FFI/Keys/PublicKey.swift | 16 +- .../TariLib/Core/FFI/Keys/TariAddress.swift | 126 ++- .../Core/FFI/Keys/TariAddressComponents.swift | 89 +++ .../TariLib/Core/FFI/TariEmojis.swift} | 55 +- .../Libraries/TariLib/Core/FFI/Wallet.swift | 7 +- .../TariLib/Core/FFIWalletManager.swift | 11 +- .../Core/Services/CoreTariService.swift | 1 + .../Core/Services/TariContactsService.swift | 4 +- .../Core/Services/TariRecoveryService.swift | 9 +- .../Services/TariTransactionsService.swift | 4 +- .../Libraries/TariLib/Core/Tari.swift | 5 +- .../Libraries/TariLib/Core/TorManager.swift | 2 +- .../TariLib/Core/WalletCallbacksManager.swift | 9 + .../ConnectionMonitor.swift | 23 +- .../Wrappers/Utils/Loggers/CrashLogger.swift | 1 + .../Wrappers/Utils/Network/TariNetwork.swift | 11 + .../Wallet/WalletSettingsManager.swift | 4 +- .../ScreenRecordingSettingsConstructor.swift | 4 +- ...creenRecordingSettingsViewController.swift | 3 +- .../SelectNetworkViewController.swift | 4 +- .../Screens/AppEntry/Splash/SplashView.swift | 9 +- .../Splash/SplashViewController.swift | 16 +- .../AppEntry/Splash/SplashViewModel.swift | 3 - .../Splash/WalletCreationViewController.swift | 85 +- .../Add Contact/AddContactModel.swift | 42 +- .../Add Contact/AddContactView.swift | 16 - .../AddContactViewController.swift | 4 - .../Contact Details/ContactDetailsModel.swift | 58 +- .../Contact Details/ContactDetailsView.swift | 66 +- .../ContactDetailsViewController.swift | 34 +- .../ContactTransactionListModel.swift | 10 +- .../Legacy/TxTableViewCell.swift | 25 +- .../Legacy/TxTableViewModel.swift | 6 - .../ContactTransactionListPlaceholder.swift | 2 +- .../Link Contacts/LinkContactsModel.swift | 10 +- .../LinkContactsViewController.swift | 18 +- .../ContactBookContactListView.swift | 3 +- .../ContactBookListPlaceholder.swift | 2 +- .../Contact Book/List/ContactBookModel.swift | 95 +-- .../List/ContactBookViewController.swift | 42 - .../List/Rotary Menu/RotaryMenuOverlay.swift | 194 ----- .../Rotary Menu/RotaryMenuOverlayView.swift | 282 ------- .../Rotary Menu/Views/RotaryMenuButton.swift | 170 ---- .../RotaryMenuCircleBackgroundView.swift | 185 ----- .../Views/RotaryMenuOuterCircleView.swift | 123 --- .../Rotary Menu/Views/RotaryMenuView.swift | 194 ----- .../List/Views/ContactBookCell.swift | 72 +- .../Managers/ContactsManager.swift | 54 +- .../Managers/ExternalContactsManager.swift | 7 +- .../Managers/InternalContactsManager.swift | 28 +- .../PopPresenter+ContactBook.swift | 8 +- .../Screens/Home/Home/HomeModel.swift | 6 +- .../Views/PopUpNetworkStatusContentView.swift | 36 +- .../TransactionDetailsModel.swift | 13 +- .../TransactionDetailsView.swift | 3 +- .../TransactionDetailsViewController.swift | 9 +- .../Views/TransactionDetailsContactView.swift | 2 +- .../Views/TransactionDetailsEmojiView.swift | 27 +- .../Views/TransactionDetailsValueView.swift | 4 +- .../TransactionHistoryViewController.swift | 1 - .../Views/TransactionHistoryCell.swift | 13 +- .../PasswordVerificationViewController.swift | 4 +- .../Screens/Profile/ProfileModel.swift | 70 +- .../Screens/Profile/ProfileView.swift | 53 +- .../Profile/ProfileViewController.swift | 34 +- .../QrCode/QRCodePresentationController.swift | 93 --- .../QrCode/QRCodePresentationView.swift | 151 ---- .../RequestTari/RequestTariAmountModel.swift | 4 +- .../RequestTari/RequestTariAmountView.swift | 5 +- .../RequestTariAmountViewController.swift | 23 +- .../QR Code Scanner/QRCodeScannerModel.swift | 15 +- .../RestoreWalletFromSeedsModel.swift | 8 + .../RestoreWalletFromSeedsView.swift | 4 +- ...RestoreWalletFromSeedsViewController.swift | 4 + .../Views/TokenCollectionView.swift | 2 +- ...dWordsRecoveryProgressViewController.swift | 2 + .../AddAmount/AddAmountViewController.swift | 74 +- .../Send/AddNote/AddNoteViewController.swift | 45 +- .../Send/AddRecipient/AddRecipientModel.swift | 81 +- .../Send/AddRecipient/AddRecipientView.swift | 19 +- .../AddRecipientViewController.swift | 10 +- .../SecureBackupViewController.swift | 8 +- .../Seed Words List/SeedWordsListView.swift | 2 +- .../VerifySeedWords/VerifySeedWordsView.swift | 53 +- .../VerifySeedWordsViewController.swift | 3 +- .../Bug Reporting/BugReportingView.swift | 4 +- .../Settings/CustomBridgesHandable.swift | 78 -- .../MoreSettings/Views/AboutViewHeader.swift | 3 +- .../Settings/SettingsViewController.swift | 30 +- .../Settings/Views/SettingsProfileCell.swift | 52 +- .../UIElements/Buttons/ActionButton.swift | 284 +++---- .../UIElements/Buttons/LoadingGIFButton.swift | 2 +- .../Buttons/RoundedLabeledButton.swift | 32 +- .../UIElements/Buttons/SlideView.swift | 10 +- .../UIElements/Buttons/TextButton.swift | 154 ++-- MobileWallet/UIElements/EmojiIdView.swift | 749 ------------------ MobileWallet/UIElements/GradientView.swift | 4 + .../RadialGradientView.swift | 2 +- .../TabBar/MenuTabBarController.swift | 11 +- .../TransactionProgressPresenter.swift | 5 +- MobileWallet/en.lproj/Localizable.strings | 60 +- Podfile | 9 +- Podfile.lock | 34 +- README.md | 1 + UnitTests/DeepLinkFormatterTests.swift | 18 +- UnitTests/NetworkManagerTests.swift | 2 +- dependencies.env | 2 +- 170 files changed, 2390 insertions(+), 3828 deletions(-) delete mode 100644 MobileWallet/Assets.xcassets/Assets/TxFee.imageset/Contents.json delete mode 100644 MobileWallet/Assets.xcassets/Assets/TxFee.imageset/transaction fee helper.png delete mode 100644 MobileWallet/Assets.xcassets/Assets/TxFee.imageset/transaction fee helper@2x.png delete mode 100644 MobileWallet/Assets.xcassets/Assets/TxFee.imageset/transaction fee helper@3x.png create mode 100644 MobileWallet/Assets.xcassets/Icons/General/CellArrow.imageset/CellArrow.pdf rename MobileWallet/Assets.xcassets/Icons/{Star/Border.imageset => General/CellArrow.imageset}/Contents.json (86%) rename MobileWallet/Assets.xcassets/Icons/{Rotary Menu/Close.imageset => General/Info.imageset}/Contents.json (88%) create mode 100644 MobileWallet/Assets.xcassets/Icons/General/Info.imageset/Info.pdf delete mode 100644 MobileWallet/Assets.xcassets/Icons/General/Profile.imageset/Contents.json delete mode 100644 MobileWallet/Assets.xcassets/Icons/General/Profile.imageset/ico-profile.pdf delete mode 100644 MobileWallet/Assets.xcassets/Icons/General/Send.imageset/Contents.json delete mode 100644 MobileWallet/Assets.xcassets/Icons/General/Send.imageset/ico-send-receive.pdf rename MobileWallet/Assets.xcassets/Icons/{Star/Filled.imageset => General/Star.imageset}/Contents.json (100%) rename MobileWallet/Assets.xcassets/Icons/{Star/Filled.imageset => General/Star.imageset}/ico-star-filled.pdf (100%) delete mode 100644 MobileWallet/Assets.xcassets/Icons/General/Unlink.imageset/Contents.json delete mode 100644 MobileWallet/Assets.xcassets/Icons/General/Unlink.imageset/ico-unlink.pdf delete mode 100644 MobileWallet/Assets.xcassets/Icons/Rotary Menu/Close.imageset/close.pdf delete mode 100644 MobileWallet/Assets.xcassets/Icons/Rotary Menu/Contents.json delete mode 100644 MobileWallet/Assets.xcassets/Icons/Rotary Menu/Switch Side.imageset/Contents.json delete mode 100644 MobileWallet/Assets.xcassets/Icons/Rotary Menu/Switch Side.imageset/switch.pdf delete mode 100644 MobileWallet/Assets.xcassets/Icons/Star/Border.imageset/ico-star.pdf delete mode 100644 MobileWallet/Assets.xcassets/Icons/Star/Contents.json delete mode 100644 MobileWallet/Common/Extensions/CGPoint+Utils.swift rename MobileWallet/Common/{Views/RoundedTextView.swift => Extensions/ContactsManager+Utils.swift} (64%) rename MobileWallet/Common/Extensions/{CGFloat+Utils.swift => UInt8+Utils.swift} (84%) create mode 100644 MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressDetailsContentSectionView.swift create mode 100644 MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressDetailsContentView.swift create mode 100644 MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressViewDoubleLabel.swift create mode 100644 MobileWallet/Common/Pop-up/Handlers/ScreenshotPopUpHandler.swift create mode 100644 MobileWallet/Common/Tools/AddressViewDefaultActions.swift create mode 100644 MobileWallet/Common/Views/Address View/AddressView.swift rename MobileWallet/{Screens/Home/Transaction History/Views/TransactionHistoryHeaderView.swift => Common/Views/Address View/RoundedAddressView.swift} (61%) create mode 100644 MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddressComponents.swift rename MobileWallet/{Common/Views/RoundedInputField.swift => Libraries/TariLib/Core/FFI/TariEmojis.swift} (58%) delete mode 100644 MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlay.swift delete mode 100644 MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlayView.swift delete mode 100644 MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuButton.swift delete mode 100644 MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuCircleBackgroundView.swift delete mode 100644 MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuOuterCircleView.swift delete mode 100644 MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuView.swift delete mode 100644 MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationController.swift delete mode 100644 MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationView.swift delete mode 100644 MobileWallet/Screens/Settings/CustomBridgesHandable.swift delete mode 100644 MobileWallet/UIElements/EmojiIdView.swift diff --git a/MobileWallet.xcodeproj/project.pbxproj b/MobileWallet.xcodeproj/project.pbxproj index 979b0d49..3a94be7a 100644 --- a/MobileWallet.xcodeproj/project.pbxproj +++ b/MobileWallet.xcodeproj/project.pbxproj @@ -117,9 +117,7 @@ 3A312FEE28B6316D00A290D3 /* CompletedTransactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A312FED28B6316D00A290D3 /* CompletedTransactions.swift */; }; 3A35412C26A72D9F002AB5A8 /* ViewIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35412B26A72D9F002AB5A8 /* ViewIdentifiable.swift */; }; 3A35413126A72DEE002AB5A8 /* SelectBaseNodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35413026A72DEE002AB5A8 /* SelectBaseNodeCell.swift */; }; - 3A35413626A738D4002AB5A8 /* RoundedInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35413526A738D4002AB5A8 /* RoundedInputField.swift */; }; 3A35413A26A738F5002AB5A8 /* ContentScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35413926A738F5002AB5A8 /* ContentScrollView.swift */; }; - 3A35413E26A73939002AB5A8 /* RoundedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35413D26A73939002AB5A8 /* RoundedTextView.swift */; }; 3A386F3727A7FC6C0027FED4 /* ErrorMessageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A386F3627A7FC6C0027FED4 /* ErrorMessageManager.swift */; }; 3A39AF1C27B1570F00A32F46 /* SettingsViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A39AF1B27B1570F00A32F46 /* SettingsViewFooter.swift */; }; 3A39E57229012C8B000BBEBF /* DropboxBackupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A39E57129012C8B000BBEBF /* DropboxBackupService.swift */; }; @@ -246,8 +244,6 @@ 3A9A08F526E0A83D00D2E75C /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9A08F426E0A83D00D2E75C /* AppRouter.swift */; }; 3AA2717A279028BF0076E51F /* RequestTariAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA27179279028BF0076E51F /* RequestTariAmountViewController.swift */; }; 3AA2DD902796C98A00DC3CF7 /* AmountNumberFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2DD8F2796C98A00DC3CF7 /* AmountNumberFormatterTests.swift */; }; - 3AA2DD942796E8BA00DC3CF7 /* QRCodePresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2DD932796E8BA00DC3CF7 /* QRCodePresentationController.swift */; }; - 3AA2DD962796F72100DC3CF7 /* QRCodePresentationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2DD952796F72100DC3CF7 /* QRCodePresentationView.swift */; }; 3AA2E2212885755A00D30B62 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2E2202885755A00D30B62 /* NetworkMonitor.swift */; }; 3AABFC9B28F5960100D87773 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AABFC9A28F5960100D87773 /* LogFormatter.swift */; }; 3AABFC9D28F59B2200D87773 /* StatusLoggerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AABFC9C28F59B2200D87773 /* StatusLoggerManager.swift */; }; @@ -265,9 +261,7 @@ 3ABF91A7283FFA6E0001C766 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF91A6283FFA6E0001C766 /* AboutView.swift */; }; 3ABF91A9283FFA790001C766 /* AboutModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF91A8283FFA790001C766 /* AboutModel.swift */; }; 3ABF91AC283FFAA50001C766 /* AboutViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF91AB283FFAA50001C766 /* AboutViewCell.swift */; }; - 3AC05F9B26C3F302002742C6 /* TransactionHistoryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC05F9A26C3F302002742C6 /* TransactionHistoryHeaderView.swift */; }; 3AC6F11228E9C6590068E6FF /* WalletCallbacksManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC6F11128E9C6590068E6FF /* WalletCallbacksManager.swift */; }; - 3AC6F11428E9D8840068E6FF /* CustomBridgesHandable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC6F11328E9D8840068E6FF /* CustomBridgesHandable.swift */; }; 3AC877E4287415AD006F327B /* UTXOsWalletPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC877E3287415AD006F327B /* UTXOsWalletPlaceholderView.swift */; }; 3AC877E628741680006F327B /* UTXOsWalletLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC877E528741680006F327B /* UTXOsWalletLoadingView.swift */; }; 3AC8C25D27999CAC00334F26 /* PageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC8C25C27999CAC00334F26 /* PageViewController.swift */; }; @@ -337,6 +331,7 @@ 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 */; }; + 540C3A1F2C748CAE00B0CC97 /* RoundedAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540C3A1E2C748CAE00B0CC97 /* RoundedAddressView.swift */; }; 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 */; }; @@ -354,9 +349,6 @@ 540F023B2A6A7F6400691FF5 /* TransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540F023A2A6A7F6400691FF5 /* TransactionsViewController.swift */; }; 540F023D2A6A7F6C00691FF5 /* TransactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540F023C2A6A7F6C00691FF5 /* TransactionsView.swift */; }; 540F02412A6A7F8000691FF5 /* TransactionsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540F02402A6A7F8000691FF5 /* TransactionsConstructor.swift */; }; - 541029042A402D930095F7CB /* RotaryMenuOuterCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541029032A402D930095F7CB /* RotaryMenuOuterCircleView.swift */; }; - 541029062A406E4E0095F7CB /* CGFloat+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541029052A406E4E0095F7CB /* CGFloat+Utils.swift */; }; - 541029082A406E810095F7CB /* CGPoint+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541029072A406E810095F7CB /* CGPoint+Utils.swift */; }; 54103CDA2A555A1A00C456B4 /* TransactionHistoryConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54103CD92A555A1A00C456B4 /* TransactionHistoryConstructor.swift */; }; 54103CDC2A555A3100C456B4 /* TransactionHistoryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54103CDB2A555A3100C456B4 /* TransactionHistoryModel.swift */; }; 54103CDE2A555A3D00C456B4 /* TransactionHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54103CDD2A555A3D00C456B4 /* TransactionHistoryViewController.swift */; }; @@ -373,15 +365,17 @@ 5422CBF82B88B1CA00428394 /* ScreenRecordingSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5422CBF72B88B1CA00428394 /* ScreenRecordingSettingsViewController.swift */; }; 5422CBFA2B88B5C900428394 /* ScreenRecordingSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5422CBF92B88B5C800428394 /* ScreenRecordingSettingsModel.swift */; }; 5422CBFC2B88C79200428394 /* ScreenRecordingSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5422CBFB2B88C79200428394 /* ScreenRecordingSettingsView.swift */; }; - 542585C32A332F08009D12CD /* RotaryMenuOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542585C22A332F08009D12CD /* RotaryMenuOverlay.swift */; }; - 542585C52A332F32009D12CD /* RotaryMenuOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542585C42A332F32009D12CD /* RotaryMenuOverlayView.swift */; }; - 542585C82A334697009D12CD /* RotaryMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542585C72A334697009D12CD /* RotaryMenuView.swift */; }; - 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 */; }; + 542967062C46A43100FA6584 /* ContactsManager+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542967052C46A43100FA6584 /* ContactsManager+Utils.swift */; }; + 542F22B72C33F69E00F07557 /* PopUpAddressDetailsContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542F22B62C33F69E00F07557 /* PopUpAddressDetailsContentView.swift */; }; + 542F22B92C33F73200F07557 /* PopUpAddressDetailsContentSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542F22B82C33F73200F07557 /* PopUpAddressDetailsContentSectionView.swift */; }; + 542F22BB2C33F78400F07557 /* PopUpAddressViewDoubleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542F22BA2C33F78400F07557 /* PopUpAddressViewDoubleLabel.swift */; }; 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 */; }; + 543537AD2C2E97190005B2B1 /* AddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543537AC2C2E97190005B2B1 /* AddressView.swift */; }; + 54360D162C45555100EC0BBC /* TariEmojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54360D152C45555100EC0BBC /* TariEmojis.swift */; }; 54394EDE29CA133400E7CAEA /* LoadingImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */; }; 543DF19C293F3DA90031EA70 /* TorBridgesFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543DF19B293F3DA90031EA70 /* TorBridgesFooterView.swift */; }; 543F89D02B98811F00B9821C /* ScreenRecordingSettingsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543F89CF2B98811F00B9821C /* ScreenRecordingSettingsConstructor.swift */; }; @@ -433,6 +427,7 @@ 54621F9529E01423000E9659 /* DeepLinkEncoderStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54621F9429E01423000E9659 /* DeepLinkEncoderStorage.swift */; }; 54621F9729E01443000E9659 /* DeepLinkKeyedEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54621F9629E01443000E9659 /* DeepLinkKeyedEncodingContainer.swift */; }; 54621F9929E01465000E9659 /* DeepLinkUnkeyedEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54621F9829E01465000E9659 /* DeepLinkUnkeyedEncodingContainer.swift */; }; + 54624FEF2C3565BF00637930 /* UInt8+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54624FEE2C3565BF00637930 /* UInt8+Utils.swift */; }; 546B03232983B49400DBED8E /* StylizedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546B03222983B49400DBED8E /* StylizedLabel.swift */; }; 546B03262983E92700DBED8E /* ContentNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546B03252983E92700DBED8E /* ContentNavigationViewController.swift */; }; 546B03282983F2F700DBED8E /* OnboardingPagerElementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546B03272983F2F700DBED8E /* OnboardingPagerElementView.swift */; }; @@ -462,6 +457,8 @@ 5494BFB32AF122A600791A52 /* RoundedLabeledButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A9C88929F6A54B009B0653 /* RoundedLabeledButton.swift */; }; 549565E02AA1D21E0092A10F /* Task+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549565DF2AA1D21E0092A10F /* Task+Utils.swift */; }; 5495C4792B7B718E00BB9051 /* SecureWrapperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5495C4782B7B718E00BB9051 /* SecureWrapperView.swift */; }; + 5499CF2B2C3EA31F001C417F /* AddressViewDefaultActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5499CF2A2C3EA31F001C417F /* AddressViewDefaultActions.swift */; }; + 5499CF2D2C3EA7F5001C417F /* TariAddressComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5499CF2C2C3EA7F5001C417F /* TariAddressComponents.swift */; }; 549B033C2B84DA8D0059983C /* SecureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549B033B2B84DA8D0059983C /* SecureViewController.swift */; }; 549E0E0D29754E9C00828743 /* PartialBackupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E0E0C29754E9B00828743 /* PartialBackupModel.swift */; }; 549E1A012A5EB7B00063022C /* QRCodeScannerBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E1A002A5EB7B00063022C /* QRCodeScannerBoxView.swift */; }; @@ -485,7 +482,6 @@ 54B7F4452996583800BB484B /* ContactBookCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7F4442996583800BB484B /* ContactBookCell.swift */; }; 54B854B929BF993600A2367A /* ContactBookListPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B854B829BF993600A2367A /* ContactBookListPlaceholder.swift */; }; 54BB5E052A2F081300E239C7 /* UserProfileDeeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BB5E042A2F081300E239C7 /* UserProfileDeeplink.swift */; }; - 54BBAF032A37238A002BC64B /* RotaryMenuCircleBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BBAF022A37238A002BC64B /* RotaryMenuCircleBackgroundView.swift */; }; 54BFE3F62B99C74800273272 /* PublicKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BFE3F52B99C74800273272 /* PublicKeys.swift */; }; 54C02B912BA075120057301A /* BaseNodeConnectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C02B902BA075120057301A /* BaseNodeConnectionError.swift */; }; 54C36D0B2A5D3E9800BD973A /* QRCodeScannerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C36D0A2A5D3E9800BD973A /* QRCodeScannerModel.swift */; }; @@ -523,6 +519,7 @@ 54E007B62B8DF55600AFCD7C /* SecurityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E007B52B8DF55600AFCD7C /* SecurityManager.swift */; }; 54E1CB1E29C7292700E6777C /* EmojiTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E1CB1D29C7292700E6777C /* EmojiTextField.swift */; }; 54ED0FBE2A5D315400ED1F7E /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ED0FBD2A5D315400ED1F7E /* SearchField.swift */; }; + 54F1FDA62C296CC5007B3CD8 /* ScreenshotPopUpHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F1FDA52C296CC5007B3CD8 /* ScreenshotPopUpHandler.swift */; }; 54F23DE729BBA0C3001E39A2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 54F23DE629BBA0C3001E39A2 /* InfoPlist.strings */; }; 54F2E34329EE6B8A00A7A15A /* ContactTransactionListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F2E34229EE6B8A00A7A15A /* ContactTransactionListViewController.swift */; }; 54F2E34529EE6B9600A7A15A /* ContactTransactionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F2E34429EE6B9600A7A15A /* ContactTransactionListView.swift */; }; @@ -557,7 +554,6 @@ BF191A3223E70D3200D33C85 /* NerdEmojiAnimation.json in Resources */ = {isa = PBXBuildFile; fileRef = BF191A3123E70D3200D33C85 /* NerdEmojiAnimation.json */; }; BF5537B92412B9BB0071328C /* purple_orb.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = BF5537B82412B9BB0071328C /* purple_orb.mp4 */; }; BF8316FD23EF7EAA00235403 /* LAContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8316FC23EF7EAA00235403 /* LAContext.swift */; }; - BFAB5D1123FDEA69009E8563 /* EmojiIdView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAB5D1023FDEA69009E8563 /* EmojiIdView.swift */; }; BFC5532523D9B8E4009130A8 /* UIView+Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC5532423D9B8E4009130A8 /* UIView+Content.swift */; }; BFD758E623E98ABD00B0F1A5 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD758E523E98ABD00B0F1A5 /* ProfileViewController.swift */; }; BFE1FC0023E20E3600BA5EEC /* WalletCreationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE1FBFF23E20E3600BA5EEC /* WalletCreationViewController.swift */; }; @@ -707,9 +703,7 @@ 3A312FED28B6316D00A290D3 /* CompletedTransactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedTransactions.swift; sourceTree = ""; }; 3A35412B26A72D9F002AB5A8 /* ViewIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewIdentifiable.swift; sourceTree = ""; }; 3A35413026A72DEE002AB5A8 /* SelectBaseNodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectBaseNodeCell.swift; sourceTree = ""; }; - 3A35413526A738D4002AB5A8 /* RoundedInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedInputField.swift; sourceTree = ""; }; 3A35413926A738F5002AB5A8 /* ContentScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScrollView.swift; sourceTree = ""; }; - 3A35413D26A73939002AB5A8 /* RoundedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedTextView.swift; sourceTree = ""; }; 3A386F3627A7FC6C0027FED4 /* ErrorMessageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessageManager.swift; sourceTree = ""; }; 3A39AF1B27B1570F00A32F46 /* SettingsViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewFooter.swift; sourceTree = ""; }; 3A39E57129012C8B000BBEBF /* DropboxBackupService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropboxBackupService.swift; sourceTree = ""; }; @@ -836,8 +830,6 @@ 3A9A08F426E0A83D00D2E75C /* AppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = ""; }; 3AA27179279028BF0076E51F /* RequestTariAmountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTariAmountViewController.swift; sourceTree = ""; }; 3AA2DD8F2796C98A00DC3CF7 /* AmountNumberFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountNumberFormatterTests.swift; sourceTree = ""; }; - 3AA2DD932796E8BA00DC3CF7 /* QRCodePresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodePresentationController.swift; sourceTree = ""; }; - 3AA2DD952796F72100DC3CF7 /* QRCodePresentationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodePresentationView.swift; sourceTree = ""; }; 3AA2E2202885755A00D30B62 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; 3AABFC9A28F5960100D87773 /* LogFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogFormatter.swift; sourceTree = ""; }; 3AABFC9C28F59B2200D87773 /* StatusLoggerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusLoggerManager.swift; sourceTree = ""; }; @@ -855,9 +847,7 @@ 3ABF91A6283FFA6E0001C766 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 3ABF91A8283FFA790001C766 /* AboutModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutModel.swift; sourceTree = ""; }; 3ABF91AB283FFAA50001C766 /* AboutViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewCell.swift; sourceTree = ""; }; - 3AC05F9A26C3F302002742C6 /* TransactionHistoryHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryHeaderView.swift; sourceTree = ""; }; 3AC6F11128E9C6590068E6FF /* WalletCallbacksManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletCallbacksManager.swift; sourceTree = ""; }; - 3AC6F11328E9D8840068E6FF /* CustomBridgesHandable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBridgesHandable.swift; sourceTree = ""; }; 3AC877E3287415AD006F327B /* UTXOsWalletPlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletPlaceholderView.swift; sourceTree = ""; }; 3AC877E528741680006F327B /* UTXOsWalletLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletLoadingView.swift; sourceTree = ""; }; 3AC8C25C27999CAC00334F26 /* PageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageViewController.swift; sourceTree = ""; }; @@ -927,6 +917,7 @@ 4C2C95C6237962CB005058AB /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 4CD20A352407967B007B64D8 /* TransportConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportConfig.swift; sourceTree = ""; }; 4CDEC322273A5E3500999DCB /* Balance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Balance.swift; sourceTree = ""; }; + 540C3A1E2C748CAE00B0CC97 /* RoundedAddressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedAddressView.swift; sourceTree = ""; }; 540CB6ED29C1D519003FACEF /* SettingsProfileCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsProfileCell.swift; sourceTree = ""; }; 540CB6F029C1DDCC003FACEF /* AddContactModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactModel.swift; sourceTree = ""; }; 540CB6F229C1DE26003FACEF /* AddContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactViewController.swift; sourceTree = ""; }; @@ -944,9 +935,6 @@ 540F023A2A6A7F6400691FF5 /* TransactionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsViewController.swift; sourceTree = ""; }; 540F023C2A6A7F6C00691FF5 /* TransactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsView.swift; sourceTree = ""; }; 540F02402A6A7F8000691FF5 /* TransactionsConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsConstructor.swift; sourceTree = ""; }; - 541029032A402D930095F7CB /* RotaryMenuOuterCircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuOuterCircleView.swift; sourceTree = ""; }; - 541029052A406E4E0095F7CB /* CGFloat+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Utils.swift"; sourceTree = ""; }; - 541029072A406E810095F7CB /* CGPoint+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGPoint+Utils.swift"; sourceTree = ""; }; 54103CD92A555A1A00C456B4 /* TransactionHistoryConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryConstructor.swift; sourceTree = ""; }; 54103CDB2A555A3100C456B4 /* TransactionHistoryModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryModel.swift; sourceTree = ""; }; 54103CDD2A555A3D00C456B4 /* TransactionHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryViewController.swift; sourceTree = ""; }; @@ -963,15 +951,17 @@ 5422CBF72B88B1CA00428394 /* ScreenRecordingSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecordingSettingsViewController.swift; sourceTree = ""; }; 5422CBF92B88B5C800428394 /* ScreenRecordingSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecordingSettingsModel.swift; sourceTree = ""; }; 5422CBFB2B88C79200428394 /* ScreenRecordingSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecordingSettingsView.swift; sourceTree = ""; }; - 542585C22A332F08009D12CD /* RotaryMenuOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuOverlay.swift; sourceTree = ""; }; - 542585C42A332F32009D12CD /* RotaryMenuOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuOverlayView.swift; sourceTree = ""; }; - 542585C72A334697009D12CD /* RotaryMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuView.swift; sourceTree = ""; }; - 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 = ""; }; + 542967052C46A43100FA6584 /* ContactsManager+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactsManager+Utils.swift"; sourceTree = ""; }; + 542F22B62C33F69E00F07557 /* PopUpAddressDetailsContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpAddressDetailsContentView.swift; sourceTree = ""; }; + 542F22B82C33F73200F07557 /* PopUpAddressDetailsContentSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpAddressDetailsContentSectionView.swift; sourceTree = ""; }; + 542F22BA2C33F78400F07557 /* PopUpAddressViewDoubleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpAddressViewDoubleLabel.swift; 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 = ""; }; + 543537AC2C2E97190005B2B1 /* AddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressView.swift; sourceTree = ""; }; + 54360D152C45555100EC0BBC /* TariEmojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariEmojis.swift; sourceTree = ""; }; 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingImageView.swift; sourceTree = ""; }; 543DF19B293F3DA90031EA70 /* TorBridgesFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBridgesFooterView.swift; sourceTree = ""; }; 543F89CF2B98811F00B9821C /* ScreenRecordingSettingsConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecordingSettingsConstructor.swift; sourceTree = ""; }; @@ -1023,6 +1013,7 @@ 54621F9429E01423000E9659 /* DeepLinkEncoderStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkEncoderStorage.swift; sourceTree = ""; }; 54621F9629E01443000E9659 /* DeepLinkKeyedEncodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkKeyedEncodingContainer.swift; sourceTree = ""; }; 54621F9829E01465000E9659 /* DeepLinkUnkeyedEncodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkUnkeyedEncodingContainer.swift; sourceTree = ""; }; + 54624FEE2C3565BF00637930 /* UInt8+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt8+Utils.swift"; sourceTree = ""; }; 546B03222983B49400DBED8E /* StylizedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StylizedLabel.swift; sourceTree = ""; }; 546B03252983E92700DBED8E /* ContentNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentNavigationViewController.swift; sourceTree = ""; }; 546B03272983F2F700DBED8E /* OnboardingPagerElementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPagerElementView.swift; sourceTree = ""; }; @@ -1051,6 +1042,8 @@ 5494BFB12AF0E9B500791A52 /* TorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorError.swift; sourceTree = ""; }; 549565DF2AA1D21E0092A10F /* Task+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Task+Utils.swift"; sourceTree = ""; }; 5495C4782B7B718E00BB9051 /* SecureWrapperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureWrapperView.swift; sourceTree = ""; }; + 5499CF2A2C3EA31F001C417F /* AddressViewDefaultActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressViewDefaultActions.swift; sourceTree = ""; }; + 5499CF2C2C3EA7F5001C417F /* TariAddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariAddressComponents.swift; sourceTree = ""; }; 549B033B2B84DA8D0059983C /* SecureViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureViewController.swift; sourceTree = ""; }; 549E0E0C29754E9B00828743 /* PartialBackupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialBackupModel.swift; sourceTree = ""; }; 549E1A002A5EB7B00063022C /* QRCodeScannerBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerBoxView.swift; sourceTree = ""; }; @@ -1075,7 +1068,6 @@ 54B7F4442996583800BB484B /* ContactBookCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookCell.swift; sourceTree = ""; }; 54B854B829BF993600A2367A /* ContactBookListPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookListPlaceholder.swift; sourceTree = ""; }; 54BB5E042A2F081300E239C7 /* UserProfileDeeplink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileDeeplink.swift; sourceTree = ""; }; - 54BBAF022A37238A002BC64B /* RotaryMenuCircleBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaryMenuCircleBackgroundView.swift; sourceTree = ""; }; 54BFE3F52B99C74800273272 /* PublicKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKeys.swift; sourceTree = ""; }; 54C02B902BA075120057301A /* BaseNodeConnectionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNodeConnectionError.swift; sourceTree = ""; }; 54C36D0A2A5D3E9800BD973A /* QRCodeScannerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerModel.swift; sourceTree = ""; }; @@ -1113,6 +1105,7 @@ 54E007B52B8DF55600AFCD7C /* SecurityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityManager.swift; sourceTree = ""; }; 54E1CB1D29C7292700E6777C /* EmojiTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiTextField.swift; sourceTree = ""; }; 54ED0FBD2A5D315400ED1F7E /* SearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchField.swift; sourceTree = ""; }; + 54F1FDA52C296CC5007B3CD8 /* ScreenshotPopUpHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotPopUpHandler.swift; sourceTree = ""; }; 54F23DE629BBA0C3001E39A2 /* InfoPlist.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = InfoPlist.strings; sourceTree = ""; }; 54F2E34229EE6B8A00A7A15A /* ContactTransactionListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTransactionListViewController.swift; sourceTree = ""; }; 54F2E34429EE6B9600A7A15A /* ContactTransactionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTransactionListView.swift; sourceTree = ""; }; @@ -1147,7 +1140,6 @@ BF191A3123E70D3200D33C85 /* NerdEmojiAnimation.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = NerdEmojiAnimation.json; sourceTree = ""; }; BF5537B82412B9BB0071328C /* purple_orb.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = purple_orb.mp4; sourceTree = ""; }; BF8316FC23EF7EAA00235403 /* LAContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LAContext.swift; sourceTree = ""; }; - BFAB5D1023FDEA69009E8563 /* EmojiIdView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiIdView.swift; sourceTree = ""; }; BFC5532423D9B8E4009130A8 /* UIView+Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Content.swift"; sourceTree = ""; }; BFD758E523E98ABD00B0F1A5 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; BFE1FBFF23E20E3600BA5EEC /* WalletCreationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletCreationViewController.swift; sourceTree = ""; }; @@ -1218,9 +1210,8 @@ children = ( 376C62AD247574850091BB28 /* Animation+EnumInit.swift */, 3A2B956928648D860085BE21 /* CChar+Helpers.swift */, - 541029052A406E4E0095F7CB /* CGFloat+Utils.swift */, - 541029072A406E810095F7CB /* CGPoint+Utils.swift */, 3A420596279803DF00A8D49C /* CharacterSet+CustomSets.swift */, + 542967052C46A43100FA6584 /* ContactsManager+Utils.swift */, 54AD5F652A420A9F00D223B8 /* Data+Utlis.swift */, 00BBD808237D80C800EBF5E6 /* Date.swift */, 3A685425290AD04600032963 /* DateFormatter+Formats.swift */, @@ -1238,8 +1229,10 @@ 3A8826C427F1B8320037F779 /* UICollectionViewDiffableDataSource+Animation.swift */, 3AECD493284F46FD00D81C80 /* UIColor+Utils.swift */, 37E0B007249B700F00DFE315 /* UIFont+FontStyle.swift */, + 5453E43B2AC9D5CA00C7F40D /* UIImage+Utils.swift */, 37ABB68F24781CE800F08163 /* UILabel.swift */, 54DD2E8529C37A7600C8C0D9 /* UINavigationController+Common.swift */, + 54624FEE2C3565BF00637930 /* UInt8+Utils.swift */, 54DEF9F22987DA8700C4B749 /* UIScreen+Tools.swift */, 37C8BA46248126B4005BBC05 /* UIScrollView+RefreshControl.swift */, 5488068629D4230500C2A0F9 /* UIStackView+Common.swift */, @@ -1251,7 +1244,6 @@ 54A6E9F3297F260800A60853 /* UIViewController+Common.swift */, 004277F623E0628A00AE7BD9 /* UIViewController+Content.swift */, 54621F8629E00E04000E9659 /* URL+Tools.swift */, - 5453E43B2AC9D5CA00C7F40D /* UIImage+Utils.swift */, ); path = Extensions; sourceTree = ""; @@ -1414,7 +1406,6 @@ 00D988682449B04B000FDC9C /* AnimatedRefreshingView.swift */, 3AECD48F284F3BB300D81C80 /* BaseNavigationContentView.swift */, 373CCDBC2490B66E00D0A2C9 /* CircularProgressView.swift */, - BFAB5D1023FDEA69009E8563 /* EmojiIdView.swift */, 3A13260227EDE42A00289C8C /* GradientView.swift */, 375DB1DF246E90D100B2BEF4 /* NavigationBar.swift */, 3A4205A027980FFA00A8D49C /* QRCodeView.swift */, @@ -1551,7 +1542,6 @@ 3708D749247FCF2300807D72 /* SettingsViewController.swift */, A0779C5F2552C19500614EF3 /* AdvancedSettings */, 3708D750247FF7AD00807D72 /* BackUpSettings */, - 3AC6F11328E9D8840068E6FF /* CustomBridgesHandable.swift */, ); path = Settings; sourceTree = ""; @@ -1795,6 +1785,7 @@ 3A8005CA28EAF3A90022A38A /* TransactionValidationData.swift */, 3A8473BE28EC51780015E63A /* TransactionValidationStatus.swift */, 3A4D90A3273A75FC00A23FA8 /* Wallet.swift */, + 54360D152C45555100EC0BBC /* TariEmojis.swift */, ); path = FFI; sourceTree = ""; @@ -1822,6 +1813,7 @@ 3A35413426A738C8002AB5A8 /* Views */ = { isa = PBXGroup; children = ( + 5425EBC52C4953B30074B76D /* Address View */, 549B033A2B84DA790059983C /* Secure View */, 54DF83C22A4C59380040E3F4 /* AmountBadge.swift */, 3AF79D5F2727206200613C24 /* ContactSearchView.swift */, @@ -1830,8 +1822,6 @@ 3AFAEBF126B15A3300B82603 /* KeyboardAvoidingContentView.swift */, 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */, 54DF83C42A4C5A220040E3F4 /* RoundedGlassContentView.swift */, - 3A35413526A738D4002AB5A8 /* RoundedInputField.swift */, - 3A35413D26A73939002AB5A8 /* RoundedTextView.swift */, 3AF79D5D2727201A00613C24 /* ScrollableLabel.swift */, 54ED0FBD2A5D315400ED1F7E /* SearchField.swift */, 5421551329A4AE92000A3F49 /* SearchTextField.swift */, @@ -2037,6 +2027,7 @@ 3A6A03312802DB64000432B4 /* Pop-up */ = { isa = PBXGroup; children = ( + 54F1FDA42C296CBB007B3CD8 /* Handlers */, 3A6A03322802DB98000432B4 /* Components */, 3A6A03392802DCE7000432B4 /* TariPopUp.swift */, 3A6A033B2802DD34000432B4 /* PopUpPresenter.swift */, @@ -2050,6 +2041,7 @@ 3A6A03322802DB98000432B4 /* Components */ = { isa = PBXGroup; children = ( + 542F22B52C33F69300F07557 /* PopUpAddressDetailsContentView */, 3A6A03332802DBB5000432B4 /* PopUpHeaderView.swift */, 3A6A03352802DC2B000432B4 /* PopUpDescriptionContentView.swift */, 3A6A03372802DC85000432B4 /* PopUpButtonsView.swift */, @@ -2106,6 +2098,7 @@ 3A70C4F5292E51CF00212026 /* Tools */ = { isa = PBXGroup; children = ( + 5499CF2A2C3EA31F001C417F /* AddressViewDefaultActions.swift */, 3A70C4F6292E51D800212026 /* VersionValidator.swift */, ); path = Tools; @@ -2268,7 +2261,6 @@ 3AA271782790284E0076E51F /* RequestTari */ = { isa = PBXGroup; children = ( - 3AA2DD922796E89400DC3CF7 /* QrCode */, 3AA27179279028BF0076E51F /* RequestTariAmountViewController.swift */, 3A6CD3B4279194980034DF81 /* RequestTariAmountView.swift */, 3AE9F4E32795A260006101D1 /* RequestTariAmountModel.swift */, @@ -2276,15 +2268,6 @@ path = RequestTari; sourceTree = ""; }; - 3AA2DD922796E89400DC3CF7 /* QrCode */ = { - isa = PBXGroup; - children = ( - 3AA2DD932796E8BA00DC3CF7 /* QRCodePresentationController.swift */, - 3AA2DD952796F72100DC3CF7 /* QRCodePresentationView.swift */, - ); - path = QrCode; - sourceTree = ""; - }; 3AA2E21F2885754500D30B62 /* Connection Monitor */ = { isa = PBXGroup; children = ( @@ -2343,7 +2326,6 @@ 3AC05F9926C3F2F4002742C6 /* Views */ = { isa = PBXGroup; children = ( - 3AC05F9A26C3F302002742C6 /* TransactionHistoryHeaderView.swift */, 54103CE12A5564CA00C456B4 /* TransactionHistoryCell.swift */, ); path = Views; @@ -2481,6 +2463,7 @@ isa = PBXGroup; children = ( 3A890EC829262744000F5DA6 /* TariAddress.swift */, + 5499CF2C2C3EA7F5001C417F /* TariAddressComponents.swift */, 54BFE3F52B99C74800273272 /* PublicKeys.swift */, 001F6CDB238011CA00FA7002 /* PublicKey.swift */, ); @@ -2613,25 +2596,23 @@ path = "Screen Recoding Settings"; sourceTree = ""; }; - 542585C12A332EE7009D12CD /* Rotary Menu */ = { + 5425EBC52C4953B30074B76D /* Address View */ = { isa = PBXGroup; children = ( - 542585C62A33468C009D12CD /* Views */, - 542585C22A332F08009D12CD /* RotaryMenuOverlay.swift */, - 542585C42A332F32009D12CD /* RotaryMenuOverlayView.swift */, + 540C3A1E2C748CAE00B0CC97 /* RoundedAddressView.swift */, + 543537AC2C2E97190005B2B1 /* AddressView.swift */, ); - path = "Rotary Menu"; + path = "Address View"; sourceTree = ""; }; - 542585C62A33468C009D12CD /* Views */ = { + 542F22B52C33F69300F07557 /* PopUpAddressDetailsContentView */ = { isa = PBXGroup; children = ( - 542585C72A334697009D12CD /* RotaryMenuView.swift */, - 542585C92A3346C0009D12CD /* RotaryMenuButton.swift */, - 54BBAF022A37238A002BC64B /* RotaryMenuCircleBackgroundView.swift */, - 541029032A402D930095F7CB /* RotaryMenuOuterCircleView.swift */, + 542F22B62C33F69E00F07557 /* PopUpAddressDetailsContentView.swift */, + 542F22B82C33F73200F07557 /* PopUpAddressDetailsContentSectionView.swift */, + 542F22BA2C33F78400F07557 /* PopUpAddressViewDoubleLabel.swift */, ); - path = Views; + path = PopUpAddressDetailsContentView; sourceTree = ""; }; 5430B97529B73FF600C80AA2 /* Link Contacts */ = { @@ -2757,7 +2738,6 @@ 5460258329A74D3600CF5764 /* List */ = { isa = PBXGroup; children = ( - 542585C12A332EE7009D12CD /* Rotary Menu */, 5421550E29A4AE28000A3F49 /* Contact List */, 54B7F4432996582B00BB484B /* Views */, 54D419B22995092F00D496B4 /* ContactBookModel.swift */, @@ -2979,6 +2959,14 @@ path = Theme; sourceTree = ""; }; + 54F1FDA42C296CBB007B3CD8 /* Handlers */ = { + isa = PBXGroup; + children = ( + 54F1FDA52C296CC5007B3CD8 /* ScreenshotPopUpHandler.swift */, + ); + path = Handlers; + sourceTree = ""; + }; 54F2E34129EE6B6300A7A15A /* Contact Transactions List */ = { isa = PBXGroup; children = ( @@ -3375,7 +3363,6 @@ 540CB6EE29C1D519003FACEF /* SettingsProfileCell.swift in Sources */, 3AE885082837857B0070D1AC /* CurrencyLabelView.swift in Sources */, 54394EDE29CA133400E7CAEA /* LoadingImageView.swift in Sources */, - 541029062A406E4E0095F7CB /* CGFloat+Utils.swift in Sources */, 5460258C29A74DC100CF5764 /* ContactDetailsConstructor.swift in Sources */, 3A90653A29001D830084EE66 /* AppValues.swift in Sources */, 3A7E06DC287831D800D15190 /* PopUpCombineUTXOsConfirmationContentView.swift in Sources */, @@ -3403,7 +3390,6 @@ 00DD160F241CD41D00C955B4 /* BaseNode.swift in Sources */, 3AF97E6527CF767D00FF6A3F /* TransactionsSendDeeplink.swift in Sources */, 54BB5E052A2F081300E239C7 /* UserProfileDeeplink.swift in Sources */, - 3A35413626A738D4002AB5A8 /* RoundedInputField.swift in Sources */, 3A0391E8290BA45C00352D73 /* BugReportingModel.swift in Sources */, 3AD4AB6329014217005D6D37 /* BackupFilesManager.swift in Sources */, 5444C81129F1B05C00BF3875 /* SelectableCell.swift in Sources */, @@ -3411,6 +3397,7 @@ 3A70C4F7292E51D800212026 /* VersionValidator.swift in Sources */, 3AE138D12804A8B300443D34 /* SuccessToast.swift in Sources */, 54C02B912BA075120057301A /* BaseNodeConnectionError.swift in Sources */, + 542F22BB2C33F78400F07557 /* PopUpAddressViewDoubleLabel.swift in Sources */, 5410F5D12951F04B006976DC /* TariWindow.swift in Sources */, 54D1E1A32ABD92290021A365 /* DataCollectionSettingsConstructor.swift in Sources */, 370E887824FEA54100576F61 /* NetworkTools.m in Sources */, @@ -3462,7 +3449,6 @@ 00B4D6F6241B778D00ED8318 /* BackgroundTaskManager.swift in Sources */, 540CEA882A543E7B00BD26C7 /* HomeTransactionsPlaceholderView.swift in Sources */, 549E0E0D29754E9C00828743 /* PartialBackupModel.swift in Sources */, - 541029042A402D930095F7CB /* RotaryMenuOuterCircleView.swift in Sources */, 375AFDCF2475387700C62CA1 /* WebBrowserViewController.swift in Sources */, 3A8473D828EC56EF0015E63A /* TariKeyValueService.swift in Sources */, 3A874038278842F500D80823 /* TorManager.swift in Sources */, @@ -3471,6 +3457,7 @@ 3A4949A5283FD603002768AE /* AboutViewController.swift in Sources */, 5444C80C29F17DB700BF3875 /* BluetoothSettingsConstructor.swift in Sources */, 3A983A6027C67C0500F0AB61 /* VerifySeedWordsConstructor.swift in Sources */, + 540C3A1F2C748CAE00B0CC97 /* RoundedAddressView.swift in Sources */, 0012562F2371D81500A9C067 /* SplashViewController.swift in Sources */, 3A70434128E21D7A00207D6F /* PendingInboundTransaction.swift in Sources */, 5421551229A4AE4B000A3F49 /* ContactBookContactListView.swift in Sources */, @@ -3506,7 +3493,6 @@ BFC5532523D9B8E4009130A8 /* UIView+Content.swift in Sources */, 54C36D0F2A5D3EAC00BD973A /* QRCodeScannerView.swift in Sources */, 3A4205A127980FFA00A8D49C /* QRCodeView.swift in Sources */, - 3AC05F9B26C3F302002742C6 /* TransactionHistoryHeaderView.swift in Sources */, 5477518D2AAB227000B78E1D /* CustomTorBridgesHeaderView.swift in Sources */, 3AFC6E032745051400A20287 /* SettingsHeaderView.swift in Sources */, 3AEDBE5127CE477B006B0166 /* DeepLinkEncoder.swift in Sources */, @@ -3524,8 +3510,10 @@ 0072BF24247E735700BD28FB /* ReminderNotifications.swift in Sources */, 3A8473C628EC55DB0015E63A /* TariContactsService.swift in Sources */, 5453E43C2AC9D5CA00C7F40D /* UIImage+Utils.swift in Sources */, + 542F22B92C33F73200F07557 /* PopUpAddressDetailsContentSectionView.swift in Sources */, 3A2F30B628F594520095E25D /* FileLogger.swift in Sources */, 3A8473D028EC56600015E63A /* TariRecoveryService.swift in Sources */, + 5499CF2B2C3EA31F001C417F /* AddressViewDefaultActions.swift in Sources */, 5421551029A4AE37000A3F49 /* ContactBookContactListViewController.swift in Sources */, 3AABFC9D28F59B2200D87773 /* StatusLoggerManager.swift in Sources */, 54F306112A3B049500B5689D /* UIView+Utils.swift in Sources */, @@ -3579,7 +3567,6 @@ 5453242029A68310009281A7 /* PageToolbarView.swift in Sources */, 545A9D16294F6EEA008D24A6 /* ThemeSettingsView.swift in Sources */, 544692A429B6059C0081085D /* ContactsManager.swift in Sources */, - 542585C52A332F32009D12CD /* RotaryMenuOverlayView.swift in Sources */, 540F02412A6A7F8000691FF5 /* TransactionsConstructor.swift in Sources */, 5444C80829F17DA100BF3875 /* BluetoothSettingsView.swift in Sources */, 54B854B929BF993600A2367A /* ContactBookListPlaceholder.swift in Sources */, @@ -3592,7 +3579,6 @@ 5494BFB02AF0E98E00791A52 /* TorConnectionStatus.swift in Sources */, 3A5A3D5A29016E1300B689C6 /* BackupWalletSettingsView.swift in Sources */, 00A66527243C766D0046E730 /* ConnectionMonitor.swift in Sources */, - 3AC6F11428E9D8840068E6FF /* CustomBridgesHandable.swift in Sources */, 00E4919A2366E08B007B332D /* AppDelegate.swift in Sources */, 3A62674626D76E6B007F9895 /* SelectNetworkViewController.swift in Sources */, 3AEDBE4F27CE46D6006B0166 /* DeepLinkDecoder.swift in Sources */, @@ -3614,26 +3600,25 @@ 549E1A012A5EB7B00063022C /* QRCodeScannerBoxView.swift in Sources */, 3A8473CC28EC562E0015E63A /* TariConnectionService.swift in Sources */, 3A84CDD62846089B005F2F8D /* UTXOsWalletModel.swift in Sources */, + 54F1FDA62C296CC5007B3CD8 /* ScreenshotPopUpHandler.swift in Sources */, 37C8BA4F24813F98005BBC05 /* SettingsParentTableViewController.swift in Sources */, 3A8473C828EC55F50015E63A /* TariValidationService.swift in Sources */, 3AE8850C28378BF20070D1AC /* TariSegmentedControl.swift in Sources */, 54D419B72995094100D496B4 /* ContactBookView.swift in Sources */, 3A87C3B527A94086007A553F /* CoreError.swift in Sources */, - 542585C82A334697009D12CD /* RotaryMenuView.swift in Sources */, 54DF83C32A4C59380040E3F4 /* AmountBadge.swift in Sources */, 3723A7AB24ACD03E003382EB /* PasswordField.swift in Sources */, 3A793BF128031F540094DF23 /* PopUpImageHeaderView.swift in Sources */, 54AA5D5E298126CA0031A396 /* OnboardingPageView.swift in Sources */, 00E4919C2366E08B007B332D /* SceneDelegate.swift in Sources */, + 543537AD2C2E97190005B2B1 /* AddressView.swift in Sources */, 37049270247EA0770034EE5D /* RestoreWalletViewController.swift in Sources */, 3ABBC5E02726D104001BB864 /* YatTransactionConstructor.swift in Sources */, 5482C8D22A714A7300C2C80A /* BaseToolbar.swift in Sources */, 3A7DAB9C28FDC9D8002CC013 /* LogView.swift in Sources */, - 54BBAF032A37238A002BC64B /* RotaryMenuCircleBackgroundView.swift in Sources */, 3A87E57F28409D630099EA0E /* AddAmountSpinnerView.swift in Sources */, 3A01248327B3BD0700A481F4 /* ProgressBar.swift in Sources */, 5430B97929B7401200C80AA2 /* LinkContactsViewController.swift in Sources */, - 3AA2DD942796E8BA00DC3CF7 /* QRCodePresentationController.swift in Sources */, 548C137B29BF64AD00ACDF0C /* UITableView+Common.swift in Sources */, 54909AAF29C05822002D1070 /* ContactType+Data.swift in Sources */, 3A8473CE28EC56480015E63A /* TariUTXOsService.swift in Sources */, @@ -3668,10 +3653,10 @@ 54103CDE2A555A3D00C456B4 /* TransactionHistoryViewController.swift in Sources */, 00B4D6F8241B77A900ED8318 /* ScheduleReminderNotificationsOperation.swift in Sources */, 3A6A03342802DBB5000432B4 /* PopUpHeaderView.swift in Sources */, + 54624FEF2C3565BF00637930 /* UInt8+Utils.swift in Sources */, 54D419B92995094B00D496B4 /* ContactBookConstructor.swift in Sources */, 37D3D4ED251C970F00D24149 /* TxGifManager.swift in Sources */, 3A62674A26D76EC4007F9895 /* SelectNetworkModel.swift in Sources */, - 3A35413E26A73939002AB5A8 /* RoundedTextView.swift in Sources */, 5453241A29A60C6E009281A7 /* TariPagerViewController.swift in Sources */, 3ACDA85A2721578700F08C70 /* YatTransactionViewController.swift in Sources */, 3ABC7E9328FE864100EAC852 /* PopUpButtonsTableView.swift in Sources */, @@ -3744,6 +3729,7 @@ 543F89D02B98811F00B9821C /* ScreenRecordingSettingsConstructor.swift in Sources */, 3A8473D628EC56C90015E63A /* TariBalanceService.swift in Sources */, 3A6F3FF8283BF980005D1793 /* TariFeePerGramStats.swift in Sources */, + 54360D162C45555100EC0BBC /* TariEmojis.swift in Sources */, 3A5A3D5829016DE300B689C6 /* BackupWalletSettingsViewController.swift in Sources */, 3A94DB21283E7CEF00B0A740 /* TransactionFeesManager.swift in Sources */, 54D1E1A52ABD92380021A365 /* DataCollectionSettingsModel.swift in Sources */, @@ -3777,7 +3763,6 @@ 5455969B29B0A6B500D6719E /* InternalContactsManager.swift in Sources */, 49996E1923F164BA002B6696 /* AnimatedBalanceLabel.swift in Sources */, 54A6E9F0297F1F9900A60853 /* StagedWalletSecurityManager.swift in Sources */, - 541029082A406E810095F7CB /* CGPoint+Utils.swift in Sources */, 3A7DAB9328FDB18A002CC013 /* LogsListModel.swift in Sources */, 3AEE7AA3286599E6000F84ED /* UTXOsWalletTopBar.swift in Sources */, 3AABFC9B28F5960100D87773 /* LogFormatter.swift in Sources */, @@ -3788,6 +3773,7 @@ 3A42059A279804B300A8D49C /* QRCodeFactory.swift in Sources */, 3ACFDD1D26E8C1A900C5E1EA /* AppInfo.swift in Sources */, 540F023B2A6A7F6400691FF5 /* TransactionsViewController.swift in Sources */, + 5499CF2D2C3EA7F5001C417F /* TariAddressComponents.swift in Sources */, 3A983A5C27C62A5700F0AB61 /* VerifySeedWordsView.swift in Sources */, 5491696E2940F78000783E54 /* LogFilesManager.swift in Sources */, 3AA2717A279028BF0076E51F /* RequestTariAmountViewController.swift in Sources */, @@ -3801,7 +3787,6 @@ 3A70434828E21F5600207D6F /* Transaction.swift in Sources */, 54A9C88829F6A06F009B0653 /* DataFlowManager.swift in Sources */, 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 */, @@ -3812,6 +3797,7 @@ 3A8005C628EAF0A50022A38A /* TransactionSendResult.swift in Sources */, 3A3E8BF32924FB4C00490E57 /* MigrationManager.swift in Sources */, 3A84CDD22846087D005F2F8D /* UTXOsWalletViewController.swift in Sources */, + 542967062C46A43100FA6584 /* ContactsManager+Utils.swift in Sources */, 3A42059D279804F300A8D49C /* AmountNumberFormatter.swift in Sources */, 3A7DAB8F28FDAF9A002CC013 /* LogsListViewController.swift in Sources */, 3A5A3D55290152E300B689C6 /* BackupManager.swift in Sources */, @@ -3831,10 +3817,10 @@ 37ABB69024781CE800F08163 /* UILabel.swift in Sources */, 54E1CB1E29C7292700E6777C /* EmojiTextField.swift in Sources */, 54DD2E8629C37A7600C8C0D9 /* UINavigationController+Common.swift in Sources */, - 3AA2DD962796F72100DC3CF7 /* QRCodePresentationView.swift in Sources */, 5444C80A29F17DAD00BF3875 /* BluetoothSettingsModel.swift in Sources */, 3A8826C527F1B8320037F779 /* UICollectionViewDiffableDataSource+Animation.swift in Sources */, 546B03262983E92700DBED8E /* ContentNavigationViewController.swift in Sources */, + 542F22B72C33F69E00F07557 /* PopUpAddressDetailsContentView.swift in Sources */, 3ACFA42B278EC9F200EBED98 /* ProfileModel.swift in Sources */, 3A6A03382802DC85000432B4 /* PopUpButtonsView.swift in Sources */, 54F2E34729EE6BA100A7A15A /* ContactTransactionListModel.swift in Sources */, @@ -3845,13 +3831,11 @@ 3ACDA70526B031A400F138B8 /* SeedWordsRecoveryProgressViewController.swift in Sources */, 545A9D24294F9E77008D24A6 /* UserSettingsManager.swift in Sources */, 546CE7F429AF514A00264699 /* FormOverlay.swift in Sources */, - 542585C32A332F08009D12CD /* RotaryMenuOverlay.swift in Sources */, 549B033C2B84DA8D0059983C /* SecureViewController.swift in Sources */, 3A3D70BB291A637100A8CD7A /* BaseMenuTableView.swift in Sources */, 54FCC3712AA5FF1100CA0025 /* CustomTorBridgesView.swift in Sources */, 3A21FC5128FFD7B8004B09A0 /* PopUpTableView.swift in Sources */, 544D9D4C296D9B2000D8ECEF /* UnblindedOutputs.swift in Sources */, - BFAB5D1123FDEA69009E8563 /* EmojiIdView.swift in Sources */, A01F54A8255EAB7E00F49AFA /* Localization.swift in Sources */, 540F02362A6A67FD00691FF5 /* AddRecipientConstructor.swift in Sources */, 37AFE265245193CA006EA270 /* AlwaysPoppableNavigationController.swift in Sources */, @@ -4077,7 +4061,7 @@ "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = 0.26.0; + MARKETING_VERSION = 0.27.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4110,7 +4094,7 @@ "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = 0.26.0; + MARKETING_VERSION = 0.27.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/MobileWallet/AppDelegate.swift b/MobileWallet/AppDelegate.swift index 4f1d4dd5..0381b972 100644 --- a/MobileWallet/AppDelegate.swift +++ b/MobileWallet/AppDelegate.swift @@ -99,7 +99,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { let hexString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() - Logger.log(message: "Registed for push notifications with token \(hexString).", domain: .general, level: .info) + Logger.log(message: "Registered for push notifications with token \(hexString).", domain: .general, level: .info) NotificationManager.shared.registerDeviceToken(deviceToken) } diff --git a/MobileWallet/Assets.xcassets/Assets/TxFee.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets/TxFee.imageset/Contents.json deleted file mode 100644 index 46ef9520..00000000 --- a/MobileWallet/Assets.xcassets/Assets/TxFee.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "transaction fee helper.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "transaction fee helper@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "transaction fee helper@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/MobileWallet/Assets.xcassets/Assets/TxFee.imageset/transaction fee helper.png b/MobileWallet/Assets.xcassets/Assets/TxFee.imageset/transaction fee helper.png deleted file mode 100644 index 4a355cdf3f838335038ee0453bff3a9c2241ead1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 456 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1&TfmH9gB0%lVEGHE%*fNlF+@Xj z?xekbhYdtpH$FD>;N%uI2-vt_pQGB5DGO`a=QVaR9^omzDIs~|mSXVHhEHs3QeQAT zC>f@lzw|y?e#9nV=dEyvYIz`EqY<;Imv$d zx|j*6H$yL*tYYqgO%7O-iNsydbQ2jGSAsB zzn{ULw)CbT$2Mio)FYoxZtlWA^HA`WK%Rf1l0J bWA@Yj^ux)wJ_la@3JOI}S3j3^P6Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR919H0XL1ONa40RR918~^|S0A#)Nq5uE_w@E}nR7efImOY3QK@`Vt*Ogo# zXvkGwY%IiUl@sm?krW}4FNCOtjjcsmyDKcTuoS^k5WzwXo=FS@D>;bQsI3vfDvCl9 z1)GqN_;Bo3zp;?yog{nop zW83z2I-Pzm{fmmVXs}YLTtfU7m{W^z+E^3wtz0hmOzWEnXByV)^%K2b?=i@Y3AA}* zHxvq8O{G$w{gf%ga=CmSP>J7)AN-H)Huyq5pMS0MCJe=lC3(d%9@as7jnV3(mDEk` zv6w6)acRHIL|18*l(uW-8LDUYzsu-~`uBlnrCkS5l_xsyS@QfpG z5dXleHksTIGsNSJKgYl#+w0kE_AO&~Obo@#1}Lwqz=CK*oza_c&O%s6peV#>H2N`6 z0DyQo)H371g7_l=LGrL<=st>Ww|mm-TpYwDL-$3z7&kqN*?pGt9(>WbJ&l;|5ijo? zP7lxTVR;{BGMS>7RjbtpATP{EPmGLg$nuFXF5vr-NTf(T!t5@l*G$Q9zDkBIQ-Z?% zNF)-!B*rZ8DEPEn4z4>86TX4DO|VBf8ZQ(eMtd&SKOkN_d)w$up^;^0f9LlNvqM^c zU?^Vh=ZW+60kr8?y~^h%C+cMS64P{`(;q++-kQu-?j~ChpPCTC9&0oj;ZCQs3}?-R zGxl)nF@Y@K5F33lFzsL#1}{I8vP*WG0`$(I+(Br|wIHxy76W8z)>hiCu4kw&JOyUo z4|FsHD6N8ineq%pL+&2TK1pblX@GBAv_wgFKP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91Dxd=Z1ONa40RR91DgXcg0Mr_#3jhEFib+I4R9FesnN4U^R}{yc$>f9L zqAnsN3tJPYXsFUf+*ouWrO6~2sa=R52oa&Hq8qV`gsOCro!Pnw2tlNa7AN0vRj{iT zYAaD(BtgLlZI^*0CNt^p|9mIBd3iIPcV{xu3oqw>oOAB)op- zpkrfW2k7J$(Yb(3#smzj=ZW|zl>>c!eJ{g}QRV*yotv9$n4h1&iQ_9kHbyydt;6Rk zK8M@e+Xp*3I^LA>QxbG?a>79L}cG>3@p(DhfI^HFb1jVE!WrApNN9p2I_zA-)a@f@JlPQ z41><_FesS4nmGQLtjXsNL#nI*i9syE_5#p_g#|4?J34WKofpfCK`TwLee2Wv>*RBk znt}?umDkcm--m3S4MtSad?z3k9G~Tu1`yHrc22hr!#A2u4@cWhFpfvZSQDy1M2<29GH}W>SnY_J`^8n^1c>#iY-6 z)!=l%h@F63BWhnqKyLZc*4Wb0@+}p=Z&k+2cx(xOasM-(?UwfKOeS;HN=(Q!k-I_p zLtfw>C#3IrnN79x3C|Oqd!`#l^2Ls&*d{Y^jDot1!~0g|g%7ct{Y=?g|ML0#;Q09X zbIKkZA)gh20y}i8;O^5Pj%R9){BoV1PC{TBSyo~h1 zoy}&u=|@YTQxc;sPfiY^jDWijWIr;mc4qrY#E(36D!Zm64wqbw!*=+3($>~?F#!1m z6K)Eog!aRB+VCcNz2o- zKt)AaqSwIgWD(p~k+Xm?exD|EI^h#V{-6rz?rN4D@ThliX8Blvh^d|dh2 zyZ>wm!Yc2dw))9xFOPeWS;S^%|9wLT+me6JuN6Xf1KKhp{~<>GGiGzP)EGpkWcm*P WG1V2r%v9e10000045s9-#-9xI>p%xSiY4Pv*>pG6@Ai<592G#+39*x<=QyMDBc7iL&Cv`5fsPccvwLArtMVw}= x0J*&T1<5fK{K!T)Ot+(26g}d+L^*IJu)a=z8L_YaOvK;I`dFTEr~!5FAK2q*+Jp}3?dH8Gc~f^q7U(|K171lqn=cCDHCs!DI$ z@o0x6UK^*7kR_MBRtTLxZ=Yvm_Lgna$tR!h*iQa^o_~M69RK}&HFxZ5@4tI~{o|&; zw=|x9eDhQ2ol}>wU(APdyJMbI+*+_qclx_&iXv}kexLsMX8-Zc-&M}8{hnwvZ?4JI zLn(W*7v1a0QTKMvHJtI{h8}n13#GZs_iQL&e%z40Lrm;ZWPiF8i{-Z9`)b?|ZL%h} zG$&WzdDMLC>fYsRmzA68dz0Zqce|QW*zYrf38!-;G|Cm2-6>pa1TqG5TwTWiv7=x$_(L zhJH6vJhZoa;|32lRyN&}ajm!677IShKcVTJUctp-@@VJ0D!v(KSm&}TcsSJRifk6Y z_r)v0jkjb$WX0o|Z`h`-H7U`Kn7g_57yqUu8#fr={ve|2x<}!7+H{FGW|F(?v!vhf zY~8TqX2R!R#gEkLwk_H*^Z482?Qbi8|GICl@v8Ieny>XgoR8kQ7btXCse~uPU*%G_ zp1{*GVKw)tDVrEW-_*VD>yq5Y;Te7D-WJpL-j%DJ{HN>)bDw)d|F}>!+n)P|PMPMi z%R;h>gMGX+uX0qW#DvOR?MdUkQnmKki6xWwxuvqa@Q|=i*Gc>ORWQ|}@W|YW+yRZ& zcdRCaDK2@k?CO$5x(%UACcXYTr}v_;&aQ7+tkd0_=E^e)>~lOlJ#bct?6S6_liBuq zh5hcxx-!vtg66{6eNtW;Y{7Sm*UkEUVyTzn8%7E7P{+j+V8r4`yGn+{*Uej~`@~JF0PKnM$SrI$WjlGhxnoau`#0y2myX8!Kb(-;7 z0OKavYxiVg%rrioQ_>JrHD*~*9Jj|j|5=vL7l{}4JP~E_pbP*_!LY0VO52buVrFRy z&mbmnQDDZ=cg)MnFDX`tj)i86kjjEo1^s};bXY#|O-;#6bjq(#h*mJrGXMhw!^i|C zgkZr;3Q8@`FD*(=Emi3KI-SECPxtm_mh+ax+K>p3C*U zGgE+0RRH-QNC9S#bABmMuOWu}iy^rh78qtICV@Oy47U*EFo?&U6H5|v^3zeZRurYC zaTzF>b3wchA{ER`O^rD9+DK)l|^POwoh{ihfXjeu)Ce!{7kc56-Mg1-c$w V#3dG$fW2dAXky5vs_N?R1^{03>~a7A literal 0 HcmV?d00001 diff --git a/MobileWallet/Assets.xcassets/Icons/General/Profile.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/General/Profile.imageset/Contents.json deleted file mode 100644 index 4e2cdcb4..00000000 --- a/MobileWallet/Assets.xcassets/Icons/General/Profile.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "ico-profile.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/MobileWallet/Assets.xcassets/Icons/General/Profile.imageset/ico-profile.pdf b/MobileWallet/Assets.xcassets/Icons/General/Profile.imageset/ico-profile.pdf deleted file mode 100644 index f8fea438ab523ba90ad3533e414dc31090b0aba3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1720 zcma)-O>fgc5QgvjE9O$EMe2HIzgMa%(Uc+th>~)vI0VOSgAyBJ7ZrXzv#~RFD2MJL zT0fqB=W9JVy1u+RMHz%ZP|$q)E&$HX;QU-v^)f#SJ>|uRs=n_Yz!02gtG3%#i;XBQ zt6!_Kn!mk(+0FE+Y{gH3QtcL~{pOT+{4PkVj0zr5OEfm>0Oe*p%67bI%UU?;l6Ou4 zN|Tbo2pbV(LWdSdmSjlQ6ERYU7|DvsPQW?~rC_!bP}Tt}?Or)+f&DQlMHUUorCu~! z?Siprdx5OPWa5t*N5x8<4f|tK4mm3|R1+QL9HoyTr{C9=7^1{v5@n{PQ_iO_LA@+` z%6hcR8d1jxEH=CgYRH>>j;Iv9aMTmXZDlbg6Q9gDG?wD{d-G|QrMP<~YVrA{7s^N< zw5G0W+MxFC1@{T14c^D0VLDo>t(FQ?)Cg93R3O|rD;X0`11Ql1;zE^z8b*CYPnimG*56l&K$0k zrY80CXx#0U=2hD~Zp*5LJP+rC+^ng)swR(l81CWqVoMC%fF>g|BOay2`_+;{g`B}0 z_I_r~Bb~)(@wY@KO8!78C)l#XG4a`=TdbS=!=q2z>P{#KqR%z|0(H_jAUrIjA(9u= zj10wjfDnCph7uLx(8|#F^$)UUJlJg)t97*%xqI&}sT{{`v#Cztc(pv~i&D&+rh}aD duE533>Q6<{@BU*J+isT*+7!jn(VI_Ke*xWJYc&7> diff --git a/MobileWallet/Assets.xcassets/Icons/General/Send.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/General/Send.imageset/Contents.json deleted file mode 100644 index 8f2ddc4e..00000000 --- a/MobileWallet/Assets.xcassets/Icons/General/Send.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "ico-send-receive.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/MobileWallet/Assets.xcassets/Icons/General/Send.imageset/ico-send-receive.pdf b/MobileWallet/Assets.xcassets/Icons/General/Send.imageset/ico-send-receive.pdf deleted file mode 100644 index c5bd5045b32f3321c157497e391cb54ec4d14b0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2957 zcmb7GO>Y}F5WVlO;Ke|42*vLYAPCUdO;NN(U8T362UXrUDrBjZlp^h~&wC`7yLJl% z*@L}$J2M=Z?aiAPTE=0}LW$$=e+)vrd?{YN8pgvNe-37euYMd4_m>aC0$kJT zd^wGqhhcRy{=M6d>+fER>$l5a+w<_(prtC0P}9qc{?@;Fse-pE32i5V>$>(BYoLBJM4odP>srJ!ou?MC@ zRE`cI*p#@CRz7AT(R-sDg9htj1d@D;vBpveZ!ED8d`PtN_-}~BfR6<+)@dRz(zfKF zbWAz{%gNZXUgOS5g)hEDs%+NH17Ysn%0zFF1CScYNgIv!g%0rIy>QZnz(tB*%?em_ z%z=59%z-4+XbjqTML&GOj_KD}aMB}D6XDmGax$dsK^gfBT08i~5M9pj>%1ofhz2z7 zfko%Yagru@2i|)HleFH@s@J(ATh!iW5a;06t%AyAv`6C7!4{jg9{qADw1{XFMXu0# z8cpPYM4MUM1>$v7AtLt$X0k(vA*&uqB^1k+t(NryK7|CE?O@OrioI3?pr-pl`nNf=3tM4a*lkxC}i#IfXcF9l;8VbppgB^~>zc1WR(J!3OO zfMsgML`T-4?FFyhULrjm|LyQ$ct0G5U%py)upTz5y!f)}Xe~=o%9LU7itGS%l!_t< z6$E5_&Y-XF0gzVc=sF%vd9;#|52w$^GGa4C2YQIvl~pMcvK=Uv8hw^AG}eJkPE=(d zL-`1^(dnW;&91Uo^e*a9XSl0~hK@~2R>0DLiJhn*C?ygriyBi|SZ%D=k(L-Hyf#HK zL)&vRNLLHa2L{b?7L?4+QI8@jUB(DdqR4V0+pI02K9WnYP3|&U8}FI8t~$t%ZLB$g z6bq;hn!Q)BD+-`i=?)dw3_%6w9n?l@g99SW(gVmOOjH8YwFeUz(BWmCsZ5g?py&qW zr*t#3o}TUGDyt$pLJ3b1!vcCrB|)4^pq<$v z;XtS!jRn%=4p7Kd5CF!2Ez&M}w7jBgRF1_IYq!$(v>I^d}Q?7Q&r=G3l5(;bS}rFyqAR)>DIf>g{`uGKe(!|`$!?|#D- z4A1JUMKKHsGi6Znt@T{3PCC8*NhiiwP4nzfFnfam%McgcYQou z#sgO54Q~Q(Hz!~M@>HP3J&;hpAFh7b-62)M9jwK)&h_yleD#0wKLc4r!X;`Ei5ook z1?P=lZ!Vkt@&2*vr_=a;P$CX>`}`|{_h@^-?a{be@ezj(S>*W$!o>zzJb?_9>@gG+ zxOoEMn46%>X|vmprvVq{)%Q1`Qr^>)!R4UeBqZmO@4Os-9P?0&GVOE&R=~s9dF<1&nfKji$5Q4 z-#`91N5D&sdU(7)UVWG@-W>mPeK=nJ;p_SJxBI^i57WOVzc~BM?)2r0?c4TSSn@h3 zEUCEC<9zsVp1BR%zC0XmC%3fhQjK$5a;hPx`NPClUt?};)|!je&o@(A7?lE6FEM-X z3>KQ9bZ=uDsuH;&tEAv8kT50Ten0#1#t*K_M z+Jo-}BLHK|A!#dCXrazPDd+C_T>VJ3cP-4l279C63ja!%{6W24-80&#mNa7}LaF*e9om*DY^ zMFOwONJbGtSi~rXWYK>jvLQx|!-T}SisE13Z!w-IQVuBLjK=w3Q;ohBv(Mp2tO6W| zB`6se!4t7AvfvsO$Rj8?vL-VX+g*(063kAa^6Slt!SknB;M`G|b0tAy%WC)}nueCN zoaMRr4*?>Zip|10Oi>axPNS5=oKAj{sMR~CL?yE``LJd-(KExIW&6$aQZ8J z$%2?2vY?1Hh{6N~XN4@=VsEl+12d`XLrAC9a3hq5ciE^jc9mm7DA_36OT_aQF;oFr z5MzQvQ7k&yw_Jfp5ICmjkUlI6*IV?)YzJR^$RbCrrIJZRr$FaqF{}Y8aXPUhPq8rZ zRaxm!MxTn*4Ienz<%w#*Ga402L#pJ*Ov-~G4kVJL#I$Iv@W084n*;^cdh4ts2KoQ((|?$Il{9jQWjv_AC& zi36jU7C4JU*QgyiFNw);Vwov1;y^%rak%D{l&L&ms7`5tQG}r3s!`~fs0tTwng)^L z99FScbwz5x0*RpL;p0a2gNEAJE1nq6QqTjkHmiqgLZaZvy^8_XS$(A*H%yb(5L^PF zlAAgO4wfNiF#QZkwn!R_@iwj^k~NSvjAOM5j~Yiury%II>na15F2AA;FIJ_tkW{n= zv+s(q6#o-M37$*t38|w?;T+PjfrSN?K&s@H6BZYIO_RZd>JZW*ZOyIHD!~&7!|+Jp z<$q#>Qj&wHRV0>K@(wFXsHN`_SRumf<`K9G2Qt`Lo#5IB#4H$9L7dy{T&seHozJ7P zV-wX|l_nR77VGM;fuKBa&$TAGImtlgatniTWo246KHEKQ5 zhKXZx9p-G<<{ef?;!Gol4-r|igVx3Px_TPB7=xd-XkdE}D#IIMzEsR!%oVbzQBFvU z##I6rasH1sES#O1unIE$2tA+fMUHlWei5T`4v1G=Vu-p75u;3bv}Ox!Ha(6JxtG1f zt>RLp9tuHI!94U(f=HUBhNP@WwW2`fwWf-Bj6%ZkD4H=m4+C?G_DrX0eGqFlE%Pz@kD8icxx@H z*UEFKhn9eF^Wv7oJPJslzCa-=8XenV664vN1i_PzjYq5Gky~q?3sqZ*Rq5zK0*wzD zW>=Hq8=1h)Tx61Z1!@a@i}k{ESlls~pf-cs;RuBmeIBqE6NTErwb@}9<%AwWB>gB+ z@C!F>Qwb%zHMSdOG^UflGo=tFg>q0`furoI!`Ko6I{A`0m8A{I&pgNSEV`wZk`nLl zkdc%ub{=I(>9Tmc&BWV1jn%1MmXutq0%dFngPKqU4#B4bS@nYeoT0TUaG`_m>&%@L zZR>A>g)m?Pp+D)Lv=lGN)H9@*OA_f1&!B>C2W?Nl)gKse9yy$5FAT^D)oZx+B;2Og zBLJe6sk)K;6gZPFON|pULiutE>0yHFsP5kxAM-r&aSGva`!Gj62P(3wOV*pHi`&)m ztzX$;qq_d0zMsm)uD-8%ZJo$g-%sH_*LUOU+S3qO)pyN%R^RoEa;oOTf?u!i+o-Ky z`7QQUre)7!@p29{lAT6bdWcGq0H4GGo~P(Sjl9%Cw{oC2-91Ec`bR4~t96zKnU}d8 zcgP{GuEc1EWtCs6tLsXpq^Ldf#(unu6jW!t3aOc0^M=6PFm1p&NIp~$h2WlU1ufHSpfRMSo=;RjbsIL;(nv!) zo2RG7wXu!Jvb91KJ*#3|ptw%SZ`_Y<3=$q_d#n)RQoJNZplD@`m)wTN{oo14?OLxn zQVDKr5>3x))^kxSJC{?0D>mKuCGLjE(I~O*gc~Mxq3*4MPw%+axTk?V6G|_{VcaIPAci;2W%;)0o|NYl-zIc1}{y2cY9N%4EeSP=y z{GDR7QfB)Sc&b>T^S9GVm&b>@pY9LGhnW-4mt(otcejtnTXrOJ8~FC>9&AQ%4BEYS z3)4pV2H;Br3g^ZM>5=K5dQ{{XUE2`cVZ(g7c~4bKOEef4;CbNBw~)SvH< zKTN#iPU~6q?@S8;FM!8K8Kuw6JNWqMU0RHmW;peLAtLQykFa{nho& z@qXez1}^^i26ue+aQET(`TW`SyU$mrTwLDWJ;E0G-u{E&>i*G=Q(WxR Lt5?7O>o@-guNc=` diff --git a/MobileWallet/Assets.xcassets/Icons/Rotary Menu/Close.imageset/close.pdf b/MobileWallet/Assets.xcassets/Icons/Rotary Menu/Close.imageset/close.pdf deleted file mode 100644 index 3e3bd992ffe28055bd4811b38d6d274fc64f90eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1896 zcmZWq%Wl&^6y48P%%)Pis6FqorK%E5DMElKDZ7e=aojd2u_1O5@b#QK9*>>Yi)eCu zpXZ*rKDoWVIhUDdOme}y?>`vl7Z?2Uk~Qsy{xY@1^N&sYI6QF;a7|YIaA>MM%deZ? z+qzl5yW*?6<-fXTznIKKc(|Aj=joK**~}@URmtg~h4H-Jvt;)A(AO>VS?PRHl-y<7 zyV6>|XHsQGNg)lFA}h2Ih2T3vomSeGTq2{9!b%RzTd8zGm{wlXDPUG8!kn_D6TBvG zSbFCOEtRna-cv-a%MeFNDLi6RZiV#eq(^VOHzvG?k9Q$ssjPId3gt||oN`tNJ8xAf z6?uo+O6kYiG|GrQ#5_)$LLkoAExn~GG+KvuBt~*j<%JiPxIhcKP$6J6B+yG`roPZD z_(K?5Or+>iAao`bcn;lrR0^oZkP1}@XJdf`ip(0B>I>0CrZEh^%u23O7G8&Vit^i8 zBY|pL7;yxFA(}I}Mo|TVkiUf86fMlyCk|D$q`J{FOC73nIGE99=N;d}6Nu(I`6sHIk&2}mavtC9UC94)$ z!>w}~+9A6@%&aghrIDFzT5RLSYtRp+&#=PDsJNCpL zSj)aJT8eSwP3w_1;kY)XvNml^C~w}T`J1-whMwPl!*<1!fBpNj;rVU#*hJt{v)NWx z-5-8G(X>##GLy#U(-djl^xgBJZh8)u_&SDLb?wl!$Vkh30&lAWFb+`#XtCl6N~=Hr zu-%|kP93cIlxNjF!&knlzX!62g!wHZQGRFA?d)$ulIL)X8QJ( z3|IgGaba8?&vaK+pQ<|5^Xl!JZx43K(`08eKm7Aw(>#Cm)%^9>)A9bD{yDi3zxea< z{{8chvj@D?Q%}#2$E&;P;?42DH`m9@KYTO4{%-u&_0#m9i49Eqo6Y{?aQfB%XUI#4 zzQjB`yI5DMN%QsH)SvwL$J6!wWK(-9gmv*y!+a;u_!RsA+PEa2ti#X6rsPeTfqXDE zWuTC3NN&EK08NO+>eU=mvT3$qDY^1DV^>Vo@ne8u4&GP2+CkPZ#g;hVo`J!|Sflpc zwunn{u^QIvrX*aqW;bOs86RzOGNn6~mROweR*=F*06K&z36rgh18DQ&GUgYa+gHmA z9%f=P?wXg9Y{_9hSyvY0a;g?+_hEoGU)i;TcG;pr00wZ6Uf<#r<8I1$7Tx62nRUk+ z9dz8M0Cebj`f>VUx}W~``Pg@GY4I*5=sWUnfH~vsI0f2ROYp&p{MKszKpXZh~X&WCsa(Fjv;50=R;V0w%!{k3+jC0 z@Hf;|H#@&n?;Ls0lP;IKVk!Gb;Q^yinVO*mPX?<+=Y!tyR3^RctT#CcOB9q$W%mna z*1+3-@sw0B-gZmPp-}HZt#yHSgauC_o)2z;z1G_Hwh5F+VWFBrF>8C96pAlGDZ0sp zb?=bmjfD=;;jL`+fr2M2m1vUH+d4rP3oQ-ix=P8so!0!M=27vfu?gw}kt>dOXabdd zV3UN|*5n2iBBjiAd7&7WLIz<>N`~k1k_&MXWU!vrc~=%|4{ z=3Mn0p~ih6<0~n@LLpMLX`(&{YumPZFcUeUnRdmYYQ3C_LfCAv30v40gl+*Aic18h z1p!Mrtaf~AKVDIQ@itIUbkv5{)0HG84n z<93`|zR@jLjBdm_zedLw=YlV^wNJ=}2w5#*g*xz!kn{6yqEI`NpyrOkcKo)ldsu@aV(Mnx%ylB=%pw$`lC zSQ<)0NpAAENJcq}k38v9At}KSY)O7Mj9_`*hSk|C*H$!Uv{Jz_L>oS8A5I_?Fw!g3 z4I}?l38zYvBz@MmV+zLDxk~c|ik99^)EuN+B@@V*b?@Rh6xEeVyN)idYt|e+FVWrT zuquei8XMVMZx`5YwIWVXV%SA*SHPBMO3kER4Mr@)7oMcpsAhptEyMz_8qsX)-ZdaA zwI)tP+-*-9&u$QMsMyW3}4GR<|U$0oN0?3%acQ=kPIKB9tP_;k7)0tT=Ja z+XN=+99gs4OjnWSB%hKP+;XqBjQThqUsMcq$n+x;=C?-)PjY#Rx1vdcAfyNaosnFS z`WM+dOm&2OsCR1O00WxhNbm+y;5>4UJ^{Nldg#gEIImW8Q2~|Wvo65F8$M!%R0i5-aw z*kjPa>uHQwhi?Uoi7jsigGHnLkuEi^}^6Md9KS zL-83(mrw?;?{98u0%I&zsTd-n5ezw|BH4r21};W>uuj%9tRKjL_OlyS?1wz;J@8?$ zvQua9rgLoYyJOe1^B$v)-hwj9S|iUp+9qCxUuSE7bLOEA$j0cF0C1(2!IbFcut3oo|?;3DgaN+0v~vtw?g)BH2mzGrX_Up9YbAMCgO! zEg(vNRjZnAHBrhYLkk@SPeDwr&~unj1tJlLNmlm|4369|@8d3eVjA}V6#RWEX1H|i zjP~W94`>nITg!?Xv`z^t( zW&tK@wA|@Bfg(XVD%ofY>#YOA+V~)aPEXjcCjiM*)HdLDj{)SFoH>Gi-N9<)If{O~ z7tyY|m=?HQpkN)vQ&O_uvs5l_=vnI-?-w!}h#~_|W5arLQz(pF7BmQM>4N81==L(I zV+*nw8T#95`*dx4sEdKF*n@JRX+Z#1IH70@B}F0uC?v#<1fc73eMBRf54v69RNL*O z&YCfWFp|cMfmD3UfXig5IzvOCIubm_IL01kaB?70>6;qXv`EEcXHF$k_BAw-9%Pk{ zo!D`k0_3>#Q9WkeN>N#dJ#I3XW5qS*YWC8uBq|3A6)d+~ToPGcfbecs{bvYJg31rR zJdnRMcrIfY@^P0lFPI7SBek7ZiAt+_x`5X;kIKCQ7&P($X2dcB3dLM~1vyQQG)B+# zEyu)89cB&ZbooR%M?6dO6mSI z@x5lXn)az01`miqVsE-C_aL;Cl`kLOJa=ZDXss_>+h?o&8<#$3t>3;9(|`Z;#V4TC zC$Wp)-QPbvKh59&gHL4qUHtuj|31zaZ?E1TH{dVFcQ;qxJp4R=ugkg*K6oX!L#JQ+ zN|(o{ho2s=k598c!d-65y?(fVKHjsVzQFC^+p9;gId0JKola2yes}Swn|GKx%Y(~# z-{f&s42JLXD|iXe7U+1$8O{e#6ljx8QS26*b@vwZ;3{h3!jZO?yXf}U_Z``m zm1K1=MvplpUy|b4)y3sYw#gVKNB!rohUnERdi~n8-A#QpGUtmw+U|CDM;>rlt%lvU zUEQ0-Mf>l%X_xQb()q{vTQit%hOHYPa>v8V=`{T(R`*88hP_6i=KgeZGMf%V(;2a; z6qhrx*is|~rF)ZXEG08jv64N{%1wl>O%aByG%{VYbm} zff#WXs&Xojio`++C41rk)nfsWL$Q#>5ILuU$_0&SZVMIy>Jq6+6vs)c|KMUSV^I}i za-pWy1^@ftDBGw$_*6@ftVHL%Be5YDA8M%qlgAPF5s(WDakMJ2aK->2buK`Pc9-Z7 z*)qojIS4X!B-oNef#cdb6-`>*9Sk=nARO|Z`SVs%7KgH17vsLuN!h1#%B_K~#Og%n zA{+`Ea=nQO+{@h<%x6#x5$IHyIM0 zQk5xU*@Y-52V6R&4Z}Hm()aYLL=M^DRk=)Z59cA&bdmIrs|eYa~nM67%2 z2(MOKFwz5>ZL^A|Ew}i6eS=h~Dp=An&UybGclk;F1IWx%eBh}H9HxLL;peN}YSZ67 zy!vC?eld=c*{{X=OHtJ;0UsWAG@huNkVl^NnGiedgU6_#IuETRKDp>UzMX^BZo67< z+O4VGdw+p)JRAD^_BlOU-#p(JWwGr09aX_&ffpawKU-u!uK(3`H_BmOk~us3?a#|! DjYwJ5 diff --git a/MobileWallet/Assets.xcassets/Icons/Star/Contents.json b/MobileWallet/Assets.xcassets/Icons/Star/Contents.json deleted file mode 100644 index 6e965652..00000000 --- a/MobileWallet/Assets.xcassets/Icons/Star/Contents.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "provides-namespace" : true - } -} diff --git a/MobileWallet/Backup/Manager/BackupFilesManager.swift b/MobileWallet/Backup/Manager/BackupFilesManager.swift index eea0e153..cc4dc923 100644 --- a/MobileWallet/Backup/Manager/BackupFilesManager.swift +++ b/MobileWallet/Backup/Manager/BackupFilesManager.swift @@ -81,7 +81,7 @@ enum BackupFilesManager { .all .map { try $0.json } - let model = PartialBackupModel(source: try Tari.shared.walletAddress.byteVector.hex, utxos: rawUTXOs) + let model = PartialBackupModel(source: try Tari.shared.walletAddress.components.fullRaw, utxos: rawUTXOs) let data = try jsonEncoder.encode(model) let fileURL = workingDirectory.appendingPathComponent(unencryptedFileName) @@ -142,7 +142,7 @@ enum BackupFilesManager { try await Tari.shared.startWallet() - let sourceAddress = try TariAddress(hex: model.source) + let sourceAddress = try TariAddress(base58: model.source) try model.utxos .map { try UnblindedOutput(json: $0) } diff --git a/MobileWallet/Common/Data Models/PaymentInfo.swift b/MobileWallet/Common/Data Models/PaymentInfo.swift index cc747d6f..c79ef5b0 100644 --- a/MobileWallet/Common/Data Models/PaymentInfo.swift +++ b/MobileWallet/Common/Data Models/PaymentInfo.swift @@ -39,7 +39,7 @@ */ struct PaymentInfo { - let address: String + let addressComponents: TariAddressComponents let alias: String? let yatID: String? let amount: MicroTari? diff --git a/MobileWallet/Common/Deep Links/DeepLinkDefaultActionsHandler.swift b/MobileWallet/Common/Deep Links/DeepLinkDefaultActionsHandler.swift index 45e957c9..033a28be 100644 --- a/MobileWallet/Common/Deep Links/DeepLinkDefaultActionsHandler.swift +++ b/MobileWallet/Common/Deep Links/DeepLinkDefaultActionsHandler.swift @@ -77,7 +77,8 @@ enum DeepLinkDefaultActionsHandler { amount = MicroTari(rawAmount) } - let paymentInfo = PaymentInfo(address: transactionSendDeepLink.receiverAddress, alias: nil, yatID: nil, amount: amount, feePerGram: nil, note: transactionSendDeepLink.note) + guard let addressComponents = try? TariAddress(base58: transactionSendDeepLink.receiverAddress).components else { return } + let paymentInfo = PaymentInfo(addressComponents: addressComponents, alias: nil, yatID: nil, amount: amount, feePerGram: nil, note: transactionSendDeepLink.note) Task { @MainActor in AppRouter.presentSendTransaction(paymentInfo: paymentInfo) @@ -165,7 +166,7 @@ enum DeepLinkDefaultActionsHandler { // MARK: - Actions private static func contactData(deeplink: ContactListDeeplink) -> [ContactData] { - deeplink.list.map { ContactData(name: $0.alias, address: $0.hex) } + deeplink.list.map { ContactData(name: $0.alias, address: $0.tariAddress) } } private static func contactData(deeplink: UserProfileDeeplink) -> [ContactData] { @@ -178,7 +179,7 @@ enum DeepLinkDefaultActionsHandler { try contacts.forEach { - let address = try TariAddress(hex: $0.address) + let address = try TariAddress(base58: $0.address) if Tari.shared.isWalletConnected { _ = try contactsManager.createInternalModel(name: $0.name, isFavorite: false, address: address) diff --git a/MobileWallet/Common/Deep Links/Models/ContactListDeeplink.swift b/MobileWallet/Common/Deep Links/Models/ContactListDeeplink.swift index 3216bf5c..ec9ed6d2 100644 --- a/MobileWallet/Common/Deep Links/Models/ContactListDeeplink.swift +++ b/MobileWallet/Common/Deep Links/Models/ContactListDeeplink.swift @@ -42,7 +42,7 @@ struct ContactListDeeplink { struct Contact: Codable { let alias: String - let hex: String + let tariAddress: String } let list: [Contact] diff --git a/MobileWallet/Common/Extensions/CGPoint+Utils.swift b/MobileWallet/Common/Extensions/CGPoint+Utils.swift deleted file mode 100644 index b9128201..00000000 --- a/MobileWallet/Common/Extensions/CGPoint+Utils.swift +++ /dev/null @@ -1,52 +0,0 @@ -// CGPoint+Utils.swift - -/* - Package MobileWallet - Created by Adrian Truszczyński on 19/06/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 CGPoint { - - func point(distance: CGPoint) -> CGPoint { - CGPoint(x: x + distance.x, y: y + distance.y) - } - - static func point(radius: CGFloat, normalizedDegrees: CGFloat) -> CGPoint { - let xPoint = radius * sin(normalizedDegrees.degToRad) - let yPoint = radius * cos(normalizedDegrees.degToRad) * -1.0 - return CGPoint(x: xPoint, y: yPoint) - } -} diff --git a/MobileWallet/Common/Views/RoundedTextView.swift b/MobileWallet/Common/Extensions/ContactsManager+Utils.swift similarity index 64% rename from MobileWallet/Common/Views/RoundedTextView.swift rename to MobileWallet/Common/Extensions/ContactsManager+Utils.swift index f4eed446..37cffffb 100644 --- a/MobileWallet/Common/Views/RoundedTextView.swift +++ b/MobileWallet/Common/Extensions/ContactsManager+Utils.swift @@ -1,10 +1,10 @@ -// RoundedTextView.swift +// ContactsManager+Utils.swift /* Package MobileWallet - Created by Adrian Truszczynski on 20/07/2021 + Created by Adrian Truszczyński on 16/07/2024 Using Swift 5.0 - Running on macOS 12.0 + Running on macOS 14.4 Copyright 2019 The Tari Project @@ -38,34 +38,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit - -final class RoundedTextView: DynamicThemeTextView { - - // MARK: - Initializers - - override init() { - super.init() - setupView() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupView() { - - layer.cornerRadius = 10.0 - textContainerInset = UIEdgeInsets(top: 12.0, left: 12.0, bottom: 12.0, right: 12.0) - textContainer.lineFragmentPadding = 0.0 - } - - // MARK: - Updates - - override func update(theme: ColorTheme) { - super.update(theme: theme) - backgroundColor = theme.backgrounds.primary +extension ContactsManager.Model { + + var contactBookCellAddressViewModel: AddressView.ViewModel { + if let alias, !alias.isEmpty { + return AddressView.ViewModel(prefix: nil, text: .single(alias), isDetailsButtonVisible: false) + } else if let addressComponents = internalModel?.addressComponents { + return AddressView.ViewModel( + prefix: addressComponents.networkAndFeatures, + text: .truncated(prefix: addressComponents.coreAddressPrefix, suffix: addressComponents.coreAddressSuffix), + isDetailsButtonVisible: false + ) + } else { + return AddressView.ViewModel(prefix: nil, text: .single(""), isDetailsButtonVisible: false) + } } } diff --git a/MobileWallet/Common/Extensions/Data+Utlis.swift b/MobileWallet/Common/Extensions/Data+Utlis.swift index 6f02ccb9..ba0a94e9 100644 --- a/MobileWallet/Common/Extensions/Data+Utlis.swift +++ b/MobileWallet/Common/Extensions/Data+Utlis.swift @@ -45,6 +45,8 @@ extension Data { case last } + var string: String { String(decoding: self, as: UTF8.self) } + var isBLEChunk: Bool { bleChunkType != nil } var bleChunkType: BLEChunkType? { @@ -92,14 +94,11 @@ extension Data { extension Array where Element == Data { + var stringFromBLEChunks: String? { dataFromBLEChunks?.string } + var dataFromBLEChunks: Data? { guard first(where: { !$0.isBLEChunk }) == nil else { return nil } return map { $0.dropLast() } .reduce(into: Data()) { $0.append($1) } } - - var stringFromBLEChunks: String? { - guard let dataFromBLEChunks else { return nil } - return String(data: dataFromBLEChunks, encoding: .utf8) - } } diff --git a/MobileWallet/Common/Extensions/String.swift b/MobileWallet/Common/Extensions/String.swift index 048777a5..eb71c102 100644 --- a/MobileWallet/Common/Extensions/String.swift +++ b/MobileWallet/Common/Extensions/String.swift @@ -42,16 +42,13 @@ import UIKit extension String { + static var dots: Self { "•••" } + var firstOrEmpty: String { guard let first else { return "" } return String(first) } - var obfuscatedText: String { - guard count >= 9 else { return self } - return "\(prefix(3))•••\(suffix(3))" - } - func insertSeparator(_ separatorString: String, atEvery n: Int) -> String { guard 0 < n else { return self } return self.enumerated().map({String($0.element) + (($0.offset != self.count - 1 && $0.offset % n == n - 1) ? "\(separatorString)" : "")}).joined() diff --git a/MobileWallet/Common/Extensions/CGFloat+Utils.swift b/MobileWallet/Common/Extensions/UInt8+Utils.swift similarity index 84% rename from MobileWallet/Common/Extensions/CGFloat+Utils.swift rename to MobileWallet/Common/Extensions/UInt8+Utils.swift index d5beead6..24ce3350 100644 --- a/MobileWallet/Common/Extensions/CGFloat+Utils.swift +++ b/MobileWallet/Common/Extensions/UInt8+Utils.swift @@ -1,10 +1,10 @@ -// CGFloat+Utils.swift +// UInt8+Utils.swift /* Package MobileWallet - Created by Adrian Truszczyński on 19/06/2023 + Created by Adrian Truszczyński on 03/07/2024 Using Swift 5.0 - Running on macOS 13.4 + Running on macOS 14.4 Copyright 2019 The Tari Project @@ -38,11 +38,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -extension CGFloat { +extension UInt8 { - var degToRad: CGFloat { self * .pi / 180.0 } - - static func angle(normalizedDegrees: CGFloat) -> CGFloat { - normalizedDegrees.degToRad - (.pi / 2.0) + var tariEmoji: String { + get throws { + try TariEmojis().all[Int(self)] + } } + + func flag(bitmask: UInt8) -> Bool { self & bitmask == bitmask } } diff --git a/MobileWallet/Common/Formatters/TransactionFormatter.swift b/MobileWallet/Common/Formatters/TransactionFormatter.swift index 5af24dd6..743cbc91 100644 --- a/MobileWallet/Common/Formatters/TransactionFormatter.swift +++ b/MobileWallet/Common/Formatters/TransactionFormatter.swift @@ -42,7 +42,6 @@ final class TransactionFormatter { struct Model: Identifiable { var id: UInt64 - let avatar: RoundedAvatarView.Avatar let titleComponents: [StylizedLabel.StylizedText] let timestamp: TimeInterval let amountModel: AmountBadge.ViewModel @@ -66,14 +65,13 @@ final class TransactionFormatter { let contactName = try contactName(transaction: transaction) if !filter.isEmpty { - guard try transaction.address.emojis.range(of: filter) != nil || transaction.address.byteVector.hex.range(of: filter) != nil || contactName.range(of: filter) != nil else { return nil } + guard try transaction.address.components.fullEmoji.range(of: filter) != nil || transaction.address.components.fullRaw.range(of: filter) != nil || contactName.range(of: filter) != nil else { return nil } } let messageComponents = try messageComponents(transaction: transaction) return try Model( id: transaction.identifier, - avatar: avatar(transaction: transaction), titleComponents: transactionTitleComponents(transaction: transaction, name: contactName), timestamp: TimeInterval(transaction.timestamp), amountModel: amountViewModel(transaction: transaction), @@ -83,34 +81,30 @@ final class TransactionFormatter { ) } - func contact(hex: String) -> ContactsManager.Model? { - contactsManager.tariContactModels.first { $0.internalModel?.hex == hex } - } - - private func avatar(transaction: Transaction) throws -> RoundedAvatarView.Avatar { - - guard try !transaction.isOneSidedPayment else { - return .text(localized("transaction.one_sided_payment.avatar")) - } - - let contact = try contact(transaction: transaction) - - if let image = contact?.avatarImage { - return .image(image) - } - - let avatar = try contact?.avatar ?? transaction.address.emojis.firstOrEmpty - return .text(avatar) + func contact(uniqueIdentifier: String) -> ContactsManager.Model? { + contactsManager.tariContactModels.first { $0.internalModel?.addressComponents.uniqueIdentifier == uniqueIdentifier } } private func contactName(transaction: Transaction) throws -> String { - guard try !transaction.isCoinbase else { return localized("transaction.coinbase.user_placeholder") } let contact = try contact(transaction: transaction) return contact?.name ?? localized("transaction.one_sided_payment.inbound_user_placeholder") } private func transactionTitleComponents(transaction: Transaction, name: String) throws -> [StylizedLabel.StylizedText] { + guard try !transaction.isCoinbase else { + + guard try transaction.isOutboundTransaction else { + return [StylizedLabel.StylizedText(text: localized("transaction.coinbase.title.inbound"), style: .bold)] + } + + return [ + StylizedLabel.StylizedText(text: localized("transaction.coinbase.title.outbound.part.1.bold"), style: .bold), + StylizedLabel.StylizedText(text: localized("transaction.coinbase.title.outbound.part.2"), style: .normal), + StylizedLabel.StylizedText(text: localized("transaction.coinbase.title.outbound.part.3.bold"), style: .bold) + ] + } + if try transaction.isOutboundTransaction { return [ StylizedLabel.StylizedText(text: localized("transaction.normal.title.outbound.part.1"), style: .normal), @@ -195,7 +189,6 @@ final class TransactionFormatter { // MARK: - Helpers private func contact(transaction: Transaction) throws -> ContactsManager.Model? { - let hex = try transaction.address.byteVector.hex - return contact(hex: hex) + contact(uniqueIdentifier: try transaction.address.components.uniqueIdentifier) } } diff --git a/MobileWallet/Common/Managers/AddressPoisoningManager.swift b/MobileWallet/Common/Managers/AddressPoisoningManager.swift index 17829b18..2865b5fa 100644 --- a/MobileWallet/Common/Managers/AddressPoisoningManager.swift +++ b/MobileWallet/Common/Managers/AddressPoisoningManager.swift @@ -68,48 +68,50 @@ final class AddressPoisoningManager { try await contactsManager.fetchModels() - let emojiID = try address.emojis + let spendKey = try address.components.spendKey var result: [SimilarAddressData] = [] if includeInputAddress { try result.append(inputAddressData(address: address)) } - result += try similarContacts(toEmojiID: emojiID) + result += try similarContacts(toSpendKey: spendKey) return result } - private func similarContacts(toEmojiID emojiID: String) throws -> [SimilarAddressData] { + private func similarContacts(toSpendKey spendKey: String) throws -> [SimilarAddressData] { try (contactsManager.tariContactModels + contactsManager.externalModels) .filter { guard let internalModel = $0.internalModel else { return false } - return emojiID.isSimilar(to: internalModel.emojiID, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) + return spendKey.isSimilar(to: internalModel.addressComponents.spendKey, minSameCharacters: minSameCharacters, usedPrefixSuffixCharacters: usedPrefixSuffixCharacters) } .compactMap { try data(contact: $0) } } private func inputAddressData(address: TariAddress) throws -> SimilarAddressData { - let emojiID = try address.emojis - guard let existingContact = (contactsManager.tariContactModels + contactsManager.externalModels).first(where: { $0.internalModel?.emojiID == emojiID }) else { - return try data(hex: address.byteVector.hex, emojiID: emojiID) + let addressComponents = try address.components + let emojiID = addressComponents.fullEmoji + let uniqueIdentifier = addressComponents.uniqueIdentifier + guard let existingContact = (contactsManager.tariContactModels + contactsManager.externalModels).first(where: { $0.internalModel?.addressComponents.uniqueIdentifier == uniqueIdentifier }) else { + return data(address: addressComponents.fullRaw, emojiID: emojiID) } - return try data(contact: existingContact) ?? data(hex: address.byteVector.hex, emojiID: emojiID) + return try data(contact: existingContact) ?? data(address: addressComponents.fullRaw, emojiID: emojiID) } - private func data(hex: String, emojiID: String) -> SimilarAddressData { - SimilarAddressData(address: hex, emojiID: emojiID, alias: nil, transactionsCount: 0, lastTransaction: nil) + private func data(address: String, emojiID: String) -> SimilarAddressData { + SimilarAddressData(address: address, emojiID: emojiID, alias: nil, transactionsCount: 0, lastTransaction: nil) } private func data(contact: ContactsManager.Model) throws -> SimilarAddressData? { guard let internalModel = contact.internalModel else { return nil } - let transactions = try transactions(forHex: internalModel.hex) + let transactions = try transactions(forUniqueIdentifier: internalModel.addressComponents.uniqueIdentifier) let lastTransaction = try formattedLastTransaction(transactions: transactions) - return SimilarAddressData(address: internalModel.hex, emojiID: internalModel.emojiID, alias: contact.name, transactionsCount: transactions.count, lastTransaction: lastTransaction) + return SimilarAddressData(address: internalModel.addressComponents.fullRaw, emojiID: internalModel.addressComponents.fullEmoji, alias: contact.name, transactionsCount: transactions.count, lastTransaction: lastTransaction) } - private func transactions(forHex hex: String) throws -> [Transaction] { + private func transactions(forUniqueIdentifier uniqueIdentifier: String) throws -> [Transaction] { try Tari.shared.transactions.all - .filter { try $0.address.byteVector.hex == hex } + .filter { try $0.address.components.uniqueIdentifier == uniqueIdentifier } .sorted { try $0.timestamp > $1.timestamp } } diff --git a/MobileWallet/Common/Managers/BLE/BLEPeripheralManager.swift b/MobileWallet/Common/Managers/BLE/BLEPeripheralManager.swift index be83cd3a..7c8f549b 100644 --- a/MobileWallet/Common/Managers/BLE/BLEPeripheralManager.swift +++ b/MobileWallet/Common/Managers/BLE/BLEPeripheralManager.swift @@ -255,7 +255,7 @@ final class BLEPeripheralManager: NSObject { // MARK: - Helpers private func makeUserProfileDeeplinkChunks() -> [Data] { - guard let alias = UserSettingsManager.name, let address = try? Tari.shared.walletAddress.byteVector.hex else { return [] } + guard let alias = UserSettingsManager.name, let address = try? Tari.shared.walletAddress.components.fullRaw else { return [] } let model = UserProfileDeeplink(alias: alias, tariAddress: address) guard let url = try? DeepLinkFormatter.deeplink(model: model) else { return [] } return url.absoluteString.data(using: .utf8)?.bleDataChunks ?? [] diff --git a/MobileWallet/Common/Managers/DataFlowManager.swift b/MobileWallet/Common/Managers/DataFlowManager.swift index 67a4e555..2b779e62 100644 --- a/MobileWallet/Common/Managers/DataFlowManager.swift +++ b/MobileWallet/Common/Managers/DataFlowManager.swift @@ -52,7 +52,6 @@ final class DataFlowManager { } private func setupCallbacks() { - Tari.shared.$isWalletConnected .filter { $0 } .sink { [weak self] _ in self?.updateUserName() } @@ -60,7 +59,7 @@ final class DataFlowManager { } private func updateUserName() { - guard UserSettingsManager.name == nil, let address = try? Tari.shared.walletAddress.emojis.prefix(3) else { return } + guard UserSettingsManager.name == nil, let address = try? Tari.shared.walletAddress.components.coreAddressPrefix else { return } UserSettingsManager.name = [localized("common.user"), String(address)].joined(separator: " ") } } diff --git a/MobileWallet/Common/Managers/ErrorMessageManager.swift b/MobileWallet/Common/Managers/ErrorMessageManager.swift index fa670a7f..d58a222b 100644 --- a/MobileWallet/Common/Managers/ErrorMessageManager.swift +++ b/MobileWallet/Common/Managers/ErrorMessageManager.swift @@ -57,6 +57,8 @@ enum ErrorMessageManager { return model(internalWalletError: error) case let error as SeedWords.InternalError: return model(seedWordsError: error) + case let error as ContactsManager.InternalError: + return model(contactManagerError: error) default: return genericErrorModel } @@ -107,6 +109,18 @@ enum ErrorMessageManager { return MessageModel(title: genericErrorModel.title, message: message, type: .error) } + + private static func model(contactManagerError: ContactsManager.InternalError) -> MessageModel { + switch contactManagerError { + case .emptyContactName: + return MessageModel( + title: localized("error.contact_book.no_name.title"), + message: localized("error.contact_book.no_name.description"), + closeButtonTitle: localized("error.contact_book.no_name.close_button"), + type: .error + ) + } + } } private extension String { diff --git a/MobileWallet/Common/Managers/MigrationManager.swift b/MobileWallet/Common/Managers/MigrationManager.swift index e74d2ab2..01fb9302 100644 --- a/MobileWallet/Common/Managers/MigrationManager.swift +++ b/MobileWallet/Common/Managers/MigrationManager.swift @@ -42,15 +42,10 @@ enum MigrationManager { // MARK: - Properties - private static let minValidVersion = "1.0.0-rc.5" + private static let minValidVersion = "1.4.1-rc.0" // MARK: - Actions - static func performPeerDBMigration() async -> Bool { - guard let version = await fetchDBVersion() else { return false } - return performPeerDBMigration(dbVersion: version) - } - static func validateWalletVersion(completion: @escaping (Bool) -> Void) { Task { @@ -106,12 +101,4 @@ enum MigrationManager { let popUp = TariPopUp(headerSection: headerSection, contentSection: contentSection, buttonsSection: buttonsSection) PopUpPresenter.show(popUp: popUp) } - - // FIXME: Temporary migration mechanism. It should be removed when minValidVersion is greater than 1.0.0-rc.8 - private static func performPeerDBMigration(dbVersion: String) -> Bool { - guard !VersionValidator.compare(dbVersion, isHigherOrEqualTo: "1.0.0-rc.8") else { return false } - let peerDBPath = Tari.shared.connectedDatabaseDirectory.appendingPathComponent("data.mdb") - try? FileManager.default.removeItem(at: peerDBPath) - return true - } } diff --git a/MobileWallet/Common/Managers/WalletTransactionsManager.swift b/MobileWallet/Common/Managers/WalletTransactionsManager.swift index b9206bb2..cef95f18 100644 --- a/MobileWallet/Common/Managers/WalletTransactionsManager.swift +++ b/MobileWallet/Common/Managers/WalletTransactionsManager.swift @@ -112,20 +112,21 @@ final class WalletTransactionsManager { private func sendTransactionToBlockchain(address: String, amount: MicroTari, feePerGram: MicroTari, message: String, isOneSidedPayment: Bool, result: @escaping (Result) -> Void) { do { - let tariAddress = try TariAddress(hex: address) + let tariAddress = try TariAddress(base58: address) let transactionID = try Tari.shared.transactions.send( toAddress: tariAddress, amount: amount.rawValue, feePerGram: feePerGram.rawValue, - message: message, - isOneSidedPayment: isOneSidedPayment + message: isOneSidedPayment ? "" : message, + isOneSidedPayment: isOneSidedPayment, + paymentID: isOneSidedPayment ? message : "" ) guard !isOneSidedPayment else { result(.success) return } - try startListeningForWalletEvents(transactionID: transactionID, publicKey: tariAddress.publicKey, result: result) + try startListeningForWalletEvents(transactionID: transactionID, publicKey: tariAddress.spendKey.byteVector.hex, result: result) } catch { result(.failure(.transactionError(error: error))) } diff --git a/MobileWallet/Common/NotificationManager.swift b/MobileWallet/Common/NotificationManager.swift index 5e9a12b4..33746315 100644 --- a/MobileWallet/Common/NotificationManager.swift +++ b/MobileWallet/Common/NotificationManager.swift @@ -227,7 +227,7 @@ final class NotificationManager { } private func sign(message: String) throws -> (hex: String, metadata: MessageMetadata) { - let hex = try Tari.shared.walletAddress.publicKey + let hex = try Tari.shared.walletAddress.spendKey.byteVector.hex 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) @@ -254,13 +254,13 @@ final class NotificationManager { } var responseDict: [String: Any]? - if let responseString = String(data: data, encoding: .utf8) { - if let data = responseString.data(using: .utf8) { - do { - responseDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] - } catch { - return onError(error) - } + let responseString = data.string + + if let data = responseString.data(using: .utf8) { + do { + responseDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + } catch { + return onError(error) } } diff --git a/MobileWallet/Common/Onboarding/OnboardingPageView.swift b/MobileWallet/Common/Onboarding/OnboardingPageView.swift index c52e045f..db3a96f3 100644 --- a/MobileWallet/Common/Onboarding/OnboardingPageView.swift +++ b/MobileWallet/Common/Onboarding/OnboardingPageView.swift @@ -96,7 +96,7 @@ final class OnboardingPageView: DynamicThemeView { @View private var actionButton: TextButton = { let view = TextButton() - view.setVariation(.secondary) + view.style = .secondary return view }() diff --git a/MobileWallet/Common/Persistant Data/User Settings/UserSettings.swift b/MobileWallet/Common/Persistant Data/User Settings/UserSettings.swift index 827b6b20..8a2846bc 100644 --- a/MobileWallet/Common/Persistant Data/User Settings/UserSettings.swift +++ b/MobileWallet/Common/Persistant Data/User Settings/UserSettings.swift @@ -53,17 +53,11 @@ struct UserSettings: Codable { case alwaysOn } - enum RotaryMenuPosition: Codable { - case left - case right - } - var name: String? var colorScheme: ColorScheme var bleAdvertismentMode: BLEAdvertisementMode - var rotaryMenuPosition: RotaryMenuPosition } extension UserSettings { - static var `default`: Self { Self(colorScheme: .system, bleAdvertismentMode: .onlyOnForeground, rotaryMenuPosition: .left) } + static var `default`: Self { Self(colorScheme: .system, bleAdvertismentMode: .onlyOnForeground) } } diff --git a/MobileWallet/Common/Persistant Data/User Settings/UserSettingsManager.swift b/MobileWallet/Common/Persistant Data/User Settings/UserSettingsManager.swift index 144b0705..199334e3 100644 --- a/MobileWallet/Common/Persistant Data/User Settings/UserSettingsManager.swift +++ b/MobileWallet/Common/Persistant Data/User Settings/UserSettingsManager.swift @@ -67,15 +67,6 @@ enum UserSettingsManager { } } - static var rotaryMenuPosition: UserSettings.RotaryMenuPosition { - get { userSettings.rotaryMenuPosition } - set { - var userSettings = userSettings - userSettings.rotaryMenuPosition = newValue - GroupUserDefaults.userSettings = userSettings - } - } - private static var userSettings: UserSettings { guard let settings = GroupUserDefaults.userSettings else { diff --git a/MobileWallet/Common/Pop-up/Components/CustomDeeplinkPopUpContentView.swift b/MobileWallet/Common/Pop-up/Components/CustomDeeplinkPopUpContentView.swift index a93c4554..7ea9b9db 100644 --- a/MobileWallet/Common/Pop-up/Components/CustomDeeplinkPopUpContentView.swift +++ b/MobileWallet/Common/Pop-up/Components/CustomDeeplinkPopUpContentView.swift @@ -77,7 +77,7 @@ final class CustomDeeplinkPopUpContentView: DynamicThemeView { @View private var showHideButton: TextButton = { let view = TextButton() - view.setVariation(.secondary) + view.style = .secondary return view }() diff --git a/MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressDetailsContentSectionView.swift b/MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressDetailsContentSectionView.swift new file mode 100644 index 00000000..3ea27bfd --- /dev/null +++ b/MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressDetailsContentSectionView.swift @@ -0,0 +1,109 @@ +// PopUpAddressDetailsContentSectionView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 02/07/2024 + Using Swift 5.0 + Running on macOS 14.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import TariCommon + +final class PopUpAddressDetailsContentSectionView: DynamicThemeView { + + // MARK: - Subviews + + @View private var titleLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.heavy.withSize(15.0) + return view + }() + + @View private var contentBackgroundView: UIView = { + let view = UIView() + view.layer.cornerRadius = 4.0 + return view + }() + + @View private(set) var contentView = T() + + // MARK: - Properties + + var title: String? { + get { titleLabel.text } + set { titleLabel.text = newValue } + } + + // MARK: - Initialisers + + override init() { + super.init() + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [titleLabel, contentBackgroundView].forEach(addSubview) + contentBackgroundView.addSubview(contentView) + + let constraints = [ + titleLabel.topAnchor.constraint(equalTo: topAnchor), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor), + contentBackgroundView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10.0), + contentBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor), + contentBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor), + contentBackgroundView.bottomAnchor.constraint(equalTo: bottomAnchor), + contentView.topAnchor.constraint(equalTo: contentBackgroundView.topAnchor, constant: 10.0), + contentView.leadingAnchor.constraint(equalTo: contentBackgroundView.leadingAnchor, constant: 10.0), + contentView.trailingAnchor.constraint(equalTo: contentBackgroundView.trailingAnchor, constant: -10.0), + contentView.bottomAnchor.constraint(equalTo: contentBackgroundView.bottomAnchor, constant: -10.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + contentBackgroundView.backgroundColor = theme.backgrounds.secondary + } +} diff --git a/MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressDetailsContentView.swift b/MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressDetailsContentView.swift new file mode 100644 index 00000000..4ee93e80 --- /dev/null +++ b/MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressDetailsContentView.swift @@ -0,0 +1,192 @@ +// PopUpAddressDetailsContentView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 02/07/2024 + Using Swift 5.0 + Running on macOS 14.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import TariCommon + +final class PopUpAddressDetailsContentView: DynamicThemeView { + + struct ViewModel { + let network: String + let networkDescription: String + let features: String + let featuresDescription: String + let viewKey: String? + let coreAddress: String + let checksum: String + } + + // MARK: - Subviews + + @View private var scrollView: ContentScrollView = { + let view = ContentScrollView() + view.alwaysBounceVertical = false + return view + }() + + @View private var stackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + view.spacing = 20.0 + return view + }() + + @View private var networkSection: PopUpAddressDetailsContentSectionView = { + let view = PopUpAddressDetailsContentSectionView() + view.title = localized("address_view.details.label.network.title") + return view + }() + + @View private var featuresSection: PopUpAddressDetailsContentSectionView = { + let view = PopUpAddressDetailsContentSectionView() + view.title = localized("address_view.details.label.features.title") + return view + }() + + @View private var viewKeySection: PopUpAddressDetailsContentSectionView = { + let view = PopUpAddressDetailsContentSectionView() + view.title = localized("address_view.details.label.view_key.title") + view.contentView.font = .Avenir.medium.withSize(14.0) + view.contentView.numberOfLines = 0 + return view + }() + + @View private var coreAddressSection: PopUpAddressDetailsContentSectionView = { + let view = PopUpAddressDetailsContentSectionView() + view.title = localized("address_view.details.label.address.title") + view.contentView.font = .Avenir.medium.withSize(14.0) + view.contentView.numberOfLines = 0 + return view + }() + + @View private var checksumSection: PopUpAddressDetailsContentSectionView = { + let view = PopUpAddressDetailsContentSectionView() + view.title = localized("address_view.details.label.checksum.title") + view.contentView.font = .Avenir.medium.withSize(14.0) + return view + }() + + @View private var buttonsSectionStackView: UIStackView = { + let view = UIStackView() + view.spacing = 10.0 + view.distribution = .fillEqually + return view + }() + + @View private var copyRawAddressButton: ActionButton = { + let view = ActionButton() + view.setTitle(localized("address_view.details.button.copy.base58"), for: .normal) + return view + }() + + @View private var copyEmojiAddressButton: ActionButton = { + let view = ActionButton() + view.setTitle(localized("address_view.details.button.copy.emojis"), for: .normal) + return view + }() + + // MARK: - Properties + + var onCopyRawAddressButtonTap: (() -> Void)? { + get { copyRawAddressButton.onTap } + set { copyRawAddressButton.onTap = newValue } + } + + var onCopyEmojiAddressButtonTap: (() -> Void)? { + get { copyEmojiAddressButton.onTap } + set { copyEmojiAddressButton.onTap = newValue } + } + + // MARK: - Initialisers + + override init() { + super.init() + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [scrollView, buttonsSectionStackView].forEach(addSubview) + scrollView.contentView.addSubview(stackView) + [networkSection, featuresSection, viewKeySection, coreAddressSection, checksumSection].forEach(stackView.addArrangedSubview) + [copyRawAddressButton, copyEmojiAddressButton].forEach(buttonsSectionStackView.addArrangedSubview) + + let constraints = [ + scrollView.topAnchor.constraint(equalTo: topAnchor), + scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), + buttonsSectionStackView.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 20.0), + buttonsSectionStackView.leadingAnchor.constraint(equalTo: leadingAnchor), + buttonsSectionStackView.trailingAnchor.constraint(equalTo: trailingAnchor), + buttonsSectionStackView.bottomAnchor.constraint(equalTo: bottomAnchor), + stackView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor), + stackView.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: scrollView.contentView.bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + viewKeySection.contentView.textColor = theme.text.body + coreAddressSection.contentView.textColor = theme.text.body + checksumSection.contentView.textColor = theme.text.body + } + + func update(viewModel: ViewModel) { + + networkSection.contentView.update(leadingText: viewModel.network, trailingText: viewModel.networkDescription) + featuresSection.contentView.update(leadingText: viewModel.features, trailingText: viewModel.featuresDescription) + viewKeySection.contentView.text = viewModel.viewKey + coreAddressSection.contentView.text = viewModel.coreAddress + checksumSection.contentView.text = viewModel.checksum + + viewKeySection.isHidden = viewModel.viewKey == nil + } +} diff --git a/MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressViewDoubleLabel.swift b/MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressViewDoubleLabel.swift new file mode 100644 index 00000000..f7b8eaa5 --- /dev/null +++ b/MobileWallet/Common/Pop-up/Components/PopUpAddressDetailsContentView/PopUpAddressViewDoubleLabel.swift @@ -0,0 +1,108 @@ +// PopUpAddressViewDoubleLabel.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 02/07/2024 + Using Swift 5.0 + Running on macOS 14.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import TariCommon + +final class PopUpAddressViewDoubleLabel: DynamicThemeView { + + // MARK: - Subviews + + @View private var stackView: UIStackView = { + let view = UIStackView() + view.spacing = 10.0 + view.alignment = .top + return view + }() + + @View private var leadingLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.medium.withSize(14.0) + return view + }() + + @View private var trailingLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.medium.withSize(14.0) + view.numberOfLines = 0 + view.setContentCompressionResistancePriority(.required, for: .vertical) + return view + }() + + // MARK: - Initialisers + + override init() { + super.init() + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + addSubview(stackView) + [leadingLabel, trailingLabel, UIView()].forEach(stackView.addArrangedSubview) + + let constraints = [ + stackView.topAnchor.constraint(equalTo: topAnchor), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + leadingLabel.textColor = theme.text.body + trailingLabel.textColor = theme.text.body + } + + func update(leadingText: String?, trailingText: String?) { + leadingLabel.text = leadingText + trailingLabel.text = trailingText + } +} diff --git a/MobileWallet/Common/Pop-up/Components/PopUpButtonsView.swift b/MobileWallet/Common/Pop-up/Components/PopUpButtonsView.swift index 0eed59e7..7d879a73 100644 --- a/MobileWallet/Common/Pop-up/Components/PopUpButtonsView.swift +++ b/MobileWallet/Common/Pop-up/Components/PopUpButtonsView.swift @@ -86,24 +86,17 @@ final class PopUpButtonsView: UIView { switch model.type { case .normal: - let actionButton = ActionButton() - actionButton.setImage(model.icon, for: .normal) - button = actionButton + button = ActionButton() case .destructive: let actionButton = ActionButton() - actionButton.variation = .destructive - actionButton.setImage(model.icon, for: .normal) + actionButton.style = .destructive button = actionButton case .text: let textButton = TextButton() - textButton.setVariation(.secondary) - textButton.setRightImage(model.icon) + textButton.style = .secondary button = textButton case .textDimmed: - let textButton = TextButton() - textButton.setVariation(.primary) - textButton.setRightImage(model.icon) - button = textButton + button = TextButton() } button.setTitle(model.title, for: .normal) diff --git a/MobileWallet/Common/Pop-up/Components/PopUpQRContentView.swift b/MobileWallet/Common/Pop-up/Components/PopUpQRContentView.swift index 9c2b5eaa..8e73cbd1 100644 --- a/MobileWallet/Common/Pop-up/Components/PopUpQRContentView.swift +++ b/MobileWallet/Common/Pop-up/Components/PopUpQRContentView.swift @@ -55,9 +55,9 @@ final class PopUpQRContentView: DynamicThemeView { // MARK: - Initailisers - override init() { + init(verticalPadding: CGFloat) { super.init() - setupConstraints() + setupConstraints(verticalPadding: verticalPadding) updateViewsState() } @@ -67,14 +67,14 @@ final class PopUpQRContentView: DynamicThemeView { // MARK: - Setups - private func setupConstraints() { + private func setupConstraints(verticalPadding: CGFloat) { addSubview(qrCodeView) let constraints = [ qrCodeView.topAnchor.constraint(equalTo: topAnchor), - qrCodeView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 58.0), - qrCodeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -58.0), + qrCodeView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: verticalPadding), + qrCodeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -verticalPadding), qrCodeView.bottomAnchor.constraint(equalTo: bottomAnchor), qrCodeView.heightAnchor.constraint(equalTo: qrCodeView.widthAnchor) ] diff --git a/MobileWallet/Common/Pop-up/Handlers/ScreenshotPopUpHandler.swift b/MobileWallet/Common/Pop-up/Handlers/ScreenshotPopUpHandler.swift new file mode 100644 index 00000000..b37e93eb --- /dev/null +++ b/MobileWallet/Common/Pop-up/Handlers/ScreenshotPopUpHandler.swift @@ -0,0 +1,111 @@ +// ScreenshotPopUpHandler.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 24/06/2024 + Using Swift 5.0 + Running on macOS 14.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import Combine + +final class ScreenshotPopUpHandler { + + // MARK: - Constants + + private let disabledViewControllers = [SendingTariViewController.self, YatTransactionViewController.self] + + // MARK: - Properties + + static let shared = ScreenshotPopUpHandler() + private var cancellables = Set() + + // MARK: - Initialisers + + private init() {} + + // MARK: - Actions + + func configure() { + NotificationCenter.default.publisher(for: UIApplication.userDidTakeScreenshotNotification) + .receive(on: DispatchQueue.main) + .filter { [weak self] _ in self?.canShowPopup() ?? false } + .sink { [weak self] _ in self?.showPopUp() } + .store(in: &cancellables) + } + + private func showPopUp() { + + var buttons = [ + PopUpDialogButtonModel(title: localized("screen_recording.pop_up.button.ok"), type: .normal) + ] + + if AppRouter.isNavigationReady { + buttons.append(PopUpDialogButtonModel(title: localized("screen_recording.pop_up.button.enable"), type: .text, callback: { [weak self] in self?.showScreenShotSettingsScreen() })) + } + + let model = PopUpDialogModel( + title: localized("screen_recording.pop_up.title"), + message: AppRouter.isNavigationReady ? localized("screen_recording.pop_up.message.normal") : localized("screen_recording.pop_up.message.simple"), + buttons: buttons, + hapticType: .error + ) + + Task { @MainActor in + PopUpPresenter.showPopUp(model: model) + } + } + + private func showScreenShotSettingsScreen() { + let controller = ScreenRecordingSettingsConstructor.buildScene(backButtonType: .close) + AppRouter.present(controller: controller) + } + + // MARK: - Handlers + + func canShowPopup() -> Bool { + + guard SecurityManager.shared.areScreenshotsDisabled else { return false } + + var topController = UIApplication.shared.topController + + if let navigationController = topController as? UINavigationController { + topController = navigationController.visibleViewController + } + + guard let topController else { return false } + return !disabledViewControllers.contains { topController.isKind(of: $0) } + } +} diff --git a/MobileWallet/Common/Pop-up/PopUpComponentsFactory.swift b/MobileWallet/Common/Pop-up/PopUpComponentsFactory.swift index c48bf426..44b81bc2 100644 --- a/MobileWallet/Common/Pop-up/PopUpComponentsFactory.swift +++ b/MobileWallet/Common/Pop-up/PopUpComponentsFactory.swift @@ -56,7 +56,7 @@ enum PopUpComponentsFactory { let view = PopUpButtonsView() models .map { model in - PopUpDialogButtonModel(title: model.title, icon: model.icon, type: model.type, callback: { + PopUpDialogButtonModel(title: model.title, type: model.type, callback: { PopUpPresenter.dismissPopup { model.callback?() } diff --git a/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift b/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift index 163a028d..7390a05d 100644 --- a/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift +++ b/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift @@ -82,13 +82,11 @@ struct PopUpDialogButtonModel { } let title: String - let icon: UIImage? let type: ButtonType let callback: (() -> Void)? - init(title: String, icon: UIImage? = nil, type: ButtonType, callback: (() -> Void)? = nil) { + init(title: String, type: ButtonType, callback: (() -> Void)? = nil) { self.title = title - self.icon = icon self.type = type self.callback = callback } @@ -103,7 +101,15 @@ struct MessageModel { let title: String let message: String? + let closeButtonTitle: String? let type: MessageType + + init(title: String, message: String?, closeButtonTitle: String? = nil, type: MessageType) { + self.title = title + self.message = message + self.closeButtonTitle = closeButtonTitle + self.type = type + } } extension PopUpPresenter { @@ -117,7 +123,14 @@ extension PopUpPresenter { } @MainActor static func show(message: MessageModel) { - let model = PopUpDialogModel(title: message.title, message: message.message, buttons: [], hapticType: makeHapticType(model: message)) + + var buttons = [PopUpDialogButtonModel]() + + if let closeButtonTitle = message.closeButtonTitle { + buttons.append(PopUpDialogButtonModel(title: closeButtonTitle, type: .text)) + } + + let model = PopUpDialogModel(title: message.title, message: message.message, buttons: buttons, hapticType: makeHapticType(model: message)) showPopUp(model: model) log(message: message) } @@ -135,14 +148,19 @@ extension PopUpPresenter { log(message: message) } - @MainActor static func showQRCodeDialog(title: String) -> PopUpQRContentView { + @MainActor static func showQRCodeDialog(title: String? = nil, verticalPadding: CGFloat = 24.0, additionalButtons: [PopUpDialogButtonModel] = []) -> PopUpQRContentView { - let headerView = PopUpHeaderView() - let contentView = PopUpQRContentView() + var headerView: PopUpHeaderView? + let contentView = PopUpQRContentView(verticalPadding: verticalPadding) let buttonsView = PopUpButtonsView() - headerView.label.text = title - buttonsView.addButton(model: PopUpDialogButtonModel(title: localized("common.close"), icon: nil, type: .text, callback: { PopUpPresenter.dismissPopup() })) + if let title { + headerView = PopUpHeaderView() + headerView?.label.text = title + } + + additionalButtons.forEach(buttonsView.addButton(model:)) + buttonsView.addButton(model: PopUpDialogButtonModel(title: localized("common.close"), type: .text, callback: { PopUpPresenter.dismissPopup() })) let popUp = TariPopUp(headerSection: headerView, contentSection: contentView, buttonsSection: buttonsView) PopUpPresenter.show(popUp: popUp) diff --git a/MobileWallet/Common/Pop-up/PopUpPresenter.swift b/MobileWallet/Common/Pop-up/PopUpPresenter.swift index d6110362..3eb38aea 100644 --- a/MobileWallet/Common/Pop-up/PopUpPresenter.swift +++ b/MobileWallet/Common/Pop-up/PopUpPresenter.swift @@ -64,12 +64,13 @@ enum PopUpPresenter { attributes.entryBackground = .clear attributes.screenBackground = .color(color: EKColor(.Static.popupOverlay)) attributes.positionConstraints.size.width = .offset(value: sideOffset) - attributes.positionConstraints.maxSize.height = .ratio(value: hasNotch ? 0.85 : 0.9) + attributes.positionConstraints.maxSize.height = hasNotch ? .offset(value: 48.0) : .ratio(value: 0.95) attributes.positionConstraints.safeArea = .empty(fillSafeArea: false) attributes.screenInteraction = .absorbTouches attributes.entryInteraction = .forward attributes.displayDuration = .infinity attributes.precedence = .enqueue(priority: .normal) + attributes.positionConstraints.size.height = .fill return attributes }() @@ -87,7 +88,10 @@ enum PopUpPresenter { attributes.hapticFeedbackType = makeHapticFeedbackType(configuration: configuration) } - SwiftEntryKit.display(entry: popUp, using: attributes) + DispatchQueue.main.async { + SwiftEntryKit.display(entry: popUp, using: attributes) + } + UIApplication.shared.hideKeyboard() } diff --git a/MobileWallet/Common/Pop-up/TariPopUp.swift b/MobileWallet/Common/Pop-up/TariPopUp.swift index 26700216..af5a311d 100644 --- a/MobileWallet/Common/Pop-up/TariPopUp.swift +++ b/MobileWallet/Common/Pop-up/TariPopUp.swift @@ -75,14 +75,19 @@ final class TariPopUp: DynamicThemeView { private func setupConstraints(headerSection: UIView?, contentSection: UIView?, buttonsSection: UIView?) { - addSubview(secureContentView) + @View var spacingView = UIView() + + [spacingView, secureContentView].forEach(addSubview) secureContentView.view.addSubview(backgroundView) - let backgroundViewTopConstraint = backgroundView.topAnchor.constraint(equalTo: secureContentView.view.topAnchor) + let backgroundViewTopConstraint = backgroundView.topAnchor.constraint(equalTo: spacingView.bottomAnchor) self.backgroundViewTopConstraint = backgroundViewTopConstraint var constraints = [ - secureContentView.topAnchor.constraint(equalTo: topAnchor), + spacingView.topAnchor.constraint(equalTo: topAnchor), + spacingView.leadingAnchor.constraint(equalTo: leadingAnchor), + spacingView.trailingAnchor.constraint(equalTo: trailingAnchor), + secureContentView.topAnchor.constraint(equalTo: spacingView.bottomAnchor), secureContentView.leadingAnchor.constraint(equalTo: leadingAnchor), secureContentView.trailingAnchor.constraint(equalTo: trailingAnchor), secureContentView.bottomAnchor.constraint(equalTo: bottomAnchor), @@ -130,7 +135,7 @@ final class TariPopUp: DynamicThemeView { // MARK: - Helpers private func makeConstraints(forView view: UIView, viewOnTop: UIView?) -> [NSLayoutConstraint] { - let anchor = viewOnTop?.bottomAnchor ?? topAnchor + let anchor = viewOnTop?.bottomAnchor ?? secureContentView.view.topAnchor var constraints = [ view.topAnchor.constraint(equalTo: anchor, constant: 22.0), view.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 22.0), diff --git a/MobileWallet/Common/Protocols/AddressPoisoningDataHandler.swift b/MobileWallet/Common/Protocols/AddressPoisoningDataHandler.swift index 413e226f..20fcb4dc 100644 --- a/MobileWallet/Common/Protocols/AddressPoisoningDataHandler.swift +++ b/MobileWallet/Common/Protocols/AddressPoisoningDataHandler.swift @@ -44,25 +44,25 @@ enum AddressPoisoningDataHandler { static func handleAddressSelection(paymentInfo: PaymentInfo, onContinue: ((PaymentInfo) -> Void)?) { - let rawAddress = paymentInfo.address + let addressComponents = paymentInfo.addressComponents - guard !isAddressTrusted(address: rawAddress) else { + guard !isAddressTrusted(addressIdentifier: addressComponents.uniqueIdentifier) else { onContinue?(paymentInfo) return } Task { @MainActor in do { - let address = try TariAddress(hex: rawAddress) + let address = try TariAddress(base58: addressComponents.fullRaw) let similarAddresses = try await AddressPoisoningManager.shared.similarAddresses(address: address, includeInputAddress: true) .map { similarAddress in - PopUpAddressPoisoningContentCell.ViewModel( + return PopUpAddressPoisoningContentCell.ViewModel( id: UUID(), emojiID: similarAddress.emojiID, name: similarAddress.alias, transactionsCount: similarAddress.transactionsCount, lastTransaction: similarAddress.lastTransaction, - isTrusted: isAddressTrusted(address: similarAddress.address) + isTrusted: isAddressTrusted(addressIdentifier: similarAddress.address) ) } @@ -79,13 +79,13 @@ enum AddressPoisoningDataHandler { private static func confirmAddressSelection(emojiID: String, originalPaymentInfo: PaymentInfo, isTrusted: Bool, onContinue: ((PaymentInfo) -> Void)?) { do { - let address = try TariAddress(emojiID: emojiID).byteVector.hex - updateSettings(hex: address, isTrusted: isTrusted) + let addressComponents = try TariAddress(emojiID: emojiID).components + updateSettings(uniqueIdentifier: addressComponents.uniqueIdentifier, isTrusted: isTrusted) - if address == originalPaymentInfo.address { + if addressComponents.uniqueIdentifier == originalPaymentInfo.addressComponents.uniqueIdentifier { onContinue?(originalPaymentInfo) } else { - onContinue?(PaymentInfo(address: address, alias: nil, yatID: nil, amount: nil, feePerGram: nil, note: nil)) + onContinue?(PaymentInfo(addressComponents: addressComponents, alias: nil, yatID: nil, amount: nil, feePerGram: nil, note: nil)) } } catch { showErrorPopUp(error: error) @@ -111,21 +111,21 @@ enum AddressPoisoningDataHandler { // MARK: - Helpers - private static func updateSettings(hex: String, isTrusted: Bool) { + private static func updateSettings(uniqueIdentifier: String, isTrusted: Bool) { if GroupUserDefaults.trustedAddresses == nil { GroupUserDefaults.trustedAddresses = Set() } guard isTrusted else { - GroupUserDefaults.trustedAddresses?.remove(hex) + GroupUserDefaults.trustedAddresses?.remove(uniqueIdentifier) return } - GroupUserDefaults.trustedAddresses?.insert(hex) + GroupUserDefaults.trustedAddresses?.insert(uniqueIdentifier) } - private static func isAddressTrusted(address: String) -> Bool { - GroupUserDefaults.trustedAddresses?.contains { $0 == address } ?? false + private static func isAddressTrusted(addressIdentifier: String) -> Bool { + GroupUserDefaults.trustedAddresses?.contains { $0 == addressIdentifier } ?? false } } diff --git a/MobileWallet/Common/Theme.swift b/MobileWallet/Common/Theme.swift index b38452f6..1b500d2b 100644 --- a/MobileWallet/Common/Theme.swift +++ b/MobileWallet/Common/Theme.swift @@ -65,7 +65,6 @@ struct Images { let forwardArrow = UIImage(named: "ForwardArrow") let close = UIImage(named: "Close") let share = UIImage(named: "share") - let txFee = UIImage(named: "TxFee") let handWave = UIImage(named: "HandWave") let attentionIcon = UIImage(named: "AttentionIcon") let scheduledIcon = UIImage(named: "ScheduledIcon") @@ -77,7 +76,6 @@ struct Images { // Amount let delete = UIImage(named: "numpad-delete") -// let helpButton = UIImage(named: "QuestionMark") } struct Fonts { @@ -115,7 +113,6 @@ struct Fonts { let txScreenSubheadingLabel = UIFont.Avenir.medium.withSize(13.0) let txScreenTextLabel = UIFont.Avenir.roman.withSize(14.0) let txFeeLabel = UIFont.Avenir.heavy.withSize(14.0) - let txFeeButton = UIFont.Avenir.roman.withSize(13.0) let txSectionTitleLabel = UIFont.Avenir.medium.withSize(16.0) // Sending tari screen @@ -130,9 +127,7 @@ struct Fonts { let feedbackPopupDescription = UIFont.Avenir.medium.withSize(14.0) // Simple text button - let textButton = UIFont.Avenir.medium.withSize(14.0) let copyButton = UIFont.Avenir.heavy.withSize(14.0) - let textButtonCancel = UIFont.Avenir.medium.withSize(12.0) // Add recipient view let searchContactsInputBoxText = UIFont.Avenir.roman.withSize(14.0) @@ -145,14 +140,12 @@ struct Fonts { // Add note screen let addNoteTitleLabel = UIFont.Avenir.heavy.withSize(16.0) let addNoteInputView = UIFont.Avenir.medium.withSize(20.0) - let searchGiphyButtonTitle = UIFont.Avenir.black.withSize(9.0) // Refresh view let refreshViewLabel = UIFont.Avenir.heavy.withSize(12.0) // App table view let systemTableViewCellMarkDescription = UIFont.Avenir.medium.withSize(14.0) - let tableViewSection = UIFont.Avenir.medium.withSize(14.0) // Restore pending view let restorePendingViewTitle = UIFont.Avenir.light.withSize(18.0) @@ -175,9 +168,6 @@ struct Fonts { let settingsPasswordPlaceholder = UIFont.Avenir.roman.withSize(14.0) let settingsPasswordWarning = UIFont.Avenir.heavy.withSize(13.0) - // Text Field - let textField = UIFont.Avenir.light.withSize(14.0) - // Restore Wallet From Seed Words let restoreFromSeedWordsToken = UIFont.Avenir.heavy.withSize(14.0) let restoreFormSeedWordsDescription = UIFont.Avenir.medium.withSize(14.0) diff --git a/MobileWallet/Common/Tools/AddressViewDefaultActions.swift b/MobileWallet/Common/Tools/AddressViewDefaultActions.swift new file mode 100644 index 00000000..7db9521d --- /dev/null +++ b/MobileWallet/Common/Tools/AddressViewDefaultActions.swift @@ -0,0 +1,86 @@ +// AddressViewDefaultActions.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 10/07/2024 + Using Swift 5.0 + Running on macOS 14.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +enum AddressViewDefaultActions { + + static func showDetailsAction(addressComponents: TariAddressComponents) -> () -> Void { + { + Task { + await showDetailsPopup( + viewModel: PopUpAddressDetailsContentView.ViewModel( + network: addressComponents.network, + networkDescription: addressComponents.networkName, + features: addressComponents.features, + featuresDescription: addressComponents.featuresNames, + viewKey: addressComponents.viewKey, + coreAddress: addressComponents.spendKey, + checksum: addressComponents.checksum + ), + rawAddress: addressComponents.fullRaw, + emojiAddress: addressComponents.fullEmoji + ) + } + } + } + + @MainActor private static func showDetailsPopup(viewModel: PopUpAddressDetailsContentView.ViewModel, rawAddress: String, emojiAddress: String) async { + + let headerSection = PopUpHeaderView() + headerSection.label.text = localized("address_view.details.label.title") + + let contentSection = PopUpAddressDetailsContentView() + contentSection.update(viewModel: viewModel) + contentSection.onCopyRawAddressButtonTap = { handleCopyAction(address: rawAddress) } + contentSection.onCopyEmojiAddressButtonTap = { handleCopyAction(address: emojiAddress) } + + let buttonsSection = PopUpButtonsView() + buttonsSection.addButton(model: PopUpDialogButtonModel(title: localized("common.close"), type: .text, callback: { PopUpPresenter.dismissPopup() })) + + let popUp = TariPopUp(headerSection: headerSection, contentSection: contentSection, buttonsSection: buttonsSection) + PopUpPresenter.show(popUp: popUp) + } + + private static func handleCopyAction(address: String) { + UIPasteboard.general.string = address + PopUpPresenter.dismissPopup() + } +} diff --git a/MobileWallet/Common/Views/Address View/AddressView.swift b/MobileWallet/Common/Views/Address View/AddressView.swift new file mode 100644 index 00000000..5f599a91 --- /dev/null +++ b/MobileWallet/Common/Views/Address View/AddressView.swift @@ -0,0 +1,194 @@ +// AddressView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 28/06/2024 + Using Swift 5.0 + Running on macOS 14.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import TariCommon + +final class AddressView: DynamicThemeView { + + enum TextType { + case truncated(prefix: String, suffix: String) + case single(_ text: String) + } + + struct TruncatedText { + let prefix: String + let suffix: String + } + + struct ViewModel { + let prefix: String? + let text: TextType + let isDetailsButtonVisible: Bool + } + + // MARK: - Subviews + + @View private var stackView: UIStackView = { + let view = UIStackView() + view.alignment = .center + return view + }() + + @View private var prefixLabel = UILabel() + @View private var firstSeparator = UIView() + + @View private var addressPrefixLabel = UILabel() + + @View private var dotsView: UILabel = { + let view = UILabel() + view.text = .dots + return view + }() + + @View private var addressSuffixLabel = UILabel() + @View private var secondSeparator = UIView() + + @View private var viewDetailsButton: BaseButton = { + let view = BaseButton() + view.setImage(.Icons.General.info, for: .normal) + view.showsMenuAsPrimaryAction = true + return view + }() + + @View private var singleLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.medium.withSize(17.0) + view.textAlignment = .center + view.isHidden = true + return view + }() + + // MARK: - Properties + + var isCompact: Bool = false { + didSet { updateViews() } + } + + var onViewDetailsButtonTap: (() -> Void)? { + get { viewDetailsButton.onTap } + set { viewDetailsButton.onTap = newValue } + } + + // MARK: - Initializers + + override init() { + super.init() + setupConstraints() + updateViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + addSubview(stackView) + [prefixLabel, firstSeparator, addressPrefixLabel, dotsView, addressSuffixLabel, singleLabel, secondSeparator, viewDetailsButton].forEach(stackView.addArrangedSubview) + + let constraints = [ + stackView.topAnchor.constraint(equalTo: topAnchor), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor), + firstSeparator.widthAnchor.constraint(equalToConstant: 1.0), + firstSeparator.heightAnchor.constraint(equalToConstant: 14.0), + secondSeparator.widthAnchor.constraint(equalToConstant: 1.0), + secondSeparator.heightAnchor.constraint(equalToConstant: 14.0), + viewDetailsButton.heightAnchor.constraint(equalToConstant: 22.0), + viewDetailsButton.widthAnchor.constraint(equalToConstant: 22.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + viewDetailsButton.tintColor = theme.icons.active + firstSeparator.backgroundColor = theme.text.lightText + secondSeparator.backgroundColor = theme.text.lightText + dotsView.textColor = theme.text.lightText + viewDetailsButton.configuration?.baseBackgroundColor = theme.text.links + viewDetailsButton.tintColor = theme.text.links + } + + func update(viewModel: ViewModel) { + + prefixLabel.text = viewModel.prefix + prefixLabel.isHidden = viewModel.prefix == nil + firstSeparator.isHidden = viewModel.prefix == nil + + switch viewModel.text { + case let .truncated(prefix, suffix): + addressPrefixLabel.text = prefix + addressSuffixLabel.text = suffix + addressPrefixLabel.isHidden = false + dotsView.isHidden = false + addressSuffixLabel.isHidden = false + singleLabel.isHidden = true + case let .single(text): + singleLabel.text = text + addressPrefixLabel.isHidden = true + dotsView.isHidden = true + addressSuffixLabel.isHidden = true + singleLabel.isHidden = false + } + + viewDetailsButton.isHidden = !viewModel.isDetailsButtonVisible + secondSeparator.isHidden = !viewModel.isDetailsButtonVisible + } + + private func updateViews() { + + let fontSize = isCompact ? 13.0 : 17.0 + + prefixLabel.font = .Avenir.medium.withSize(fontSize) + addressPrefixLabel.font = .Avenir.medium.withSize(fontSize) + dotsView.font = .Avenir.medium.withSize(fontSize) + addressSuffixLabel.font = .Avenir.medium.withSize(fontSize) + singleLabel.font = .Avenir.medium.withSize(fontSize) + + stackView.spacing = isCompact ? 4.0 : 8.0 + } +} diff --git a/MobileWallet/Screens/Home/Transaction History/Views/TransactionHistoryHeaderView.swift b/MobileWallet/Common/Views/Address View/RoundedAddressView.swift similarity index 61% rename from MobileWallet/Screens/Home/Transaction History/Views/TransactionHistoryHeaderView.swift rename to MobileWallet/Common/Views/Address View/RoundedAddressView.swift index 1b5ed207..19436977 100644 --- a/MobileWallet/Screens/Home/Transaction History/Views/TransactionHistoryHeaderView.swift +++ b/MobileWallet/Common/Views/Address View/RoundedAddressView.swift @@ -1,10 +1,10 @@ -// TransactionHistoryHeaderView.swift +// RoundedAddressView.swift /* Package MobileWallet - Created by Adrian Truszczynski on 11/08/2021 + Created by Adrian Truszczyński on 18/07/2024 Using Swift 5.0 - Running on macOS 12.0 + Running on macOS 14.4 Copyright 2019 The Tari Project @@ -40,27 +40,29 @@ import TariCommon -final class TransactionHistoryHeaderView: DynamicThemeHeaderFooterView { +final class RoundedAddressView: DynamicThemeView { // MARK: - Subviews - @View private var titleLabel: UILabel = { - let view = UILabel() - view.font = Theme.shared.fonts.txSectionTitleLabel - return view - }() + @View private var addressView = AddressView() // MARK: - Properties - var title: String? { - get { titleLabel.text } - set { titleLabel.text = newValue } + var isCompact: Bool { + get { addressView.isCompact } + set { addressView.isCompact = newValue } } - // MARK: - Initializers + var onViewDetailsButtonTap: (() -> Void)? { + get { addressView.onViewDetailsButtonTap } + set { addressView.onViewDetailsButtonTap = newValue } + } + + // MARK: - Initialisers - override init(reuseIdentifier: String?) { - super.init(reuseIdentifier: reuseIdentifier) + override init() { + super.init() + setupViews() setupConstraints() } @@ -70,15 +72,20 @@ final class TransactionHistoryHeaderView: DynamicThemeHeaderFooterView { // MARK: - Setups + private func setupViews() { + layer.cornerRadius = 10.0 + } + private func setupConstraints() { - contentView.addSubview(titleLabel) + addSubview(addressView) let constraints = [ - titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20.0), - titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 22.0), - titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -22.0), - titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20.0) + addressView.topAnchor.constraint(equalTo: topAnchor, constant: 10.0), + addressView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 10.0), + addressView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -10.0), + addressView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10.0), + addressView.centerXAnchor.constraint(equalTo: centerXAnchor) ] NSLayoutConstraint.activate(constraints) @@ -88,7 +95,10 @@ final class TransactionHistoryHeaderView: DynamicThemeHeaderFooterView { override func update(theme: ColorTheme) { super.update(theme: theme) - tintColor = theme.backgrounds.primary - titleLabel.textColor = theme.text.heading + backgroundColor = theme.backgrounds.primary + } + + func update(viewModel: AddressView.ViewModel) { + addressView.update(viewModel: viewModel) } } diff --git a/MobileWallet/Common/Views/ContactSearchView.swift b/MobileWallet/Common/Views/ContactSearchView.swift index 6cacd183..769e372b 100644 --- a/MobileWallet/Common/Views/ContactSearchView.swift +++ b/MobileWallet/Common/Views/ContactSearchView.swift @@ -64,9 +64,7 @@ final class ContactSearchView: DynamicThemeView { @View private(set) var qrButton: PulseButton = { let view = PulseButton() view.setImage(.Icons.General.QR, for: .normal) - view.contentHorizontalAlignment = .fill - view.contentVerticalAlignment = .fill - view.imageEdgeInsets = UIEdgeInsets(top: 5.0, left: 5.0, bottom: 5.0, right: 5.0) + view.imageView?.contentMode = .scaleAspectFit return view }() @@ -84,6 +82,10 @@ final class ContactSearchView: DynamicThemeView { didSet { updateViews() } } + var isYatLogoVisible: Bool = false { + didSet { updateViews() } + } + var isPreviewButtonVisible: Bool = false { didSet { updateViews() } } @@ -148,7 +150,7 @@ final class ContactSearchView: DynamicThemeView { private func updateViews() { qrButton.isHidden = !isQrButtonVisible yatPreviewButton.isHidden = !isPreviewButtonVisible - yatIconView.isHidden = !isPreviewButtonVisible + yatIconView.isHidden = !isYatLogoVisible } private func updatePreview() { diff --git a/MobileWallet/Libraries/TariLib/Core/App Setup/AppConfigurator.swift b/MobileWallet/Libraries/TariLib/Core/App Setup/AppConfigurator.swift index 1d797835..57cdee2f 100644 --- a/MobileWallet/Libraries/TariLib/Core/App Setup/AppConfigurator.swift +++ b/MobileWallet/Libraries/TariLib/Core/App Setup/AppConfigurator.swift @@ -85,6 +85,7 @@ final class AppConfigurator { StatusLoggerManager.shared.configure() DataFlowManager.shared.configure() LocalNotificationsManager.shared.configure() + ScreenshotPopUpHandler.shared.configure() } private func configureCallbacks() { diff --git a/MobileWallet/Libraries/TariLib/Core/FFI/ByteVector.swift b/MobileWallet/Libraries/TariLib/Core/FFI/ByteVector.swift index a96a3ff2..5613b7f8 100644 --- a/MobileWallet/Libraries/TariLib/Core/FFI/ByteVector.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFI/ByteVector.swift @@ -110,8 +110,4 @@ extension ByteVector { var data: Data { get throws { Data(try bytes) } } - - var string: String? { - get throws { String(data: try data, encoding: .utf8) } - } } diff --git a/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift index 06f9f33d..341197e9 100644 --- a/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/PublicKey.swift @@ -40,10 +40,6 @@ final class PublicKey { - enum InternalError: Error { - case invalidHex - } - // MARK: - Properties let pointer: OpaquePointer @@ -57,6 +53,16 @@ final class PublicKey { } } + var emojis: String { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = public_key_get_emoji_encoding(pointer, errorCodePointer) + guard errorCode == 0, let result else { throw WalletError(code: errorCode) } + return String(cString: result) + } + } + // MARK: - Initialisers init(pointer: OpaquePointer) { @@ -65,8 +71,6 @@ final class PublicKey { init(hex: String) throws { - guard hex.count == 64, hex.rangeOfCharacter(from: .hexadecimal) != nil else { throw InternalError.invalidHex } - var errorCode: Int32 = -1 let errorCodePointer = PointerHandler.pointer(for: &errorCode) diff --git a/MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddress.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddress.swift index 5038ff90..3c8c2ac0 100644 --- a/MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddress.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddress.swift @@ -40,8 +40,12 @@ final class TariAddress { - enum InternalError: Error { - case invalidHex + struct Network { + let value: UInt8 + } + + struct Features { + let value: UInt8 } // MARK: - Properties @@ -68,22 +72,71 @@ final class TariAddress { } } + var network: Network { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = tari_address_network_u8(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return Network(value: result) + } + } + + var features: Features { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = tari_address_features_u8(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return Features(value: result) + } + } + + var viewKey: PublicKey? { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = tari_address_view_key(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + guard let result else { return nil } + return PublicKey(pointer: result) + } + } + + var spendKey: PublicKey { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = tari_address_spend_key(pointer, errorCodePointer) + guard errorCode == 0, let result else { throw WalletError(code: errorCode) } + return PublicKey(pointer: result) + } + } + + var checksum: UInt8 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = tari_address_checksum_u8(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + // MARK: - Initialisers init(pointer: OpaquePointer) { self.pointer = pointer } - init(hex: String) throws { - - guard hex.count == 66, hex.rangeOfCharacter(from: .hexadecimal) != nil else { throw InternalError.invalidHex } + init(base58: String) throws { var errorCode: Int32 = -1 let errorCodePointer = PointerHandler.pointer(for: &errorCode) - let result = tari_address_from_hex(hex, errorCodePointer) + let pointer = tari_address_from_base58(base58, errorCodePointer) - guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + guard errorCode == 0, let pointer else { throw WalletError(code: errorCode) } self.pointer = pointer } @@ -105,18 +158,61 @@ 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 - } +extension TariAddress { + @available(*, deprecated, message: "This getter is obsolete and it will be removed in the future.") var publicKey: String { get throws { try String(byteVector.hex.dropLast(2)) } } - var isUnknownUser: Bool { - get throws { try publicKey.filter { $0 == "0" }.count == 64 } + static func makeTariAddress(input: String) throws -> TariAddress { + do { return try TariAddress(emojiID: input) } catch {} + return try TariAddress(base58: input) + } + + var components: TariAddressComponents { + get throws { try TariAddressComponents(address: self) } + } +} + +extension TariAddress.Network { + + var name: String { + switch value { + case 0: + return "MainNet" + case 1: + return "StageNet" + case 2: + return "NextNet" + default: + return "TestNet" + } + } +} + +extension TariAddress.Features { + + enum Feature: UInt8, CaseIterable { + case oneSided = 0b00000001 + case interactive = 0b00000010 + } + + var names: [String] { + Feature.allCases + .filter { value.flag(bitmask: $0.rawValue) } + .map(\.name) + } +} + +extension TariAddress.Features.Feature { + + var name: String { + switch self { + case .oneSided: + return localized("address_features.one_sided") + case .interactive: + return localized("address_features.interactive") + } } } diff --git a/MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddressComponents.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddressComponents.swift new file mode 100644 index 00000000..2238eead --- /dev/null +++ b/MobileWallet/Libraries/TariLib/Core/FFI/Keys/TariAddressComponents.swift @@ -0,0 +1,89 @@ +// TariAddressComponents.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 10/07/2024 + Using Swift 5.0 + Running on macOS 14.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Base58Swift + +struct TariAddressComponents { + + let network: String + let networkName: String + let features: String + let featuresNames: String + let viewKey: String? + let spendKey: String + let checksum: String + + let fullRaw: String + let fullEmoji: String + let isUnknownAddress: Bool +} + +extension TariAddressComponents { + + var networkAndFeatures: String { network + features } + var coreAddressPrefix: String { String(coreAddress.prefix(3)) } + var coreAddressSuffix: String { String(coreAddress.suffix(3)) } + var uniqueIdentifier: String { network + spendKey } + var formattedCoreAddress: String { networkAndFeatures + " | " + coreAddressPrefix + .dots + coreAddressSuffix } + + private var coreAddress: String { [viewKey, spendKey, checksum].compactMap { $0 }.joined() } + + init(address: TariAddress) throws { + + let addressNetwork = try address.network + let addressFeatures = try address.features + + let networkBase58 = Base58.base58Encode([addressNetwork.value]) + let featuresBase58 = Base58.base58Encode([addressFeatures.value]) + let addressData = try address.byteVector.data.dropFirst(2) + let addressBase58 = Base58.base58Encode([UInt8](addressData)) + + network = try addressNetwork.value.tariEmoji + networkName = addressNetwork.name + features = try addressFeatures.value.tariEmoji + featuresNames = addressFeatures.names.joined(separator: ", ") + viewKey = try address.viewKey?.emojis + spendKey = try address.spendKey.emojis + checksum = try address.checksum.tariEmoji + fullRaw = [networkBase58, featuresBase58, addressBase58].joined() + fullEmoji = try address.emojis + isUnknownAddress = try address.spendKey.byteVector.bytes.first { $0 != 0 } == nil + } +} diff --git a/MobileWallet/Common/Views/RoundedInputField.swift b/MobileWallet/Libraries/TariLib/Core/FFI/TariEmojis.swift similarity index 58% rename from MobileWallet/Common/Views/RoundedInputField.swift rename to MobileWallet/Libraries/TariLib/Core/FFI/TariEmojis.swift index ed29580a..901d014e 100644 --- a/MobileWallet/Common/Views/RoundedInputField.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFI/TariEmojis.swift @@ -1,10 +1,10 @@ -// RoundedInputField.swift +// TariEmojis.swift /* Package MobileWallet - Created by Adrian Truszczynski on 20/07/2021 + Created by Adrian Truszczyński on 15/07/2024 Using Swift 5.0 - Running on macOS 12.0 + Running on macOS 14.4 Copyright 2019 The Tari Project @@ -38,36 +38,43 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit +final class TariEmojis { -final class RoundedInputField: DynamicThemeTextField { + var length: UInt32 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = emoji_set_get_length(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } - // MARK: - Initializers + private let pointer: OpaquePointer - override init() { - super.init() - setupView() + init() { + pointer = get_emoji_set() } - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + func emoji(at index: UInt32) throws -> ByteVector { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = emoji_set_get_at(pointer, index, errorCodePointer) + guard errorCode == 0, let result else { throw WalletError(code: errorCode) } + return ByteVector(pointer: result) } - // MARK: - Setups - - private func setupView() { - layer.cornerRadius = 10.0 + deinit { + emoji_set_destroy(pointer) } +} - // MARK: - Updates +extension TariEmojis { - override func update(theme: ColorTheme) { - super.update(theme: theme) - backgroundColor = theme.backgrounds.primary + var all: [String] { + get throws { + try (0.. CGRect { bounds.insetBy(dx: 12.0, dy: 12.0) } - override func editingRect(forBounds bounds: CGRect) -> CGRect { textRect(forBounds: bounds) } } diff --git a/MobileWallet/Libraries/TariLib/Core/FFI/Wallet.swift b/MobileWallet/Libraries/TariLib/Core/FFI/Wallet.swift index 23e3a670..95de0ae8 100644 --- a/MobileWallet/Libraries/TariLib/Core/FFI/Wallet.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFI/Wallet.swift @@ -119,6 +119,10 @@ final class Wallet { WalletCallbacksManager.shared.post(name: .baseNodeConnectionStatusUpdate, object: status) } + let walletScannedHeightCallback: (@convention(c) (UInt64) -> Void) = { + WalletCallbacksManager.shared.post(name: .walletScannedHeight, object: $0) + } + let baseNodeStateCallback: (@convention(c) (OpaquePointer?) -> Void) = { pointer in WalletCallbacksManager.shared.post(name: .baseNodeStateUpdate, object: pointer) } @@ -158,6 +162,7 @@ final class Wallet { trasactionValidationCompleteCallback, storedMessagesReceivedCallback, connectivityStatusCallback, + walletScannedHeightCallback, baseNodeStateCallback, isRecoveryInProgressPointer, errorCodePointer @@ -170,7 +175,7 @@ final class Wallet { // MARK: - Deinitialiser func destroy() { - Logger.log(message: "Wallet destroyed", domain: .general, level: .info) wallet_destroy(pointer) + Logger.log(message: "Wallet destroyed", domain: .general, level: .info) } } diff --git a/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift b/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift index 57f6f4c5..4fb1831a 100644 --- a/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift +++ b/MobileWallet/Libraries/TariLib/Core/FFIWalletManager.swift @@ -55,6 +55,8 @@ final class FFIWalletManager { // MARK: - Properties @Published private(set) var baseNodeConnectionStatus: BaseNodeConnectivityStatus = .offline + @Published private(set) var scannedHeight: UInt64 = 0 + @Published private(set) var isWalletConnected: Bool = false { didSet { Logger.log(message: "isWalletConnected: \(isWalletConnected)", domain: .general, level: .info) } } @@ -81,9 +83,14 @@ final class FFIWalletManager { // MARK: - Setups private func setupCallbacks() { + WalletCallbacksManager.shared.baseNodeConnectionStatus .assign(to: \.baseNodeConnectionStatus, on: self) .store(in: &cancelables) + + WalletCallbacksManager.shared.walletScannedHeight + .assign(to: \.scannedHeight, on: self) + .store(in: &cancelables) } // MARK: - Actions @@ -363,13 +370,13 @@ final class FFIWalletManager { return result.pointee } - func sendTransaction(address: TariAddress, amount: UInt64, feePerGram: UInt64, message: String, isOneSidedPayment: Bool) throws -> UInt64 { + func sendTransaction(address: TariAddress, amount: UInt64, feePerGram: UInt64, message: String, isOneSidedPayment: Bool, paymentID: String) throws -> UInt64 { let wallet = try exisingWallet var errorCode: Int32 = -1 let errorCodePointer = PointerHandler.pointer(for: &errorCode) - let result = wallet_send_transaction(wallet.pointer, address.pointer, amount, nil, feePerGram, message, isOneSidedPayment, errorCodePointer) + let result = wallet_send_transaction(wallet.pointer, address.pointer, amount, nil, feePerGram, message, isOneSidedPayment, paymentID, errorCodePointer) guard errorCode == 0 else { throw WalletError(code: errorCode) } return result diff --git a/MobileWallet/Libraries/TariLib/Core/Services/CoreTariService.swift b/MobileWallet/Libraries/TariLib/Core/Services/CoreTariService.swift index c77d73a5..90470d75 100644 --- a/MobileWallet/Libraries/TariLib/Core/Services/CoreTariService.swift +++ b/MobileWallet/Libraries/TariLib/Core/Services/CoreTariService.swift @@ -39,6 +39,7 @@ */ protocol MainServiceable: AnyObject { + var connection: TariConnectionService { get } var validation: TariValidationService { get } var walletBalance: TariBalanceService { get } } diff --git a/MobileWallet/Libraries/TariLib/Core/Services/TariContactsService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariContactsService.swift index 0a37ea5c..48a34da2 100644 --- a/MobileWallet/Libraries/TariLib/Core/Services/TariContactsService.swift +++ b/MobileWallet/Libraries/TariLib/Core/Services/TariContactsService.swift @@ -54,7 +54,7 @@ final class TariContactsService: CoreTariService { try walletManager.remove(contact: contact) } - func findContact(hex: String) throws -> Contact? { - try allContacts.first { try $0.address.byteVector.hex == hex } + func findContact(uniqueIdentifier: String) throws -> Contact? { + try allContacts.first { try $0.address.components.uniqueIdentifier == uniqueIdentifier } } } diff --git a/MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift index 437315db..738cdd56 100644 --- a/MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift +++ b/MobileWallet/Libraries/TariLib/Core/Services/TariRecoveryService.swift @@ -49,7 +49,14 @@ final class TariRecoveryService: CoreTariService { // MARK: - Actions func startRecovery(recoveredOutputMessage: String) throws -> Bool { - guard let selectedBaseNode = NetworkManager.shared.selectedBaseNode else { return false } + + var selectedBaseNode = NetworkManager.shared.selectedBaseNode + + if selectedBaseNode == nil { + selectedBaseNode = try services.connection.defaultBaseNodePeers().randomElement() + } + + guard let selectedBaseNode else { return false } return try walletManager.startRecovery(baseNodePublicKey: selectedBaseNode.makePublicKey(), recoveredOutputMessage: recoveredOutputMessage) } diff --git a/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift b/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift index ef564c02..98d71d78 100644 --- a/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift +++ b/MobileWallet/Libraries/TariLib/Core/Services/TariTransactionsService.swift @@ -171,7 +171,7 @@ final class TariTransactionsService: CoreTariService { try walletManager.cancelPendingTransaction(identifier: identifier) } - func send(toAddress address: TariAddress, amount: UInt64, feePerGram: UInt64, message: String, isOneSidedPayment: Bool, + func send(toAddress address: TariAddress, amount: UInt64, feePerGram: UInt64, message: String, isOneSidedPayment: Bool, paymentID: String, kernelsCount: UInt32 = Tari.defaultKernelCount, outputsCount: UInt32 = Tari.defaultOutputCount) throws -> UInt64 { let estimatedFee = try walletManager.feeEstimate(amount: amount, feePerGram: feePerGram, kernelsCount: kernelsCount, outputsCount: outputsCount) @@ -182,7 +182,7 @@ final class TariTransactionsService: CoreTariService { throw InternalError.insufficientFunds(spendableMicroTari: availableBalance) } - return try walletManager.sendTransaction(address: address, amount: amount, feePerGram: feePerGram, message: message, isOneSidedPayment: isOneSidedPayment) + return try walletManager.sendTransaction(address: address, amount: amount, feePerGram: feePerGram, message: message, isOneSidedPayment: isOneSidedPayment, paymentID: paymentID) } } diff --git a/MobileWallet/Libraries/TariLib/Core/Tari.swift b/MobileWallet/Libraries/TariLib/Core/Tari.swift index ef004d08..a6bc5853 100644 --- a/MobileWallet/Libraries/TariLib/Core/Tari.swift +++ b/MobileWallet/Libraries/TariLib/Core/Tari.swift @@ -133,6 +133,8 @@ final class Tari: MainServiceable { torConnectionStatus: torManager.$connectionStatus.eraseToAnyPublisher(), torBootstrapProgress: torManager.$bootstrapProgress.eraseToAnyPublisher(), baseNodeConnectionStatus: walletManager.$baseNodeConnectionStatus.eraseToAnyPublisher(), + scannedHeight: walletManager.$scannedHeight.eraseToAnyPublisher(), + blockHeight: $blockHeight.eraseToAnyPublisher(), baseNodeSyncStatus: validation.$status.eraseToAnyPublisher() ) setupCallbacks() @@ -201,7 +203,8 @@ final class Tari: MainServiceable { await waitForTor() guard await UIApplication.shared.applicationState != .background else { return } try startWallet(seedWords: nil) - try connection.selectCurrentNode() + guard try !connection.selectCurrentNode() else { return } + try switchBaseNode() } func restoreWallet(seedWords: [String]) throws { diff --git a/MobileWallet/Libraries/TariLib/Core/TorManager.swift b/MobileWallet/Libraries/TariLib/Core/TorManager.swift index 7a2c6371..4c8b8bc5 100644 --- a/MobileWallet/Libraries/TariLib/Core/TorManager.swift +++ b/MobileWallet/Libraries/TariLib/Core/TorManager.swift @@ -438,7 +438,7 @@ final class TorManager { let configuration = TorConfiguration() configuration.cookieAuthentication = true configuration.dataDirectory = workingDirectory - configuration.arguments = arguments + configuration.arguments = NSMutableArray(array: arguments) return configuration } diff --git a/MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift b/MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift index 497d49e9..6b324f85 100644 --- a/MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift +++ b/MobileWallet/Libraries/TariLib/Core/WalletCallbacksManager.swift @@ -168,6 +168,14 @@ final class WalletCallbacksManager { .eraseToAnyPublisher() }() + let walletScannedHeight: AnyPublisher = { + NotificationCenter.default + .publisher(for: .walletScannedHeight) + .compactMap { $0.object as? UInt64 } + .share() + .eraseToAnyPublisher() + }() + let baseNodeState: AnyPublisher = { NotificationCenter.default .publisher(for: .baseNodeStateUpdate) @@ -223,6 +231,7 @@ extension Notification.Name { static let walletBalanceUpdate = Self(rawValue: "com.tari.wallet.balance_update") static let transactionValidation = Self(rawValue: "com.tari.wallet.transaction_validation") static let baseNodeConnectionStatusUpdate = Self(rawValue: "com.tari.wallet.base_node_connection_status_update") + static let walletScannedHeight = Self(rawValue: "com.tari.wallet.wallet_scanned_height") static let baseNodeStateUpdate = Self(rawValue: "com.tari.wallet.base_node_state_update") // MARK: - Recovery Callbacks diff --git a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift index 37dec4f3..3099f335 100644 --- a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift @@ -50,6 +50,8 @@ final class ConnectionMonitor { @Published private(set) var torBootstrapProgress: Int = 0 @Published private(set) var isTorBootstrapCompleted: Bool = false @Published private(set) var baseNodeConnection: BaseNodeConnectivityStatus = .offline + @Published private(set) var walletScannedHeight: UInt64 = 0 + @Published private(set) var chainTip: UInt64 = 0 @Published private(set) var syncStatus: TariValidationService.SyncStatus = .idle private let networkMonitor = NetworkMonitor() @@ -57,8 +59,8 @@ final class ConnectionMonitor { // MARK: - Setups - func setupPublishers(torConnectionStatus: AnyPublisher, torBootstrapProgress: AnyPublisher, - baseNodeConnectionStatus: AnyPublisher, baseNodeSyncStatus: AnyPublisher) { + func setupPublishers(torConnectionStatus: AnyPublisher, torBootstrapProgress: AnyPublisher, baseNodeConnectionStatus: AnyPublisher, + scannedHeight: AnyPublisher, blockHeight: AnyPublisher, baseNodeSyncStatus: AnyPublisher) { networkMonitor.$status .assign(to: \.networkConnection, on: self) @@ -81,6 +83,14 @@ final class ConnectionMonitor { .assign(to: \.baseNodeConnection, on: self) .store(in: &cancellables) + scannedHeight + .assign(to: \.walletScannedHeight, on: self) + .store(in: &cancellables) + + blockHeight + .assign(to: \.chainTip, on: self) + .store(in: &cancellables) + baseNodeSyncStatus .assign(to: \.syncStatus, on: self) .store(in: &cancellables) @@ -214,6 +224,15 @@ extension ConnectionMonitor { .sink { [weak contentSection] in contentSection?.updateBaseNodeConnectionStatus(text: $0.statusName, status: $0.status) } .store(in: &cancellables) + Publishers.CombineLatest3($walletScannedHeight, $chainTip, $baseNodeConnection) + .receive(on: DispatchQueue.main) + .map { + guard $2 == .online, $0 > 0, $1 > 0 else { return localized("connection_status.popUp.label.chain_tip.waiting_for_connection") } + return localized("connection_status.popUp.label.chain_tip.values", arguments: $0, $1) + } + .sink { [weak contentSection] in contentSection?.update(chainTipSuffix: $0) } + .store(in: &cancellables) + $syncStatus .receive(on: DispatchQueue.main) .sink { [weak contentSection] in contentSection?.updateBaseNodeSyncStatus(text: $0.statusName, status: $0.status) } diff --git a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift index c1790144..bafe8d1d 100644 --- a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift @@ -57,6 +57,7 @@ final class CrashLogger { let options = Options() options.dsn = sentryPublicDSN options.environment = TariSettings.shared.environment.name + options.enableAppHangTracking = false SentrySDK.start(options: options) Logger.log(message: "Data Collection Enabled", domain: .general, level: .info) } diff --git a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift index bb157be0..012ded81 100644 --- a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Network/TariNetwork.swift @@ -73,6 +73,17 @@ extension TariNetwork { ) } + static var esmeralda: Self { + makeNetwork( + name: "esmeralda", + presentedName: "Esmeralda", + isMainNet: false, + isRecommended: true, + dnsPeer: "seeds.esmeralda.tari.com", + blockExplorerURL: nil + ) + } + var fullPresentedName: String { guard isRecommended else { return presentedName } return "\(presentedName) (\(localized("common.recommended")))" diff --git a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift index 06cf00ef..b96a19e1 100644 --- a/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift +++ b/MobileWallet/Libraries/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift @@ -42,9 +42,7 @@ final class WalletSettingsManager { private var settings: WalletSettings { - guard let networkName = GroupUserDefaults.selectedNetworkName else { - return makeWalletSettings(networkName: "") - } + let networkName = NetworkManager.shared.selectedNetwork.name guard let existingSettings = GroupUserDefaults.walletSettings?.first(where: { $0.networkName == networkName }) else { var settings = GroupUserDefaults.walletSettings ?? [] diff --git a/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsConstructor.swift b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsConstructor.swift index 7c7005d0..5a1a1539 100644 --- a/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsConstructor.swift +++ b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsConstructor.swift @@ -40,8 +40,8 @@ enum ScreenRecordingSettingsConstructor { - static func buildScene() -> ScreenRecordingSettingsViewController { + static func buildScene(backButtonType: NavigationBar.BackButtonType) -> ScreenRecordingSettingsViewController { let model = ScreenRecordingSettingsModel() - return ScreenRecordingSettingsViewController(model: model) + return ScreenRecordingSettingsViewController(model: model, backButtonType: backButtonType) } } diff --git a/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsViewController.swift b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsViewController.swift index d91d1317..99b6b365 100644 --- a/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsViewController.swift +++ b/MobileWallet/Screens/AdvancedSettings/Screen Recoding Settings/ScreenRecordingSettingsViewController.swift @@ -49,9 +49,10 @@ final class ScreenRecordingSettingsViewController: SecureViewController() - let items = models - .enumerated() - .map { SystemMenuTableViewCellItem(title: $1.networkName, mark: $1.isSelected ? .scheduled : .none, hasArrow: false) } + let items = models.map { SystemMenuTableViewCellItem(title: $0.networkName, mark: $0.isSelected ? .scheduled : .none, hasArrow: false) } snapshot.appendSections([0]) snapshot.appendItems(items, toSection: 0) diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashView.swift b/MobileWallet/Screens/AppEntry/Splash/SplashView.swift index d410d4ef..e4e15e64 100644 --- a/MobileWallet/Screens/AppEntry/Splash/SplashView.swift +++ b/MobileWallet/Screens/AppEntry/Splash/SplashView.swift @@ -71,7 +71,7 @@ final class SplashView: UIView { return view }() - @View private var createWalletButton: ActionButton = ActionButton() + @View private var createWalletButton = ActionButton() @View private var selectNetworkButton = ActionButton() @View private var restoreWalletButton: TextButton = { @@ -111,7 +111,7 @@ final class SplashView: UIView { } var isCreateWalletButtonSpinnerVisible: Bool = false { - didSet { createWalletButton.variation = isCreateWalletButtonSpinnerVisible ? .loading : .normal } + didSet { createWalletButton.style = isCreateWalletButtonSpinnerVisible ? .loading : .normal } } var selectNetworkButtonTitle: String? { @@ -259,7 +259,10 @@ final class SplashView: UIView { walletCreatedLogoConstraint?.isActive = true } - let alpha = showInterface ? 3.0 : 0.0 + let alpha = showInterface ? 1.0 : 0.0 + + createWalletButton.isAnimated = showInterface + selectNetworkButton.isAnimated = showInterface let transition = { self.layoutIfNeeded() diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift b/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift index 7a77cfb3..59ae80b7 100644 --- a/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift +++ b/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift @@ -87,7 +87,7 @@ final class SplashViewController: UIViewController { model.$status .compactMap { $0 } .receive(on: DispatchQueue.main) - .sink { [weak self] in self?.handle(status: $0) } + .sink { [weak self] in self?.handle(statusModel: $0) } .store(in: &cancellables) model.$networkName @@ -180,6 +180,20 @@ final class SplashViewController: UIViewController { // MARK: - Helpers + private func handle(statusModel: SplashViewModel.StatusModel) { + if #available(iOS 16.0, *) { + handle(status: statusModel) + } else { + handleLegacy(status: statusModel) + } + } + + private func handleLegacy(status: SplashViewModel.StatusModel) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.handle(status: status) + } + } + private func handle(status: SplashViewModel.StatusModel) { switch (status.status, status.statusRepresentation) { diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift index c5f3662b..4babf100 100644 --- a/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift +++ b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift @@ -146,8 +146,6 @@ final class SplashViewModel { let statusRepresentation = status?.statusRepresentation ?? .content status = StatusModel(status: .working, statusRepresentation: statusRepresentation) - let isMigrationPerformed = await MigrationManager.performPeerDBMigration() - guard await validateWallet() else { self.deleteWallet() return @@ -158,7 +156,6 @@ final class SplashViewModel { status = StatusModel(status: .success, statusRepresentation: statusRepresentation) - guard isMigrationPerformed else { return } let randomBaseNode = try NetworkManager.shared.randomBaseNode() try Tari.shared.connection.select(baseNode: randomBaseNode) } catch { diff --git a/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift b/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift index 0116666e..e971c210 100644 --- a/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift +++ b/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift @@ -38,7 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit import Lottie import LocalAuthentication import AVFoundation @@ -70,7 +69,7 @@ final class WalletCreationViewController: DynamicThemeViewController { private var animationViewHeightConstraint: NSLayoutConstraint? private var animationViewWidthConstraint: NSLayoutConstraint? - private let emojiIdView = EmojiIdView() + private let addressView = RoundedAddressView() private let tapToSeeButton = UIButton() private let tapToSeeArrow = UIImageView() private let tapToSeeButtonContainer = UIView() @@ -86,7 +85,6 @@ final class WalletCreationViewController: DynamicThemeViewController { private var continueButtonShowConstraint: NSLayoutConstraint? private let localAuth = LAContext() - private let radialGradient = RadialGradientView() // MARK: - Override functions @@ -115,7 +113,7 @@ final class WalletCreationViewController: DynamicThemeViewController { // MARK: - Actions private func hideSubviews(completion: (() -> Void)?) { - let duration: TimeInterval = 1.0 + let duration: TimeInterval = 0.7 hideContinueButton() firstLabel.hideLabel(duration: duration) secondLabel.hideLabel(duration: duration) @@ -125,18 +123,18 @@ final class WalletCreationViewController: DynamicThemeViewController { self.thirdLabel.alpha = 0.0 self.animationView.alpha = 0.0 self.numpadImageView.alpha = 0.0 - self.emojiIdView.alpha = 0.0 + self.addressView.alpha = 0.0 self.tapToSeeButtonContainer.alpha = 0.0 self.view.layoutIfNeeded()}, completion: { [weak self] _ in guard let self = self else { return } self.animationView.stop() - self.stackView.setCustomSpacing(0, after: self.emojiIdView) + self.stackView.setCustomSpacing(0, after: self.addressView) self.stackView.setCustomSpacing(0, after: self.secondLabel) self.stackView.setCustomSpacing(0, after: self.animationView) self.stackViewCenterYConstraint?.constant = 0.0 self.numpadImageView.isHidden = true - self.emojiIdView.isHidden = true + self.addressView.isHidden = true completion?() }) @@ -154,12 +152,16 @@ final class WalletCreationViewController: DynamicThemeViewController { } private func hideContinueButton() { - continueButton.hideButtonWithAlpha { [weak self] in - self?.continueButtonShowConstraint?.isActive = false - self?.continueButtonSecondShowConstraint?.isActive = false - self?.continueButtonConstraint?.isActive = true - self?.view.layoutIfNeeded() - } + UIView.animate( + withDuration: 0.5, + animations: { [weak self] in self?.continueButton.alpha = 0.0 }, + completion: { [weak self] _ in + self?.continueButtonShowConstraint?.isActive = false + self?.continueButtonSecondShowConstraint?.isActive = false + self?.continueButtonConstraint?.isActive = true + self?.view.layoutIfNeeded() + } + ) } private func playLottieAnimation(_ animation: LottieAnimation, completion: ((Bool) -> Void)? = nil) { @@ -175,7 +177,6 @@ final class WalletCreationViewController: DynamicThemeViewController { } @objc func tapToSeeButtonAction(_ sender: UIButton) { - emojiIdView.expand() tapToExpandAction() } @@ -199,7 +200,6 @@ final class WalletCreationViewController: DynamicThemeViewController { case .showEmojiId: TariSettings.shared.walletSettings.configurationState = .initialized hideSubviews { [weak self] in - self?.emojiIdView.shrink(animated: false) self?.prepareSubviews(for: .localAuthentication) self?.showLocalAuthentication() } @@ -291,29 +291,15 @@ extension WalletCreationViewController { // MARK: - Show Emoji ID private func showYourEmoji() { secondLabel.showLabel(duration: 1.0) - emojiIdView.isHidden = false + addressView.isHidden = false view.layoutIfNeeded() - emojiIdView.expand(animated: false) - emojiIdView.alpha = 0 - - self.emojiIdView.tapToExpand = { [weak self] expanded in - if self?.state == .showEmojiId { - self?.showContinueButton() - UIView.animate(withDuration: CATransaction.animationDuration()) { [weak self] in - self?.tapToSeeButtonContainer.alpha = expanded ? 0.0 : 1.0 - } - } - } + addressView.alpha = 0 + showContinueButton() UIView.animate(withDuration: 1, animations: { [weak self] in self?.thirdLabel.alpha = 1.0 - self?.emojiIdView.alpha = 1.0 + self?.addressView.alpha = 1.0 }) { [weak self] (_) in - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self?.emojiIdView.shrink(completion: { [weak self] in - self?.tapToSeeButtonContainer.alpha = 1.0 - }) - } self?.state = .showEmojiId } } @@ -415,17 +401,16 @@ extension WalletCreationViewController { continueButton.setTitle(localized("common.continue"), for: .normal) - if let walletAddress = try? Tari.shared.walletAddress, let emojiID = try? walletAddress.emojis, let hex = try? walletAddress.byteVector.hex { - emojiIdView.setup( - emojiID: emojiID, - hex: hex, - textCentered: true, - inViewController: self, - showContainerViewBlur: false - ) - } + guard let addressComponents = try? Tari.shared.walletAddress.components else { return } + addressView.update( + viewModel: AddressView.ViewModel( + prefix: addressComponents.networkAndFeatures, + text: .truncated(prefix: addressComponents.coreAddressPrefix, suffix: addressComponents.coreAddressSuffix), + isDetailsButtonVisible: true) + ) + addressView.onViewDetailsButtonTap = AddressViewDefaultActions.showDetailsAction(addressComponents: addressComponents) - stackView.setCustomSpacing(30, after: emojiIdView) + stackView.setCustomSpacing(30, after: addressView) } private func prepareForLocalAuthentication() { @@ -509,12 +494,11 @@ extension WalletCreationViewController { } private func setupUserEmojiContainer() { - emojiIdView.alpha = 0.0 - emojiIdView.isHidden = true - stackView.addArrangedSubview(emojiIdView) - stackView.setCustomSpacing(30, after: emojiIdView) - emojiIdView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true - emojiIdView.widthAnchor.constraint(equalTo: stackView.widthAnchor).isActive = true + addressView.isCompact = UIScreen.isSmallScreen + addressView.alpha = 0.0 + addressView.isHidden = true + stackView.addArrangedSubview(addressView) + stackView.setCustomSpacing(30, after: addressView) } private func setupAnimationView() { @@ -592,6 +576,7 @@ extension WalletCreationViewController { private func setupContinueButton() { continueButton.addTarget(self, action: #selector(onNavigateNext), for: .touchUpInside) continueButton.alpha = 0.0 + continueButton.isAnimated = false mainView.addSubview(continueButton) continueButton.translatesAutoresizingMaskIntoConstraints = false @@ -639,7 +624,7 @@ extension WalletCreationViewController { mainView.addSubview(tapToSeeButtonContainer) tapToSeeButtonContainer.translatesAutoresizingMaskIntoConstraints = false - tapToSeeButtonContainer.bottomAnchor.constraint(equalTo: emojiIdView.topAnchor, constant: 3).isActive = true + tapToSeeButtonContainer.bottomAnchor.constraint(equalTo: addressView.topAnchor, constant: 3).isActive = true tapToSeeButtonContainer.widthAnchor.constraint(equalToConstant: 159).isActive = true tapToSeeButtonContainer.heightAnchor.constraint(equalToConstant: 38).isActive = true tapToSeeButtonContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true diff --git a/MobileWallet/Screens/Contact Book/Add Contact/AddContactModel.swift b/MobileWallet/Screens/Contact Book/Add Contact/AddContactModel.swift index 3acdbc83..a980386a 100644 --- a/MobileWallet/Screens/Contact Book/Add Contact/AddContactModel.swift +++ b/MobileWallet/Screens/Contact Book/Add Contact/AddContactModel.swift @@ -63,7 +63,6 @@ final class AddContactModel { let emojiIDSubject: CurrentValueSubject = CurrentValueSubject("") let nameSubject: CurrentValueSubject = CurrentValueSubject("") - @Published var isSearchTextFormatted: Bool = true @Published private(set) var isDataValid: Bool = false @Published private(set) var action: Action? @Published private(set) var errorText: String? @@ -71,7 +70,6 @@ final class AddContactModel { // MARK: - Properties - private var rawSearchText: String = "" private var address: TariAddress? @Published private var errors = Set([.noEmojiID]) @@ -88,9 +86,10 @@ final class AddContactModel { private func setupCallbacks() { - Publishers.CombineLatest(emojiIDSubject.removeDuplicates(), $isSearchTextFormatted.removeDuplicates()) + emojiIDSubject + .removeDuplicates() .receive(on: DispatchQueue.main) - .sink { [weak self] in self?.handle(searchText: $0, isSearchTextFormatted: $1) } + .sink { [weak self] in self?.handle(searchText: $0) } .store(in: &cancellables) nameSubject @@ -129,15 +128,13 @@ final class AddContactModel { switch deeplink { case let deeplink as UserProfileDeeplink: - let address = TariAddressFactory.address(text: deeplink.tariAddress) + let address = try? TariAddress.makeTariAddress(input: deeplink.tariAddress) guard let emojis = try? address?.emojis else { return } - rawSearchText = emojis emojiIDSubject.send(emojis) nameSubject.send(deeplink.alias) case let deeplink as TransactionsSendDeeplink: - let address = TariAddressFactory.address(text: deeplink.receiverAddress) + let address = try? TariAddress.makeTariAddress(input: deeplink.receiverAddress) guard let emojis = try? address?.emojis else { return } - rawSearchText = emojis emojiIDSubject.send(emojis) default: break @@ -146,21 +143,6 @@ final class AddContactModel { // MARK: - Handlers - private func handle(searchText: String, isSearchTextFormatted: Bool) { - - if !isSearchTextFormatted { - rawSearchText = searchText - } - - handle(searchText: rawSearchText) - - if isSearchTextFormatted, address != nil { - emojiIDSubject.send(rawSearchText.insertSeparator(" | ", atEvery: 3)) - } else { - emojiIDSubject.send(rawSearchText) - } - } - private func handle(searchText: String) { guard !searchText.isEmpty else { @@ -172,7 +154,7 @@ final class AddContactModel { errors.remove(.noEmojiID) - guard let address = TariAddressFactory.address(text: searchText) else { + guard let address = try? TariAddress.makeTariAddress(input: searchText) else { address = nil errors.insert(.invalidEmojiID) return @@ -206,15 +188,3 @@ final class AddContactModel { } } } - -enum TariAddressFactory { - - static func address(text: String) -> TariAddress? { - - if let address = try? TariAddress(emojiID: text) { - return address - } - - return try? TariAddress(hex: text) - } -} diff --git a/MobileWallet/Screens/Contact Book/Add Contact/AddContactView.swift b/MobileWallet/Screens/Contact Book/Add Contact/AddContactView.swift index e9e6b407..7ae14854 100644 --- a/MobileWallet/Screens/Contact Book/Add Contact/AddContactView.swift +++ b/MobileWallet/Screens/Contact Book/Add Contact/AddContactView.swift @@ -38,7 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Combine import TariCommon final class AddContactView: BaseNavigationContentView { @@ -82,7 +81,6 @@ final class AddContactView: BaseNavigationContentView { var onDoneButtonTap: (() -> Void)? var onQRCodeButtonTap: (() -> Void)? - var onSearchTextFieldFocusState: ((_ isFocused: Bool) -> Void)? // MARK: - Initialisers @@ -133,12 +131,9 @@ final class AddContactView: BaseNavigationContentView { } private func setupCallbacks() { - searchView.qrButton.onTap = { [weak self] in self?.onQRCodeButtonTap?() } - - searchView.textField.delegate = self } // MARK: - Updates @@ -155,14 +150,3 @@ final class AddContactView: BaseNavigationContentView { nameTextField.attributedPlaceholder = NSAttributedString(string: localized("contact_book.add_contact.text_field.name.placeholder"), attributes: [.foregroundColor: placeholderColor]) } } - -extension AddContactView: UITextFieldDelegate { - - func textFieldDidBeginEditing(_ textField: UITextField) { - onSearchTextFieldFocusState?(true) - } - - func textFieldDidEndEditing(_ textField: UITextField) { - onSearchTextFieldFocusState?(false) - } -} diff --git a/MobileWallet/Screens/Contact Book/Add Contact/AddContactViewController.swift b/MobileWallet/Screens/Contact Book/Add Contact/AddContactViewController.swift index ddba8025..692686bd 100644 --- a/MobileWallet/Screens/Contact Book/Add Contact/AddContactViewController.swift +++ b/MobileWallet/Screens/Contact Book/Add Contact/AddContactViewController.swift @@ -100,10 +100,6 @@ final class AddContactViewController: SecureViewController { mainView.searchView.textField.bind(withSubject: model.emojiIDSubject, storeIn: &cancellables) mainView.nameTextField.bind(withSubject: model.nameSubject, storeIn: &cancellables) - mainView.onSearchTextFieldFocusState = { [weak self] in - self?.model.isSearchTextFormatted = !$0 - } - mainView.onQRCodeButtonTap = { [weak self] in self?.showQRScanner() } diff --git a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsModel.swift b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsModel.swift index b6f074a6..6cb7f1fe 100644 --- a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsModel.swift +++ b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsModel.swift @@ -64,18 +64,15 @@ final class ContactDetailsModel { enum Action { case sendTokens(paymentInfo: PaymentInfo) case moveToLinkContactScreen(model: ContactsManager.Model) - case showUnlinkConfirmationDialog(emojiID: String, name: String) - case showUnlinkSuccessDialog(emojiID: String, name: String) + case showUnlinkConfirmationDialog(address: String, name: String) + case showUnlinkSuccessDialog(address: String, name: String) case moveToTransactionsList(model: ContactsManager.Model) case removeContactConfirmation case endFlow } struct ViewModel { - let avatarText: String? - let avatarImage: UIImage? - let emojiID: String - let hex: String? + let addressComponents: TariAddressComponents? let contactType: ContactsManager.ContactType } @@ -88,6 +85,7 @@ final class ContactDetailsModel { @Published private(set) var menuSections: [MenuSection] = [] @Published private(set) var action: Action? @Published private(set) var errorModel: MessageModel? + @Published private(set) var addressComponents: TariAddressComponents? var hasSplittedName: Bool { model.hasExternalModel } var nameComponents: [String] { model.nameComponents } @@ -180,11 +178,11 @@ final class ContactDetailsModel { func unlinkContact() { - guard let emojiID = model.internalModel?.emojiID.obfuscatedText, let name = model.externalModel?.fullname else { return } + guard let address = model.internalModel?.addressComponents.formattedCoreAddress, let name = model.externalModel?.fullname else { return } do { try contactsManager.unlink(contact: model) - action = .showUnlinkSuccessDialog(emojiID: emojiID, name: name) + action = .showUnlinkSuccessDialog(address: address, name: name) updateData() } catch { errorModel = ErrorMessageManager.errorModel(forError: error) @@ -215,28 +213,24 @@ final class ContactDetailsModel { private func updateData(model: ContactsManager.Model) { - let avatarImage = model.avatarImage - let avatarText = avatarImage == nil ? model.avatar : nil - - viewModel = ViewModel(avatarText: avatarText, avatarImage: avatarImage, emojiID: model.internalModel?.emojiID ?? "", hex: model.internalModel?.hex, contactType: model.type) - - var mainMenuItems: [MenuItem] = model.menuItems - .compactMap { - switch $0 { - case .send: - return .send - case .addToFavorites: - return .addToFavorites - case .removeFromFavorites: - return .removeFromFavorites - case .link: - return .linkContact - case .unlink: - return .unlinkContact - case .details: - return nil - } - } + addressComponents = model.internalModel?.addressComponents + viewModel = ViewModel(addressComponents: addressComponents, contactType: model.type) + + var mainMenuItems: [MenuItem] = [] + + if model.hasIntrenalModel { + mainMenuItems.append(.send) + } + + if model.isFFIContact, let internalModel = model.internalModel { + mainMenuItems.append(internalModel.isFavorite ? .removeFromFavorites : .addToFavorites) + } + + if model.type == .linked { + mainMenuItems.append(.unlinkContact) + } else { + mainMenuItems.append(.linkContact) + } if model.hasIntrenalModel { mainMenuItems.append(.transactionsList) @@ -259,8 +253,8 @@ final class ContactDetailsModel { } private func prepareForUnkinkAction() { - guard let emojiID = model.internalModel?.emojiID.obfuscatedText, let name = model.externalModel?.fullname else { return } - action = .showUnlinkConfirmationDialog(emojiID: emojiID, name: name) + guard let address = model.internalModel?.addressComponents.formattedCoreAddress, let name = model.externalModel?.fullname else { return } + action = .showUnlinkConfirmationDialog(address: address, name: name) } private func openAddress(type: YatRecordTag) { diff --git a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift index 964610aa..daa7cb54 100644 --- a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift +++ b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift @@ -53,8 +53,6 @@ final class ContactDetailsView: BaseNavigationContentView { // MARK: - Subviews - @View private var avatarView = RoundedAvatarView() - @View private var nameLabel: UILabel = { let view = UILabel() view.font = .Avenir.medium.withSize(17.0) @@ -62,8 +60,8 @@ final class ContactDetailsView: BaseNavigationContentView { return view }() - @View private var emojiIdView: EmojiIdView = { - let view = EmojiIdView() + @View private var addressView: RoundedAddressView = { + let view = RoundedAddressView() view.isHidden = true return view }() @@ -91,18 +89,13 @@ final class ContactDetailsView: BaseNavigationContentView { didSet { updateEditButton() } } - var avatar: RoundedAvatarView.Avatar { - get { avatarView.avatar } - set { avatarView.avatar = newValue } - } - var name: String? { get { nameLabel.text } set { nameLabel.text = newValue } } - var emojiModel: EmojiIdView.ViewModel? { - didSet { updateEmojiView() } + var addressModel: AddressView.ViewModel? { + didSet { updateAddressView() } } var yat: String? { @@ -118,6 +111,11 @@ final class ContactDetailsView: BaseNavigationContentView { set { tableView.onSelectRow = newValue } } + var onViewAddressDetailsButtonTap: (() -> Void)? { + get { addressView.onViewDetailsButtonTap } + set { addressView.onViewDetailsButtonTap = newValue } + } + var onEditButtonTap: (() -> Void)? private var idElementsState: IDElementsState = .allHidden { @@ -146,28 +144,22 @@ final class ContactDetailsView: BaseNavigationContentView { private func setupConstraints() { - [avatarView, nameLabel, emojiIdView, yatLabel, yatButton, tableView].forEach(addSubview) + [nameLabel, addressView, yatLabel, yatButton, tableView].forEach(addSubview) let constraints = [ - avatarView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 21.0), - avatarView.centerXAnchor.constraint(equalTo: centerXAnchor), - avatarView.widthAnchor.constraint(equalToConstant: 90.0), - avatarView.heightAnchor.constraint(equalToConstant: 90.0), - nameLabel.topAnchor.constraint(equalTo: avatarView.bottomAnchor, constant: 10.0), + nameLabel.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 20.0), nameLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), nameLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), - emojiIdView.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 10.0), - emojiIdView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), - emojiIdView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), - emojiIdView.heightAnchor.constraint(equalToConstant: 38.0), - yatLabel.leadingAnchor.constraint(equalTo: emojiIdView.leadingAnchor), - yatLabel.trailingAnchor.constraint(equalTo: emojiIdView.trailingAnchor), - yatLabel.centerYAnchor.constraint(equalTo: emojiIdView.centerYAnchor), + addressView.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 10.0), + addressView.centerXAnchor.constraint(equalTo: centerXAnchor), + yatLabel.leadingAnchor.constraint(equalTo: addressView.leadingAnchor), + yatLabel.trailingAnchor.constraint(equalTo: addressView.trailingAnchor), + yatLabel.centerYAnchor.constraint(equalTo: addressView.centerYAnchor), yatButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), - yatButton.centerYAnchor.constraint(equalTo: emojiIdView.centerYAnchor), + yatButton.centerYAnchor.constraint(equalTo: addressView.centerYAnchor), yatButton.widthAnchor.constraint(equalToConstant: 24.0), yatButton.heightAnchor.constraint(equalToConstant: 24.0), - tableView.topAnchor.constraint(equalTo: emojiIdView.bottomAnchor, constant: 20.0), + tableView.topAnchor.constraint(equalTo: addressView.bottomAnchor, constant: 20.0), tableView.leadingAnchor.constraint(equalTo: leadingAnchor), tableView.trailingAnchor.constraint(equalTo: trailingAnchor), tableView.bottomAnchor.constraint(equalTo: bottomAnchor) @@ -204,10 +196,10 @@ final class ContactDetailsView: BaseNavigationContentView { navigationBar.update(rightButton: NavigationBar.ButtonModel(title: editButtonName, callback: { [weak self] in self?.onEditButtonTap?() })) } - private func updateEmojiView() { + private func updateAddressView() { updateIDElementsState() - guard let emojiModel else { return } - emojiIdView.update(viewModel: emojiModel) + guard let addressModel else { return } + addressView.update(viewModel: addressModel) } private func updateYatView() { @@ -216,10 +208,12 @@ final class ContactDetailsView: BaseNavigationContentView { } private func updateIDElementsState() { - let isEmojiIdAAvailable = emojiModel?.emojiID != nil && emojiModel?.emojiID.isEmpty == false + + let isAddressAvailable = addressModel != nil + let isYatAvailable = yat != nil && yat?.isEmpty == false - switch (isEmojiIdAAvailable, isYatAvailable) { + switch (isAddressAvailable, isYatAvailable) { case (true, true): idElementsState = .yatHidden case (true, false): @@ -237,26 +231,26 @@ final class ContactDetailsView: BaseNavigationContentView { switch idElementsState { case .allHidden: - emojiIdView.isHidden = true + addressView.isHidden = true yatLabel.isHidden = true yatButton.isHidden = true case .emojiOnly: - emojiIdView.isHidden = false + addressView.isHidden = false yatLabel.isHidden = true yatButton.isHidden = true case .yatOnly: - emojiIdView.isHidden = true + addressView.isHidden = true yatLabel.isHidden = false yatButton.isHidden = false yatButton.isEnabled = false case .yatHidden: - emojiIdView.isHidden = false + addressView.isHidden = false yatLabel.isHidden = true yatButton.isHidden = false yatButton.isEnabled = true yatButton.setImage(.Icons.Yat.buttonOn, for: .normal) case .yatVisible: - emojiIdView.isHidden = true + addressView.isHidden = true yatLabel.isHidden = false yatButton.isHidden = false yatButton.isEnabled = true diff --git a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift index 30717930..7091f02a 100644 --- a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift +++ b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift @@ -92,14 +92,14 @@ final class ContactDetailsViewController: SecureViewController $1.timestamp }} .replaceError(with: [Transaction]()) @@ -105,10 +101,10 @@ final class ContactTransactionListModel { private func isContactTransaction(transaction: Transaction) -> Bool { - guard let contactHex = contactModel.internalModel?.hex else { return false } + guard let contactHex = contactModel.internalModel?.addressComponents.uniqueIdentifier else { return false } do { - let transactionHex = try transaction.address.byteVector.hex + let transactionHex = try transaction.address.components.uniqueIdentifier return transactionHex == contactHex } catch { return false diff --git a/MobileWallet/Screens/Contact Book/Contact Transactions List/Legacy/TxTableViewCell.swift b/MobileWallet/Screens/Contact Book/Contact Transactions List/Legacy/TxTableViewCell.swift index 439acde0..4442b782 100644 --- a/MobileWallet/Screens/Contact Book/Contact Transactions List/Legacy/TxTableViewCell.swift +++ b/MobileWallet/Screens/Contact Book/Contact Transactions List/Legacy/TxTableViewCell.swift @@ -44,7 +44,6 @@ import TariCommon final class TxTableViewCell: DynamicThemeCell { private let labelsContainer = UIView() - @View private var avatarView = RoundedAvatarView() private let titleLabel = UILabel() private let timeLabel = UILabel() private let statusLabel = UILabel() @@ -89,12 +88,6 @@ final class TxTableViewCell: DynamicThemeCell { setStatus(model.status) self.model = model - if let avatarImage = model.avatarImage { - avatarView.avatar = .image(avatarImage) - } else { - avatarView.avatar = .text(model.avatarText) - } - noteLabel.text = model.message titleLabel.attributedText = model.title timeLabel.text = model.time @@ -241,25 +234,9 @@ final class TxTableViewCell: DynamicThemeCell { extension TxTableViewCell { private func viewSetup() { selectionStyle = .none - - setupAvatar() setupLabels() } - private func setupAvatar() { - - contentView.addSubview(avatarView) - - let constraints = [ - avatarView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: TxTableViewCell.topCellPadding), - avatarView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Theme.shared.sizes.appSidePadding), - avatarView.widthAnchor.constraint(equalToConstant: 42.0), - avatarView.heightAnchor.constraint(equalToConstant: 42.0) - ] - - NSLayoutConstraint.activate(constraints) - } - private func setupLabels() { // MARK: - Label container labelsContainer.translatesAutoresizingMaskIntoConstraints = false @@ -279,7 +256,7 @@ extension TxTableViewCell { labelsContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: TxTableViewCell.topCellPadding).isActive = true labelsContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -25 + TxTableViewCell.topCellPadding).isActive = true - labelsContainer.leadingAnchor.constraint(equalTo: avatarView.trailingAnchor, constant: Theme.shared.sizes.appSidePadding).isActive = true + labelsContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Theme.shared.sizes.appSidePadding).isActive = true labelsContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Theme.shared.sizes.appSidePadding).isActive = true // MARK: - Value diff --git a/MobileWallet/Screens/Contact Book/Contact Transactions List/Legacy/TxTableViewModel.swift b/MobileWallet/Screens/Contact Book/Contact Transactions List/Legacy/TxTableViewModel.swift index 54cdd9bc..7b47e258 100644 --- a/MobileWallet/Screens/Contact Book/Contact Transactions List/Legacy/TxTableViewModel.swift +++ b/MobileWallet/Screens/Contact Book/Contact Transactions List/Legacy/TxTableViewModel.swift @@ -52,8 +52,6 @@ class TxTableViewModel: NSObject { let id: UInt64 private(set) var transaction: Transaction private(set) var title = NSAttributedString() - private(set) var avatarText: String = "" - private(set) var avatarImage: UIImage? private(set) var message: String private(set) var value: Value @@ -127,15 +125,11 @@ class TxTableViewModel: NSObject { private func updateTitleAndAvatar() throws { guard try !transaction.isOneSidedPayment else { - avatarText = localized("transaction.one_sided_payment.avatar") let alias = localized("transaction.one_sided_payment.inbound_user_placeholder") title = attributed(title: localized("tx_list.inbound_pending_title", arguments: alias), withAlias: alias) return } - avatarText = contact?.avatar ?? "" - avatarImage = contact?.avatarImage - var titleText = "" let alias = contact?.name ?? "" diff --git a/MobileWallet/Screens/Contact Book/Contact Transactions List/Views/ContactTransactionListPlaceholder.swift b/MobileWallet/Screens/Contact Book/Contact Transactions List/Views/ContactTransactionListPlaceholder.swift index e6d675f3..7bb1273d 100644 --- a/MobileWallet/Screens/Contact Book/Contact Transactions List/Views/ContactTransactionListPlaceholder.swift +++ b/MobileWallet/Screens/Contact Book/Contact Transactions List/Views/ContactTransactionListPlaceholder.swift @@ -70,7 +70,7 @@ final class ContactTransactionListPlaceholder: DynamicThemeView { @View private var sendButton: TextButton = { let view = TextButton() - view.setVariation(.secondary) + view.style = .secondary view.setTitle(localized("contact_book.transaction_list.placeholder.button.send"), for: .normal) return view }() diff --git a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsModel.swift b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsModel.swift index 2c556475..aca2c954 100644 --- a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsModel.swift +++ b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsModel.swift @@ -43,8 +43,8 @@ import Combine final class LinkContactsModel { enum Action { - case showConfirmation(emojiID: String, name: String) - case showSuccess(emojiID: String, name: String) + case showConfirmation(address: String, name: String) + case showSuccess(address: String, name: String) case moveToAddContact case moveToPhoneBook case moveToPermissionSettings @@ -100,7 +100,7 @@ final class LinkContactsModel { } } - name = contactModel.internalModel?.emojiID.obfuscatedText ?? contactModel.externalModel?.fullname + name = contactModel.internalModel?.addressComponents.formattedCoreAddress ?? contactModel.externalModel?.fullname } private func updateModels() { @@ -156,7 +156,7 @@ final class LinkContactsModel { unconfirmedInternalModel = internalContact unconfirmedExternalModel = externalContact - action = .showConfirmation(emojiID: internalContact.emojiID.obfuscatedText, name: externalContact.fullname) + action = .showConfirmation(address: internalContact.addressComponents.formattedCoreAddress, name: externalContact.fullname) } func linkContacts() { @@ -168,7 +168,7 @@ final class LinkContactsModel { do { try contactsManager.link(internalContact: unconfirmedInternalModel, externalContact: unconfirmedExternalModel) - action = .showSuccess(emojiID: unconfirmedInternalModel.emojiID.obfuscatedText, name: unconfirmedExternalModel.fullname) + action = .showSuccess(address: unconfirmedInternalModel.addressComponents.formattedCoreAddress, name: unconfirmedExternalModel.fullname) cancelLinkContacts() } catch { errorModel = ErrorMessageManager.errorModel(forError: error) diff --git a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift index 1071009c..04eacc79 100644 --- a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift +++ b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift @@ -82,7 +82,7 @@ final class LinkContactsViewController: SecureViewController { .store(in: &cancellables) model.$models - .map { $0.map { ContactBookCell.ViewModel(id: $0.id, name: $0.name, avatarText: $0.avatar, avatarImage: $0.avatarImage, isFavorite: false, contactTypeImage: nil, isSelectable: false) }} + .map { $0.map { ContactBookCell.ViewModel(id: $0.id, addressViewModel: $0.contactBookCellAddressViewModel, isFavorite: false, contactTypeImage: nil, isSelectable: false) }} .sink { [weak self] in self?.mainView.viewModels = $0 } .store(in: &cancellables) @@ -118,10 +118,10 @@ final class LinkContactsViewController: SecureViewController { private func handle(action: LinkContactsModel.Action) { switch action { - case let .showConfirmation(emojiID, name): - showConfirmationDialog(emojiID: emojiID, name: name) - case let .showSuccess(emojiID, name): - showSuccessDialog(emojiID: emojiID, name: name) + case let .showConfirmation(address, name): + showConfirmationDialog(address: address, name: name) + case let .showSuccess(address, name): + showSuccessDialog(address: address, name: name) case .moveToAddContact: moveToAddContact() case .moveToPhoneBook: @@ -157,14 +157,14 @@ final class LinkContactsViewController: SecureViewController { // MARK: - Actions - private func showConfirmationDialog(emojiID: String, name: String) { + private func showConfirmationDialog(address: String, name: String) { let model = PopUpDialogModel( titleComponents: [ StylizedLabel.StylizedText(text: localized("contact_book.link_contacts.popup.confirmation.title"), style: .normal) ], messageComponents: [ - StylizedLabel.StylizedText(text: localized("contact_book.link_contacts.popup.confirmation.message.part1", arguments: emojiID), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.link_contacts.popup.confirmation.message.part1", arguments: address), style: .normal), StylizedLabel.StylizedText(text: name, style: .bold) ], buttons: [ @@ -177,14 +177,14 @@ final class LinkContactsViewController: SecureViewController { PopUpPresenter.showPopUp(model: model) } - private func showSuccessDialog(emojiID: String, name: String) { + private func showSuccessDialog(address: String, name: String) { let model = PopUpDialogModel( titleComponents: [StylizedLabel.StylizedText( text: localized("contact_book.link_contacts.popup.success.title"), style: .normal) ], messageComponents: [ - StylizedLabel.StylizedText(text: localized("contact_book.link_contacts.popup.success.message.part1", arguments: emojiID), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.link_contacts.popup.success.message.part1", arguments: address), style: .normal), StylizedLabel.StylizedText(text: name, style: .bold) ], buttons: [ diff --git a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift index 91e0dc11..8664c56f 100644 --- a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift +++ b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift @@ -58,6 +58,7 @@ final class ContactBookContactListView: DynamicThemeView { view.register(headerFooterType: MenuTableHeaderView.self) view.separatorInset = UIEdgeInsets(top: 0.0, left: 22.0, bottom: 0.0, right: 22.0) view.allowsSelectionDuringEditing = true + view.sectionHeaderTopPadding = 0.0 return view }() @@ -255,7 +256,7 @@ private class ContactBookContactListFooter: UIView { @View private var button: TextButton = { let view = TextButton() - view.setVariation(.secondary) + view.style = .secondary view.setTitle(localized("contact_book.section.list.placeholder.button"), for: .normal) return view }() diff --git a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift index 2a821c17..f6553500 100644 --- a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift +++ b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift @@ -88,7 +88,7 @@ final class ContactBookListPlaceholder: DynamicThemeView { @View private var actionButton: TextButton = { let view = TextButton() - view.setVariation(.secondary) + view.style = .secondary return view }() diff --git a/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift b/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift index 8a6720fc..51505ace 100644 --- a/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift +++ b/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift @@ -43,15 +43,6 @@ import Combine final class ContactBookModel { - enum MenuItem: UInt { - case send - case addToFavorites - case removeFromFavorites - case link - case unlink - case details - } - enum ContentMode { case normal case shareContacts @@ -70,16 +61,11 @@ final class ContactBookModel { } enum Action { - case sendTokens(paymentInfo: PaymentInfo) - case link(model: ContactsManager.Model) - case unlink(model: ContactsManager.Model) - case showUnlinkSuccess(emojiID: String, name: String) case showDetails(model: ContactsManager.Model) case showQRDialog case shareQR(image: UIImage) case shareLink(link: URL) case show(dialog: DialogType) - case showMenu(model: ContactsManager.Model) } fileprivate enum SectionType: Int { @@ -157,7 +143,8 @@ final class ContactBookModel { Task { do { try await contactsManager.fetchModels() - contactModels = [contactsManager.tariContactModels, contactsManager.externalModels] + let tariContactModels = contactsManager.tariContactModels.filter { $0.internalModel?.addressComponents.isUnknownAddress == false } + contactModels = [tariContactModels, contactsManager.externalModels] } catch { errorModel = ErrorMessageManager.errorModel(forError: error) } @@ -166,26 +153,6 @@ final class ContactBookModel { } } - func performAction(contactID: UUID, menuItemID: UInt) { - - guard let model = contact(contactID: contactID), let menuItem = MenuItem(rawValue: menuItemID) else { return } - - switch menuItem { - case .send: - performSendAction(model: model) - case .addToFavorites: - update(isFavorite: true, contact: model) - case .removeFromFavorites: - update(isFavorite: false, contact: model) - case .link: - performLinkAction(model: model) - case .unlink: - performUnlinkAction(model: model) - case .details: - performShowDetailsAction(model: model) - } - } - func toggleSelection(contactID: UUID) { guard let model = contact(contactID: contactID), model.hasIntrenalModel else { return } @@ -198,19 +165,6 @@ final class ContactBookModel { selectedIDs.remove(contactID) } - func unlink(contact: ContactsManager.Model) { - - guard let emojiID = contact.internalModel?.emojiID.obfuscatedText, let name = contact.externalModel?.fullname else { return } - - do { - try contactsManager.unlink(contact: contact) - fetchContacts() - action = .showUnlinkSuccess(emojiID: emojiID, name: name) - } catch { - errorModel = ErrorMessageManager.errorModel(forError: error) - } - } - func shareSelectedContacts(shareType: ShareType) { let deeplink: URL @@ -241,20 +195,11 @@ final class ContactBookModel { func selectContact(contactID: UUID) { guard let model = contact(contactID: contactID) else { return } - action = .showMenu(model: model) + action = .showDetails(model: model) } // MARK: - Actions - private func update(isFavorite: Bool, contact: ContactsManager.Model) { - do { - try contactsManager.update(nameComponents: contact.nameComponents, isFavorite: isFavorite, yat: contact.externalModel?.yat ?? "", contact: contact) - fetchContacts() - } catch { - errorModel = ErrorMessageManager.errorModel(forError: error) - } - } - private func shareQR(deeplink: URL) { action = .showQRDialog @@ -289,27 +234,6 @@ final class ContactBookModel { } } - private func performSendAction(model: ContactsManager.Model) { - do { - guard let paymentInfo = try model.paymentInfo else { return } - action = .sendTokens(paymentInfo: paymentInfo) - } catch { - errorModel = ErrorMessageManager.errorModel(forError: error) - } - } - - private func performLinkAction(model: ContactsManager.Model) { - action = .link(model: model) - } - - private func performShowDetailsAction(model: ContactsManager.Model) { - action = .showDetails(model: model) - } - - private func performUnlinkAction(model: ContactsManager.Model) { - action = .unlink(model: model) - } - // MARK: - Handlers private func handle(bleError error: Error) { @@ -333,7 +257,7 @@ final class ContactBookModel { let list = selectedIDs .compactMap { selectedID in allModels.first { $0.id == selectedID }} .compactMap { $0.internalModel } - .map { ContactListDeeplink.Contact(alias: $0.alias ?? "", hex: $0.hex ) } + .map { ContactListDeeplink.Contact(alias: $0.alias ?? "", tariAddress: $0.addressComponents.fullRaw ) } let model = ContactListDeeplink(list: list) @@ -348,8 +272,8 @@ final class ContactBookModel { $0.filter { guard $0.name.range(of: searchText, options: .caseInsensitive) == nil else { return true } guard let internalModel = $0.internalModel else { return false } - guard internalModel.emojiID.range(of: searchText, options: .caseInsensitive) == nil else { return true } - return internalModel.hex.range(of: searchText, options: .caseInsensitive) != nil + guard internalModel.addressComponents.fullEmoji.range(of: searchText, options: .caseInsensitive) == nil else { return true } + return internalModel.addressComponents.fullRaw.range(of: searchText, options: .caseInsensitive) != nil } } } @@ -363,12 +287,9 @@ final class ContactBookModel { guard !data.element.isEmpty else { return } let items: [ContactBookCell.ViewModel] = data.element.map { - let name = (!$0.name.isEmpty ? $0.name : $0.internalModel?.emojiID.obfuscatedText) ?? "" - return ContactBookCell.ViewModel( + ContactBookCell.ViewModel( id: $0.id, - name: name, - avatarText: $0.avatar, - avatarImage: $0.avatarImage, + addressViewModel: $0.contactBookCellAddressViewModel, isFavorite: $0.isFavorite, contactTypeImage: $0.type.image, isSelectable: section?.isSelectable ?? false diff --git a/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift b/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift index 91503957..bb7e485e 100644 --- a/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift +++ b/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift @@ -251,14 +251,6 @@ final class ContactBookViewController: SecureViewController, Ov private func handle(action: ContactBookModel.Action) { switch action { - case let .sendTokens(paymentInfo): - moveToSendTokensScreen(paymentInfo: paymentInfo) - case let .link(model): - moveToLinkContactsScreen(model: model) - case let .unlink(model: model): - showUnlinkConfirmationDialog(model: model) - case let .showUnlinkSuccess(emojiID, name): - showUnlinkSuccessDialog(emojiID: emojiID, name: name) case let .showDetails(model): moveToContactDetails(model: model) case .showQRDialog: @@ -269,8 +261,6 @@ final class ContactBookViewController: SecureViewController, Ov showLinkShareDialog(link: link) case let .show(dialog): handle(dialog: dialog) - case let .showMenu(model): - showMenu(model: model) } } @@ -313,31 +303,11 @@ final class ContactBookViewController: SecureViewController, Ov navigationController?.pushViewController(controller, animated: true) } - private func moveToSendTokensScreen(paymentInfo: PaymentInfo) { - Task { @MainActor in - AppRouter.presentSendTransaction(paymentInfo: paymentInfo) - } - } - - private func moveToLinkContactsScreen(model: ContactsManager.Model) { - let controller = LinkContactsConstructor.buildScene(contactModel: model) - navigationController?.pushViewController(controller, animated: true) - } - private func moveToContactDetails(model: ContactsManager.Model) { let controller = ContactDetailsConstructor.buildScene(model: model) navigationController?.pushViewController(controller, animated: true) } - private func showUnlinkConfirmationDialog(model: ContactsManager.Model) { - guard let emojiID = model.internalModel?.emojiID.obfuscatedText, let name = model.externalModel?.fullname else { return } - PopUpPresenter.showUnlinkConfirmationDialog(emojiID: emojiID, name: name, confirmationCallback: { [weak self] in self?.model.unlink(contact: model) }) - } - - private func showUnlinkSuccessDialog(emojiID: String, name: String) { - PopUpPresenter.showUnlinkSuccessDialog(emojiID: emojiID, name: name) - } - private func openAppSettings() { AppRouter.openAppSettings() } @@ -359,16 +329,4 @@ final class ContactBookViewController: SecureViewController, Ov private func showBLEDialog(type: PopUpPresenter.BLEDialogType) { PopUpPresenter.showBLEDialog(type: type) } - - private func showMenu(model: ContactsManager.Model) { - - let overlay = RotaryMenuOverlay(model: model) - - overlay.onMenuButtonTap = { [weak self] in - self?.dismiss(animated: true) - self?.model.performAction(contactID: $0, menuItemID: $1) - } - - show(overlay: overlay) - } } diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlay.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlay.swift deleted file mode 100644 index 5ce39689..00000000 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlay.swift +++ /dev/null @@ -1,194 +0,0 @@ -// RotaryMenuOverlay.swift - -/* - Package MobileWallet - Created by Adrian Truszczyński on 09/06/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 - -final class RotaryMenuOverlay: UIViewController { - - // MARK: - Properties - - var onMenuButtonTap: ((_ contactID: UUID, _ menuItem: UInt) -> Void)? - - private let mainView: RotaryMenuOverlayView = { - switch UserSettingsManager.rotaryMenuPosition { - case .left: - return RotaryMenuOverlayView(presentationSide: .left) - case .right: - return RotaryMenuOverlayView(presentationSide: .right) - } - }() - - private let contactID: UUID - private lazy var secureWrapperView = SecureWrapperView(mainView: mainView) - - // MARK: - Initialisers - - init(model: ContactsManager.Model) { - contactID = model.id - super.init(nibName: nil, bundle: nil) - - if let avatarImage = model.avatarImage { - mainView.avatar = .image(avatarImage) - } else { - mainView.avatar = .text(model.avatar) - } - - mainView.models = model.menuItems.map { $0.buttonViewModel } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - View Lifecycle - - override func loadView() { - view = secureWrapperView - } - - override func viewDidLoad() { - super.viewDidLoad() - setupController() - setupCallbacks() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - showOverlay() - } - - // MARK: - Setups - - private func setupController() { - modalTransitionStyle = .crossDissolve - modalPresentationStyle = .overFullScreen - } - - private func setupCallbacks() { - - mainView.onMenuButtonTap = { [weak self] in - guard let self else { return } - self.onMenuButtonTap?(self.contactID, $0) - } - - mainView.onCloseButtonTap = { [weak self] in - self?.dismiss() - } - - mainView.onSwitchSideButtonTap = { [weak self] in - self?.switchPresentationSide() - } - } - - // MARK: - Actions - - private func showOverlay() { - Task { - await mainView.show() - } - } - - private func dismiss() { - Task { - await mainView.hide() - dismiss(animated: true) - } - } - - private func switchPresentationSide() { - - let userSettingsPosition: UserSettings.RotaryMenuPosition - let viewPosition: RotaryMenuOverlayView.PresentationSide - - switch mainView.presentationSide { - case .left: - userSettingsPosition = .right - viewPosition = .right - case .right: - userSettingsPosition = .left - viewPosition = .left - } - - UserSettingsManager.rotaryMenuPosition = userSettingsPosition - - Task { - await mainView.switchSide(presentationSide: viewPosition) - } - } -} - -private extension ContactBookModel.MenuItem { - - var buttonViewModel: RotaryMenuView.MenuButtonViewModel { RotaryMenuView.MenuButtonViewModel(id: rawValue, icon: icon, title: title) } - - private var icon: UIImage? { - switch self { - case .send: - return .Icons.General.send - case .addToFavorites: - return .Icons.Star.filled - case .removeFromFavorites: - return .Icons.Star.border - case .link: - return .Icons.General.link - case .unlink: - return .Icons.General.unlink - case .details: - return .Icons.General.profile - } - } - - private var title: String? { - switch self { - case .send: - return localized("contact_book.details.menu.option.send") - case .addToFavorites: - return localized("contact_book.details.menu.option.add_to_favorites") - case .removeFromFavorites: - return localized("contact_book.details.menu.option.remove_from_favorites") - case .link: - return localized("contact_book.details.menu.option.link") - case .unlink: - return localized("contact_book.details.menu.option.unlink") - case .details: - return localized("contact_book.menu.option.details") - } - } -} diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlayView.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlayView.swift deleted file mode 100644 index 889c83ce..00000000 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/RotaryMenuOverlayView.swift +++ /dev/null @@ -1,282 +0,0 @@ -// RotaryMenuOverlayView.swift - -/* - Package MobileWallet - Created by Adrian Truszczyński on 09/06/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 RotaryMenuOverlayView: UIView { - - enum PresentationSide { - case left - case right - } - - // MARK: - Subviews - - @View private var backgroundView = RotaryMenuCircleBackgroundView() - @View private var rotaryMenu = RotaryMenuView() - - @View private var switchSideButton: RoundedButton = { - let view = RoundedButton() - view.setImage(.Icons.RotaryMenu.switchSide, for: .normal) - view.backgroundColor = .Static.white.withAlphaComponent(0.4) - view.tintColor = .Static.black - view.alpha = 0.0 - return view - }() - - @View private var closeButton: RoundedButton = { - let view = RoundedButton() - view.setImage(.Icons.RotaryMenu.close, for: .normal) - view.backgroundColor = .Static.white.withAlphaComponent(0.8) - view.tintColor = .Static.black - view.alpha = 0.0 - return view - }() - - // MARK: - Properties - - var models: [RotaryMenuView.MenuButtonViewModel] = [] - - var avatar: RoundedAvatarView.Avatar { - get { backgroundView.avatar } - set { backgroundView.avatar = newValue } - } - - var onMenuButtonTap: ((UInt) -> Void)? - var onCloseButtonTap: (() -> Void)? - var onSwitchSideButtonTap: (() -> Void)? - - private(set) var presentationSide: PresentationSide - private var previousTouchLocation: CGPoint? - - private var rotationAngle: CGFloat = 0.0 { - didSet { rotaryMenu.transform = CGAffineTransform(rotationAngle: rotationAngle) } - } - - private var leftSidePresentationConstraints: [NSLayoutConstraint] = [] - private var rightSidePresentationConstraints: [NSLayoutConstraint] = [] - - // MARK: - Initalisers - - init(presentationSide: PresentationSide) { - self.presentationSide = presentationSide - super.init(frame: .zero) - setupViews() - setupConstraints() - setupGestures() - setupCallbacks() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupViews() { - backgroundColor = .Static.popupOverlay - } - - private func setupConstraints() { - - [backgroundView, rotaryMenu, switchSideButton, closeButton].forEach(addSubview) - - leftSidePresentationConstraints = [ - backgroundView.centerXAnchor.constraint(equalTo: leadingAnchor), - backgroundView.centerYAnchor.constraint(equalTo: centerYAnchor), - rotaryMenu.centerXAnchor.constraint(equalTo: leadingAnchor), - rotaryMenu.centerYAnchor.constraint(equalTo: centerYAnchor), - rotaryMenu.widthAnchor.constraint(equalTo: backgroundView.widthAnchor), - rotaryMenu.heightAnchor.constraint(equalTo: backgroundView.heightAnchor), - switchSideButton.centerXAnchor.constraint(equalTo: trailingAnchor), - switchSideButton.centerYAnchor.constraint(equalTo: centerYAnchor), - switchSideButton.widthAnchor.constraint(equalToConstant: 62.0), - switchSideButton.heightAnchor.constraint(equalToConstant: 62.0), - closeButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), - closeButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -25.0), - closeButton.widthAnchor.constraint(equalToConstant: 40.0), - closeButton.heightAnchor.constraint(equalToConstant: 40.0) - ] - - rightSidePresentationConstraints = [ - backgroundView.centerXAnchor.constraint(equalTo: trailingAnchor), - backgroundView.centerYAnchor.constraint(equalTo: centerYAnchor), - rotaryMenu.centerXAnchor.constraint(equalTo: trailingAnchor), - rotaryMenu.centerYAnchor.constraint(equalTo: centerYAnchor), - rotaryMenu.widthAnchor.constraint(equalTo: backgroundView.widthAnchor), - rotaryMenu.heightAnchor.constraint(equalTo: backgroundView.heightAnchor), - switchSideButton.centerXAnchor.constraint(equalTo: leadingAnchor), - switchSideButton.centerYAnchor.constraint(equalTo: centerYAnchor), - switchSideButton.widthAnchor.constraint(equalToConstant: 62.0), - switchSideButton.heightAnchor.constraint(equalToConstant: 62.0), - closeButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), - closeButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -25.0), - closeButton.widthAnchor.constraint(equalToConstant: 40.0), - closeButton.heightAnchor.constraint(equalToConstant: 40.0) - ] - - switch presentationSide { - case .left: - NSLayoutConstraint.activate(leftSidePresentationConstraints) - case .right: - NSLayoutConstraint.activate(rightSidePresentationConstraints) - } - } - - private func setupGestures() { - let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handle(panGesture:))) - addGestureRecognizer(panGesture) - } - - private func setupCallbacks() { - - rotaryMenu.onButtonTap = { [weak self] in - self?.onMenuButtonTap?($0) - } - - closeButton.onTap = { [weak self] in - self?.onCloseButtonTap?() - } - - switchSideButton.onTap = { [weak self] in - self?.onSwitchSideButtonTap?() - } - } - - // MARK: - Updates - - private func updateBackgroundViewsLayout() { - - NSLayoutConstraint.deactivate(leftSidePresentationConstraints) - NSLayoutConstraint.deactivate(rightSidePresentationConstraints) - - switch presentationSide { - case .left: - NSLayoutConstraint.activate(leftSidePresentationConstraints) - case .right: - NSLayoutConstraint.activate(rightSidePresentationConstraints) - } - } - - private func updateButtons() { - rotaryMenu.update(buttonViewModels: models, iconLocation: presentationSide.iconLocation) - } - - private func updateAngle(newLocation: CGPoint) { - - guard let previousTouchLocation else { return } - - let centerPoint = rotaryMenu.center - - let xValue = (newLocation.x - centerPoint.x) * (previousTouchLocation.x - centerPoint.x) + (newLocation.y - centerPoint.y) * (previousTouchLocation.y - centerPoint.y) - let yValue = (newLocation.x - centerPoint.x) * (previousTouchLocation.y - centerPoint.y) - (newLocation.y - centerPoint.y) * (previousTouchLocation.x - centerPoint.x) - let angle = atan2(xValue, yValue) - (.pi / 2.0) - - rotationAngle += angle - } - - // MARK: - Actions - - func show() async { - rotationAngle = 0.0 - updateBackgroundViewsLayout() - updateButtons() - await backgroundView.show() - await rotaryMenu.show() - updateButtons(areVisible: true) - } - - func hide() async { - updateButtons(areVisible: false) - await rotaryMenu.hide() - await backgroundView.hide() - } - - func switchSide(presentationSide: PresentationSide) async { - self.presentationSide = presentationSide - await hide() - await show() - } - - private func updateButtons(areVisible: Bool) { - UIView.animate(withDuration: 0.2, delay: 0.0, options: [.curveEaseInOut]) { - self.switchSideButton.alpha = areVisible ? 1.0 : 0.0 - self.closeButton.alpha = areVisible ? 1.0 : 0.0 - } - } - - private func springBack() { - UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.0, options: [.curveEaseInOut]) { - self.rotationAngle = 0.0 - } - } - - // MARK: - Handlers - - @objc private func handle(panGesture: UIPanGestureRecognizer) { - - let location = panGesture.location(in: self) - - switch panGesture.state { - case .began: - previousTouchLocation = location - case .changed: - updateAngle(newLocation: location) - previousTouchLocation = location - case .cancelled, .ended: - previousTouchLocation = nil - springBack() - default: - break - } - } -} - -private extension RotaryMenuOverlayView.PresentationSide { - - var iconLocation: RotaryMenuButton.IconLocation { - switch self { - case .left: - return .left - case .right: - return .right - } - } -} diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuButton.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuButton.swift deleted file mode 100644 index 975f00e1..00000000 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuButton.swift +++ /dev/null @@ -1,170 +0,0 @@ -// RotaryMenuButton.swift - -/* - Package MobileWallet - Created by Adrian Truszczyński on 09/06/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 TariCommon - -final class RotaryMenuButton: BaseButton { - - enum IconLocation { - case left - case right - } - - // MARK: - Subviews - - @View private var gradientView: TariGradientView = { - let view = TariGradientView() - view.layer.cornerRadius = 22.0 - view.clipsToBounds = true - view.isUserInteractionEnabled = false - return view - }() - - @View private(set) var iconView: UIImageView = { - let view = UIImageView() - view.tintColor = .Static.white - view.contentMode = .scaleAspectFit - view.isUserInteractionEnabled = false - return view - }() - - @View private var label: UILabel = { - let view = UILabel() - view.textColor = .Static.white - view.font = .Avenir.heavy.withSize(16.0) - view.numberOfLines = 0 - return view - }() - - // MARK: - Properties - - var icon: UIImage? { - get { iconView.image } - set { iconView.image = newValue } - } - - var title: String? { - get { label.text } - set { label.text = newValue } - } - - var iconLocation: IconLocation = .left { - didSet { updateSubviewsConstraints() } - } - - var maxWidth: CGFloat = 0.0 { - didSet { widthConstraint.constant = maxWidth } - } - - private var allConstraints: [NSLayoutConstraint] = [] - private lazy var widthConstraint = label.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth) - - // MARK: - Initialisers - - init() { - super.init(frame: .zero) - setupConstraints() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupConstraints() { - [gradientView, iconView, label].forEach(addSubview) - updateSubviewsConstraints() - widthConstraint.isActive = true - } - - private func updateSubviewsConstraints() { - - NSLayoutConstraint.deactivate(allConstraints) - - switch iconLocation { - case .left: - allConstraints = makeConstraintsWithIconOnLeft() - case .right: - allConstraints = makeConstraintsWithIconOnRight() - } - - NSLayoutConstraint.activate(allConstraints) - } - - // MARK: - Factories - - func makeConstraintsWithIconOnLeft() -> [NSLayoutConstraint] { - [ - gradientView.topAnchor.constraint(equalTo: topAnchor), - gradientView.leadingAnchor.constraint(equalTo: leadingAnchor), - gradientView.bottomAnchor.constraint(equalTo: bottomAnchor), - gradientView.widthAnchor.constraint(equalToConstant: 44.0), - gradientView.heightAnchor.constraint(equalToConstant: 44.0), - iconView.topAnchor.constraint(equalTo: gradientView.topAnchor, constant: 5.0), - iconView.leadingAnchor.constraint(equalTo: gradientView.leadingAnchor, constant: 5.0), - iconView.trailingAnchor.constraint(equalTo: gradientView.trailingAnchor, constant: -5.0), - iconView.bottomAnchor.constraint(equalTo: gradientView.bottomAnchor, constant: -5.0), - label.topAnchor.constraint(equalTo: topAnchor), - label.leadingAnchor.constraint(equalTo: gradientView.trailingAnchor, constant: 10.0), - label.trailingAnchor.constraint(equalTo: trailingAnchor), - label.bottomAnchor.constraint(equalTo: bottomAnchor) - ] - } - - func makeConstraintsWithIconOnRight() -> [NSLayoutConstraint] { - [ - gradientView.topAnchor.constraint(equalTo: topAnchor), - gradientView.trailingAnchor.constraint(equalTo: trailingAnchor), - gradientView.bottomAnchor.constraint(equalTo: bottomAnchor), - gradientView.widthAnchor.constraint(equalToConstant: 44.0), - gradientView.heightAnchor.constraint(equalToConstant: 44.0), - iconView.topAnchor.constraint(equalTo: gradientView.topAnchor, constant: 5.0), - iconView.leadingAnchor.constraint(equalTo: gradientView.leadingAnchor, constant: 5.0), - iconView.trailingAnchor.constraint(equalTo: gradientView.trailingAnchor, constant: -5.0), - iconView.bottomAnchor.constraint(equalTo: gradientView.bottomAnchor, constant: -5.0), - label.topAnchor.constraint(equalTo: topAnchor), - label.leadingAnchor.constraint(equalTo: leadingAnchor), - label.trailingAnchor.constraint(equalTo: gradientView.leadingAnchor, constant: -10.0), - label.bottomAnchor.constraint(equalTo: bottomAnchor) - ] - } -} diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuCircleBackgroundView.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuCircleBackgroundView.swift deleted file mode 100644 index d8e97e78..00000000 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuCircleBackgroundView.swift +++ /dev/null @@ -1,185 +0,0 @@ -// RotaryMenuCircleBackgroundView.swift - -/* - Package MobileWallet - Created by Adrian Truszczyński on 12/06/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 RotaryMenuCircleBackgroundView: UIView { - - // MARK: - Constants - - private let avatarViewHeight: CGFloat = 170.0 - private let animationTime: TimeInterval = 0.05 - - // MARK: - Subviews - - @View private var avatarView: RoundedAvatarView = { - let view = RoundedAvatarView() - view.backgroundColorType = .static - view.alpha = 0.0 - return view - }() - - @View private var firstCircleView: UIView = { - let view = UIView() - view.backgroundColor = .Static.white.withAlphaComponent(0.7) - view.layer.borderWidth = 1.0 - view.layer.borderColor = UIColor.white.cgColor - view.alpha = 0.0 - return view - }() - - @View private var secondCircleView: UIView = { - let view = UIView() - view.layer.borderWidth = 1.0 - view.layer.borderColor = UIColor.Static.white.withAlphaComponent(0.8).cgColor - view.alpha = 0.0 - return view - }() - - @View private var thirdCircleView: RotaryMenuOuterCircleView = { - let view = RotaryMenuOuterCircleView() - view.alpha = 0.0 - return view - }() - - // MARK: - Properties - - var avatar: RoundedAvatarView.Avatar { - get { avatarView.avatar } - set { avatarView.avatar = newValue } - } - - private var avatarViewSizeConstraints: [NSLayoutConstraint] = [] - - // MARK: - Initialisers - - init() { - super.init(frame: .zero) - setupConstaints() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupConstaints() { - - [thirdCircleView, secondCircleView, firstCircleView, avatarView].forEach(addSubview) - - avatarViewSizeConstraints = [ - avatarView.widthAnchor.constraint(equalToConstant: avatarViewHeight / 2.0), - avatarView.heightAnchor.constraint(equalToConstant: avatarViewHeight / 2.0) - ] - - let constraints = [ - thirdCircleView.topAnchor.constraint(equalTo: topAnchor), - thirdCircleView.leadingAnchor.constraint(equalTo: leadingAnchor), - thirdCircleView.trailingAnchor.constraint(equalTo: trailingAnchor), - thirdCircleView.bottomAnchor.constraint(equalTo: bottomAnchor), - secondCircleView.topAnchor.constraint(equalTo: thirdCircleView.topAnchor, constant: 46.0), - secondCircleView.leadingAnchor.constraint(equalTo: thirdCircleView.leadingAnchor, constant: 46.0), - secondCircleView.trailingAnchor.constraint(equalTo: thirdCircleView.trailingAnchor, constant: -46.0), - secondCircleView.bottomAnchor.constraint(equalTo: thirdCircleView.bottomAnchor, constant: -46.0), - firstCircleView.topAnchor.constraint(equalTo: secondCircleView.topAnchor, constant: 34.0), - firstCircleView.leadingAnchor.constraint(equalTo: secondCircleView.leadingAnchor, constant: 34.0), - firstCircleView.trailingAnchor.constraint(equalTo: secondCircleView.trailingAnchor, constant: -34.0), - firstCircleView.bottomAnchor.constraint(equalTo: secondCircleView.bottomAnchor, constant: -34.0), - avatarView.topAnchor.constraint(equalTo: firstCircleView.topAnchor, constant: 28.0), - avatarView.leadingAnchor.constraint(equalTo: firstCircleView.leadingAnchor, constant: 28.0), - avatarView.trailingAnchor.constraint(equalTo: firstCircleView.trailingAnchor, constant: -28.0), - avatarView.bottomAnchor.constraint(equalTo: firstCircleView.bottomAnchor, constant: -28.0), - avatarView.centerXAnchor.constraint(equalTo: centerXAnchor), - avatarView.centerYAnchor.constraint(equalTo: centerYAnchor) - ] - - NSLayoutConstraint.activate(constraints + avatarViewSizeConstraints) - } - - // MARK: - Actions - - func show() async { - - avatarViewSizeConstraints.forEach { $0.constant = avatarViewHeight } - - await UIView.animate(duration: animationTime, options: [.curveEaseInOut]) { - self.avatarView.alpha = 1.0 - self.firstCircleView.alpha = 1.0 - self.layoutIfNeeded() - } - - await UIView.animate(duration: animationTime, options: [.curveEaseInOut]) { - self.secondCircleView.alpha = 1.0 - } - - await UIView.animate(duration: animationTime, options: [.curveEaseInOut]) { - self.thirdCircleView.alpha = 1.0 - } - } - - func hide() async { - - await UIView.animate(duration: animationTime, options: [.curveEaseInOut]) { - self.thirdCircleView.alpha = 0.0 - } - - await UIView.animate(duration: animationTime, options: [.curveEaseInOut]) { - self.secondCircleView.alpha = 0.0 - } - - avatarViewSizeConstraints.forEach { $0.constant = avatarViewHeight / 2.0 } - - await UIView.animate(duration: animationTime, options: [.curveEaseInOut]) { - self.avatarView.alpha = 0.0 - self.firstCircleView.alpha = 0.0 - self.layoutIfNeeded() - } - } - - // MARK: - Autolayout - - override func layoutSubviews() { - super.layoutSubviews() - firstCircleView.layer.cornerRadius = firstCircleView.bounds.height / 2.0 - secondCircleView.layer.cornerRadius = secondCircleView.bounds.height / 2.0 - thirdCircleView.layer.cornerRadius = thirdCircleView.bounds.height / 2.0 - } -} diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuOuterCircleView.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuOuterCircleView.swift deleted file mode 100644 index 2da5f568..00000000 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuOuterCircleView.swift +++ /dev/null @@ -1,123 +0,0 @@ -// RotaryMenuOuterCircleView.swift - -/* - Package MobileWallet - Created by Adrian Truszczyński on 19/06/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 - -final class RotaryMenuOuterCircleView: UIView { - - // MARK: - Layers - - private let shapeLayer: CAShapeLayer = { - let layer = CAShapeLayer() - layer.strokeColor = UIColor.Static.white.withAlphaComponent(0.8).cgColor - layer.fillColor = UIColor.clear.cgColor - layer.lineWidth = 1.0 - layer.lineCap = .round - layer.lineJoin = .round - return layer - }() - - // MARK: - Initialisers - - init() { - super.init(frame: .zero) - layer.addSublayer(shapeLayer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func updatePath() { - - let path = UIBezierPath() - let radius = bounds.height / 2.0 - let centerPoint = CGPoint(x: bounds.midX, y: bounds.midY) - - path.addQuaterCirtle(centerPoint: centerPoint, radius: radius, normalizedAngle: 0.0, clockwise: true) - path.addQuaterCirtle(centerPoint: centerPoint, radius: radius, normalizedAngle: 90.0, clockwise: false) - path.addQuaterCirtle(centerPoint: centerPoint, radius: radius, normalizedAngle: 180.0, clockwise: true) - path.addQuaterCirtle(centerPoint: centerPoint, radius: radius, normalizedAngle: 270.0, clockwise: false) - - shapeLayer.path = path.cgPath - } - - override func layoutSubviews() { - super.layoutSubviews() - updatePath() - shapeLayer.frame = bounds - } -} - -private extension UIBezierPath { - - func addQuaterCirtle(centerPoint: CGPoint, radius: CGFloat, normalizedAngle: CGFloat, clockwise: Bool) { - - var angles = [0.0, 13.0, 17.0, 20.0, 22.0, 90.0] - - if clockwise { - angles = angles.map { normalizedAngle + $0 } - } else { - angles = angles.map { normalizedAngle + 90.0 - $0 } - } - - move(to: centerPoint.point(distance: .point(radius: radius, normalizedDegrees: angles[0]))) - addArc(withCenter: centerPoint, radius: radius, startAngle: .angle(normalizedDegrees: angles[0]), endAngle: .angle(normalizedDegrees: angles[1]), clockwise: clockwise) - addArrow(tipPoint: centerPoint.point(distance: .point(radius: radius, normalizedDegrees: angles[2])), normalizedDegrees: angles[2], clockwise: clockwise) - addArrow(tipPoint: centerPoint.point(distance: .point(radius: radius, normalizedDegrees: angles[3])), normalizedDegrees: angles[3], clockwise: clockwise) - move(to: centerPoint.point(distance: .point(radius: radius, normalizedDegrees: angles[4]))) - addArc(withCenter: centerPoint, radius: radius, startAngle: .angle(normalizedDegrees: angles[4]), endAngle: .angle(normalizedDegrees: angles[5]), clockwise: clockwise) - } - - private func addArrow(tipPoint: CGPoint, normalizedDegrees: CGFloat, clockwise: Bool) { - - let arrowAngle: CGFloat = 45.0 - let shiftAngle: CGFloat = 90.0 * (clockwise ? -1.0 : 1.0) - - let firstArmPoint: CGPoint = .point(radius: 9.0, normalizedDegrees: normalizedDegrees - arrowAngle + shiftAngle) - let secondArmPoint: CGPoint = .point(radius: 9.0, normalizedDegrees: normalizedDegrees + arrowAngle + shiftAngle) - - move(to: tipPoint) - addLine(to: tipPoint.point(distance: firstArmPoint)) - move(to: tipPoint) - addLine(to: tipPoint.point(distance: secondArmPoint)) - move(to: tipPoint) - } -} diff --git a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuView.swift b/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuView.swift deleted file mode 100644 index df95913d..00000000 --- a/MobileWallet/Screens/Contact Book/List/Rotary Menu/Views/RotaryMenuView.swift +++ /dev/null @@ -1,194 +0,0 @@ -// RotaryMenuView.swift - -/* - Package MobileWallet - Created by Adrian Truszczyński on 09/06/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 RotaryMenuView: UIView { - - struct MenuButtonViewModel: Identifiable { - let id: UInt - let icon: UIImage? - let title: String? - } - - // MARK: - Constants - - private let angleStep: CGFloat = CGFloat(20.0).degToRad - private let animationTime: TimeInterval = 0.2 - - // MARK: - Subviews - - private var buttons: [RotaryMenuButton] { subviews.compactMap { $0 as? RotaryMenuButton }} - - // MARK: - Properties - - private var iconLocation: RotaryMenuButton.IconLocation? - var onButtonTap: ((UInt) -> Void)? - - // MARK: - Updates - - func update(buttonViewModels: [MenuButtonViewModel], iconLocation: RotaryMenuButton.IconLocation) { - removeButtons() - - var buttonViewModels = buttonViewModels - - if iconLocation == .right { - buttonViewModels.reverse() - } - - buttonViewModels - .enumerated() - .forEach { addButton(model: $1, index: $0, iconLocation: iconLocation) } - - updateButtonsConstraints(iconLocation: iconLocation) - } - - private func updateButtonsConstraints(iconLocation: RotaryMenuButton.IconLocation) { - - let angleOffset = CGFloat(buttons.count - 1) * angleStep / 2.0 - - buttons - .enumerated() - .forEach { index, button in - - let angle = CGFloat(index) * angleStep - angleOffset - - let buttonWidth = button.bounds.width - let radius = bounds.width / 2.0 - - let translationVector: CGFloat - - switch iconLocation { - case .left: - translationVector = 1.0 - case .right: - translationVector = -1.0 - } - - button.transform = CGAffineTransform(translationX: (-buttonWidth / 2.0) * translationVector, y: 0.0) - .rotated(by: angle) - .translatedBy(x: (radius + buttonWidth / 2.0) * translationVector, y: 0.0) - } - - self.iconLocation = iconLocation - } - - private func updateButtonsWidth() { - let maxWidth = UIScreen.main.bounds.width - (bounds.width / 2.0) - 50.0 - buttons.forEach { $0.maxWidth = maxWidth } - } - - private func addButton(model: MenuButtonViewModel, index: Int, iconLocation: RotaryMenuButton.IconLocation) { - - @View var button = RotaryMenuButton() - - button.icon = model.icon - button.title = model.title - button.iconLocation = iconLocation - button.alpha = 0.0 - - button.onTap = { [weak self] in - self?.onButtonTap?(model.id) - } - - addSubview(button) - - let horizontalConstraint = button.iconView.centerXAnchor.constraint(equalTo: centerXAnchor) - let verticalConstraint = button.iconView.centerYAnchor.constraint(equalTo: centerYAnchor) - - NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint]) - } - - private func removeButtons() { - buttons.forEach { $0.removeFromSuperview() } - } - - // MARK: - Actions - - func show() async { - await updateButtons(alpha: 1.0) - } - - func hide() async { - await updateButtons(alpha: 0.0) - } - - private func updateButtons(alpha: CGFloat) async { - await withCheckedContinuation { [weak self] continuation in - guard let self else { return } - - self.buttons - .enumerated() - .forEach { index, button in - - let button = button - let delay = TimeInterval(index) * self.animationTime / 6.0 - - UIView.animate(withDuration: self.animationTime, delay: delay, animations: { - button.alpha = alpha - }, completion: { _ in - guard self.buttons.count == index + 1 else { return } - continuation.resume() - }) - } - } - } - - // MARK: - Touches - - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - - let view = subviews.first { - let convertedPoint = $0.convert(point, from: self) - return $0.point(inside: convertedPoint, with: event) - } - - return view != nil - } - - // MARK: - Autolayout - - override func layoutSubviews() { - super.layoutSubviews() - updateButtonsWidth() - guard let iconLocation else { return } - updateButtonsConstraints(iconLocation: iconLocation) - } -} diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift b/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift index ab37f651..af1a4c67 100644 --- a/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift +++ b/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift @@ -43,11 +43,9 @@ import TariCommon final class ContactBookCell: DynamicThemeCell { - struct ViewModel: Identifiable, Hashable { + struct ViewModel: Identifiable { let id: UUID - let name: String - let avatarText: String - let avatarImage: UIImage? + let addressViewModel: AddressView.ViewModel let isFavorite: Bool let contactTypeImage: UIImage? let isSelectable: Bool @@ -55,8 +53,6 @@ final class ContactBookCell: DynamicThemeCell { // MARK: - Subviews - @View private var avatarView = RoundedAvatarView() - @View private var contactTypeBackgroundView: UIView = { let view = UIView() view.layer.cornerRadius = 8.0 @@ -69,15 +65,20 @@ final class ContactBookCell: DynamicThemeCell { return view }() - @View private var nameLabel: UILabel = { - let view = UILabel() - view.font = .Avenir.heavy.withSize(15.0) + @View private var separatorView = UIView() + @View private var contectSectionView = UIView() + + @View private var stackView: UIStackView = { + let view = UIStackView() + view.spacing = 8.0 return view }() + @View private var addressView = AddressView() + @View private var favoriteView: UIImageView = { let view = UIImageView() - view.image = .Icons.Star.filled + view.image = .Icons.General.star view.contentMode = .scaleAspectFit return view }() @@ -122,9 +123,11 @@ final class ContactBookCell: DynamicThemeCell { private func setupConstraints() { - [nameLabel, favoriteView, avatarView, contactTypeBackgroundView, contactTypeView, tickView].forEach(contentView.addSubview) + [contectSectionView, addressView].forEach(stackView.addArrangedSubview) + [contactTypeBackgroundView, contactTypeView, separatorView].forEach(contectSectionView.addSubview) + [tickView, stackView, favoriteView].forEach(contentView.addSubview) - let normalModeConstraint = avatarView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 22.0) + let normalModeConstraint = stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20.0) editModeConstraint = tickView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 22.0) self.normalModeConstraint = normalModeConstraint @@ -133,23 +136,23 @@ final class ContactBookCell: DynamicThemeCell { tickView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), tickView.heightAnchor.constraint(equalToConstant: 24.0), tickView.widthAnchor.constraint(equalToConstant: 24.0), - avatarView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10.0), normalModeConstraint, - avatarView.leadingAnchor.constraint(equalTo: tickView.trailingAnchor, constant: 10.0), - avatarView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10.0), - avatarView.widthAnchor.constraint(equalToConstant: 44.0), - avatarView.heightAnchor.constraint(equalToConstant: 44.0), - contactTypeBackgroundView.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), - contactTypeBackgroundView.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor), + contactTypeBackgroundView.leadingAnchor.constraint(equalTo: contectSectionView.leadingAnchor), + contactTypeBackgroundView.centerYAnchor.constraint(equalTo: contectSectionView.centerYAnchor), contactTypeBackgroundView.widthAnchor.constraint(equalToConstant: 16.0), contactTypeBackgroundView.heightAnchor.constraint(equalToConstant: 16.0), contactTypeView.topAnchor.constraint(equalTo: contactTypeBackgroundView.topAnchor, constant: 3.0), contactTypeView.leadingAnchor.constraint(equalTo: contactTypeBackgroundView.leadingAnchor, constant: 3.0), contactTypeView.trailingAnchor.constraint(equalTo: contactTypeBackgroundView.trailingAnchor, constant: -3.0), contactTypeView.bottomAnchor.constraint(equalTo: contactTypeBackgroundView.bottomAnchor, constant: -3.0), - nameLabel.leadingAnchor.constraint(equalTo: avatarView.trailingAnchor, constant: 10.0), - nameLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - favoriteView.leadingAnchor.constraint(equalTo: nameLabel.trailingAnchor, constant: 10.0), + separatorView.leadingAnchor.constraint(equalTo: contactTypeBackgroundView.trailingAnchor, constant: 8.0), + separatorView.trailingAnchor.constraint(equalTo: contectSectionView.trailingAnchor), + separatorView.centerYAnchor.constraint(equalTo: contectSectionView.centerYAnchor), + separatorView.widthAnchor.constraint(equalToConstant: 1.0), + separatorView.heightAnchor.constraint(equalToConstant: 14.0), + stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 24.0), + stackView.leadingAnchor.constraint(equalTo: tickView.trailingAnchor, constant: 10.0), + stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -24.0), favoriteView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -22.0), favoriteView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor) ] @@ -162,27 +165,19 @@ final class ContactBookCell: DynamicThemeCell { override func update(theme: ColorTheme) { super.update(theme: theme) backgroundColor = theme.backgrounds.primary - nameLabel.textColor = theme.text.heading favoriteView.tintColor = theme.brand.purple contactTypeBackgroundView.backgroundColor = theme.brand.purple contactTypeView.tintColor = theme.buttons.primaryText + separatorView.backgroundColor = theme.text.lightText } func update(viewModel: ViewModel) { - elementID = viewModel.id isSelectable = viewModel.isSelectable - nameLabel.text = viewModel.name - - if let avatarImage = viewModel.avatarImage { - avatarView.avatar = .image(avatarImage) - } else { - avatarView.avatar = .text(viewModel.avatarText) - } - + addressView.update(viewModel: viewModel.addressViewModel) favoriteView.isHidden = !viewModel.isFavorite contactTypeView.image = viewModel.contactTypeImage - contactTypeBackgroundView.isHidden = viewModel.contactTypeImage == nil + contectSectionView.isHidden = viewModel.contactTypeImage == nil } override func setEditing(_ editing: Bool, animated: Bool) { @@ -207,3 +202,14 @@ final class ContactBookCell: DynamicThemeCell { } } } + +extension ContactBookCell.ViewModel: Equatable, Hashable { + + static func == (lhs: ContactBookCell.ViewModel, rhs: ContactBookCell.ViewModel) -> Bool { + lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} diff --git a/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift b/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift index d2ec7305..7bf72e2c 100644 --- a/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift +++ b/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift @@ -49,12 +49,17 @@ final class ContactsManager { case empty } + enum InternalError: Error { + case emptyContactName + } + struct Model: Identifiable { let id: UUID = UUID() let internalModel: InternalContactsManager.ContactModel? let externalModel: ExternalContactsManager.ContactModel? let name: String + let alias: String? let nameComponents: [String] var avatar: String @@ -93,13 +98,15 @@ final class ContactsManager { if let externalModel { nameComponents = [externalModel.firstName, externalModel.lastName] name = nameComponents.joined(separator: " ") + alias = name avatar = nameComponents .map { $0.firstOrEmpty } .joined() } else { - name = internalModel?.alias ?? internalModel?.defaultAlias ?? internalModel?.emojiID.obfuscatedText ?? "" + alias = internalModel?.alias ?? internalModel?.defaultAlias + name = alias ?? internalModel?.addressComponents.formattedCoreAddress ?? "" nameComponents = [name] - avatar = internalModel?.emojiID.firstOrEmpty ?? "" + avatar = internalModel?.addressComponents.spendKey.firstOrEmpty ?? "" } self.name = name @@ -133,7 +140,10 @@ final class ContactsManager { for index in 0..(label: nil, value: CNSocialProfile(urlString: urlString, username: emojiID, userIdentifier: nil, service: Self.auroraServiceName))) + socialProfiles.append(CNLabeledValue(label: nil, value: CNSocialProfile(urlString: urlString, username: emojiAddress, userIdentifier: nil, service: Self.auroraServiceName))) contact.socialProfiles = socialProfiles diff --git a/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift b/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift index 63ce3b42..c1997644 100644 --- a/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift +++ b/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift @@ -44,16 +44,15 @@ final class InternalContactsManager { let alias: String? let defaultAlias: String? - let emojiID: String - let hex: String + let addressComponents: TariAddressComponents let isFavorite: Bool static func == (lhs: InternalContactsManager.ContactModel, rhs: InternalContactsManager.ContactModel) -> Bool { - lhs.hex == rhs.hex + lhs.addressComponents.uniqueIdentifier == rhs.addressComponents.uniqueIdentifier } func hash(into hasher: inout Hasher) { - hasher.combine(hex) + hasher.combine(addressComponents.uniqueIdentifier) } } @@ -61,15 +60,15 @@ final class InternalContactsManager { var models: [ContactModel] = [] - 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 fetchWalletContacts().map { try ContactModel(alias: $0.alias, defaultAlias: nil, addressComponents: $0.address.components, 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) + let placeholder = try $0.components.isUnknownAddress ? localized("transaction.unknown_source") : nil + return try ContactModel(alias: nil, defaultAlias: placeholder, addressComponents: $0.components, isFavorite: false) } return models .reduce(into: [ContactModel]()) { collection, model in - guard collection.first(where: {$0.emojiID == model.emojiID }) == nil else { return } + guard collection.first(where: {$0.addressComponents.uniqueIdentifier == model.addressComponents.uniqueIdentifier }) == nil else { return } collection.append(model) } .sorted { @@ -89,24 +88,24 @@ final class InternalContactsManager { return false } - return $0.emojiID < $1.emojiID + return $0.addressComponents.fullEmoji < $1.addressComponents.fullEmoji } } 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, defaultAlias: nil, emojiID: address.emojis, hex: address.byteVector.hex, isFavorite: isFavorite) + return try ContactModel(alias: name, defaultAlias: nil, addressComponents: address.components, isFavorite: isFavorite) } - func update(name: String, isFavorite: Bool, hex: String) throws { - let address = try TariAddress(hex: hex) + func update(name: String, isFavorite: Bool, base58: String) throws { + let address = try TariAddress(base58: base58) let contact = try Contact(alias: name, isFavorite: isFavorite, addressPointer: address.pointer) try Tari.shared.contacts.upsert(contact: contact) } - func remove(hex: String) throws { - guard let contact = try Tari.shared.contacts.findContact(hex: hex) else { return } + func remove(uniqueIdentifier: String) throws { + guard let contact = try Tari.shared.contacts.findContact(uniqueIdentifier: uniqueIdentifier) else { return } try Tari.shared.contacts.remove(contact: contact) } @@ -124,6 +123,7 @@ final class InternalContactsManager { transactions += Tari.shared.transactions.completed return try transactions + .filter { try !$0.isCoinbase } .map { try $0.address } } } diff --git a/MobileWallet/Screens/Contact Book/PopPresenter+ContactBook.swift b/MobileWallet/Screens/Contact Book/PopPresenter+ContactBook.swift index fcc52604..4b465537 100644 --- a/MobileWallet/Screens/Contact Book/PopPresenter+ContactBook.swift +++ b/MobileWallet/Screens/Contact Book/PopPresenter+ContactBook.swift @@ -40,14 +40,14 @@ extension PopUpPresenter { - @MainActor static func showUnlinkConfirmationDialog(emojiID: String, name: String, confirmationCallback: @escaping () -> Void) { + @MainActor static func showUnlinkConfirmationDialog(address: String, name: String, confirmationCallback: @escaping () -> Void) { let model = PopUpDialogModel( titleComponents: [ StylizedLabel.StylizedText(text: localized("contact_book.unlink_contact.popup.confirmation.title"), style: .normal) ], messageComponents: [ - StylizedLabel.StylizedText(text: localized("contact_book.unlink_contact.popup.confirmation.message.part1", arguments: emojiID), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.unlink_contact.popup.confirmation.message.part1", arguments: address), style: .normal), StylizedLabel.StylizedText(text: name, style: .bold) ], buttons: [ @@ -60,14 +60,14 @@ extension PopUpPresenter { showPopUp(model: model) } - @MainActor static func showUnlinkSuccessDialog(emojiID: String, name: String) { + @MainActor static func showUnlinkSuccessDialog(address: String, name: String) { let model = PopUpDialogModel( titleComponents: [ StylizedLabel.StylizedText(text: localized("contact_book.unlink_contact.popup.success.title"), style: .normal) ], messageComponents: [ - StylizedLabel.StylizedText(text: localized("contact_book.unlink_contact.popup.success.message.part1", arguments: emojiID), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.unlink_contact.popup.success.message.part1", arguments: address), style: .normal), StylizedLabel.StylizedText(text: name, style: .bold) ], buttons: [ diff --git a/MobileWallet/Screens/Home/Home/HomeModel.swift b/MobileWallet/Screens/Home/Home/HomeModel.swift index ff9d57f8..4f6cc097 100644 --- a/MobileWallet/Screens/Home/Home/HomeModel.swift +++ b/MobileWallet/Screens/Home/Home/HomeModel.swift @@ -161,13 +161,13 @@ final class HomeModel { private func updateAvatar() { - guard let emojis = try? Tari.shared.walletAddress.emojis else { + guard let addressComponents = try? Tari.shared.walletAddress.components else { avatar = "" username = "" return } - avatar = emojis.firstOrEmpty - username = emojis.obfuscatedText + avatar = addressComponents.coreAddressPrefix.firstOrEmpty + username = addressComponents.formattedCoreAddress } } diff --git a/MobileWallet/Screens/Home/Home/Views/PopUpNetworkStatusContentView.swift b/MobileWallet/Screens/Home/Home/Views/PopUpNetworkStatusContentView.swift index 42c14d3f..551d5257 100644 --- a/MobileWallet/Screens/Home/Home/Views/PopUpNetworkStatusContentView.swift +++ b/MobileWallet/Screens/Home/Home/Views/PopUpNetworkStatusContentView.swift @@ -41,7 +41,7 @@ import UIKit import TariCommon -final class PopUpNetworkStatusContentView: UIView { +final class PopUpNetworkStatusContentView: DynamicThemeView { // MARK: - Subviews @@ -91,10 +91,19 @@ final class PopUpNetworkStatusContentView: UIView { return view }() + @View private var chainTipLabel: StylizedLabel = { + let view = StylizedLabel() + view.normalFont = .Avenir.medium.withSize(14.0) + view.boldFont = .Avenir.heavy.withSize(14.0) + view.separator = " " + view.textAlignment = .center + return view + }() + // MARK: - Initialisers - init() { - super.init(frame: .zero) + override init() { + super.init() setupConstraints() } @@ -106,7 +115,7 @@ final class PopUpNetworkStatusContentView: UIView { private func setupConstraints() { - addSubview(columnStackView) + [columnStackView, chainTipLabel].forEach(addSubview) [topRowStackView, bottomRowStackView].forEach(columnStackView.addArrangedSubview) [networkStatusView, torStatusView].forEach(topRowStackView.addArrangedSubview) [baseNodeConnectionStatusView, baseNodeSyncStatusView].forEach(bottomRowStackView.addArrangedSubview) @@ -115,8 +124,11 @@ final class PopUpNetworkStatusContentView: UIView { columnStackView.topAnchor.constraint(equalTo: topAnchor), columnStackView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor), columnStackView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor), - columnStackView.bottomAnchor.constraint(equalTo: bottomAnchor), - columnStackView.centerXAnchor.constraint(equalTo: centerXAnchor) + columnStackView.centerXAnchor.constraint(equalTo: centerXAnchor), + chainTipLabel.topAnchor.constraint(equalTo: columnStackView.bottomAnchor, constant: 20.0), + chainTipLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + chainTipLabel.trailingAnchor.constraint(equalTo: trailingAnchor), + chainTipLabel.bottomAnchor.constraint(equalTo: bottomAnchor) ] NSLayoutConstraint.activate(constraints) @@ -124,6 +136,11 @@ final class PopUpNetworkStatusContentView: UIView { // MARK: - Updates + override func update(theme: ColorTheme) { + super.update(theme: theme) + chainTipLabel.textColor = theme.text.body + } + func updateNetworkStatus(text: String, status: StatusView.Status) { networkStatusView.update(text: text, status: status) } @@ -139,6 +156,13 @@ final class PopUpNetworkStatusContentView: UIView { func updateBaseNodeSyncStatus(text: String, status: StatusView.Status) { baseNodeSyncStatusView.update(text: text, status: status) } + + func update(chainTipSuffix: String) { + chainTipLabel.textComponents = [ + StylizedLabel.StylizedText(text: localized("connection_status.popUp.label.chain_tip.prefix"), style: .bold), + StylizedLabel.StylizedText(text: chainTipSuffix, style: .normal) + ] + } } final class StatusView: DynamicThemeView { diff --git a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift index 9285838d..c07500f9 100644 --- a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift +++ b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift @@ -51,7 +51,7 @@ final class TransactionDetailsModel { @Published private(set) var amount: String? @Published private(set) var fee: String? @Published private(set) var transactionDirection: String? - @Published private(set) var emojiIdViewModel: EmojiIdView.ViewModel? + @Published private(set) var addressComponents: TariAddressComponents? @Published private(set) var userAlias: String? @Published private(set) var isContactSectionVisible: Bool = true @Published private(set) var isAddContactButtonVisible: Bool = true @@ -121,7 +121,7 @@ final class TransactionDetailsModel { title = try fetchTitle() transactionState = try fetchTransactionState() transactionDirection = try fetchTransactionDirection() - emojiIdViewModel = try fetchEmojiIdViewModel() + addressComponents = try fetchAddressComponents() isContactSectionVisible = try !transaction.isOneSidedPayment && !transaction.isCoinbase subtitle = try fetchSubtitle() amount = try fetchAmount() @@ -270,16 +270,13 @@ final class TransactionDetailsModel { return MicroTari(fee).formattedWithOperator } - private func fetchEmojiIdViewModel() throws -> EmojiIdView.ViewModel { - let address = try transaction.address - let emojiID = try address.emojis - let hex = try address.byteVector.hex - return EmojiIdView.ViewModel(emojiID: emojiID, hex: hex) + private func fetchAddressComponents() throws -> TariAddressComponents { + try TariAddressComponents(address: transaction.address) } private func fetchContactModel() async throws -> ContactsManager.Model? { try await contactsManager.fetchModels() - return try contactsManager.tariContactModels.first { try $0.internalModel?.hex == transaction.address.byteVector.hex } + return try contactsManager.tariContactModels.first { try $0.internalModel?.addressComponents.uniqueIdentifier == transaction.address.components.uniqueIdentifier } } private func fetchLinkToOpen() -> URL? { diff --git a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsView.swift b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsView.swift index 506f76b5..b4485644 100644 --- a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsView.swift +++ b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsView.swift @@ -67,7 +67,8 @@ final class TransactionDetailsView: DynamicThemeView { @View private(set) var cancelButton: TextButton = { let view = TextButton() view.setTitle(localized("tx_detail.tx_cancellation.cancel"), for: .normal) - view.setVariation(.warning, font: Theme.shared.fonts.textButtonCancel) + view.style = .warning + view.font = .Avenir.medium.withSize(12.0) return view }() diff --git a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift index eedfdfa3..b556df05 100644 --- a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift +++ b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift @@ -102,9 +102,11 @@ final class TransactionDetailsViewController: SecureViewController Void)? { + get { addressView.onViewDetailsButtonTap } + set { addressView.onViewDetailsButtonTap = newValue } + } + // MARK: - Initialisers init() { @@ -75,15 +80,15 @@ final class TransactionDetailsEmojiView: UIView { private func setupConstraints() { - [emojiIdView, addContactButton].forEach(addSubview) + [addressView, addContactButton].forEach(addSubview) let constraints = [ heightAnchor.constraint(equalToConstant: 85.0), - emojiIdView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 22.0), - emojiIdView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -22.0), - emojiIdView.centerYAnchor.constraint(equalTo: centerYAnchor), - addContactButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -22.0), - addContactButton.centerYAnchor.constraint(equalTo: centerYAnchor) + addressView.topAnchor.constraint(equalTo: topAnchor), + addressView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 22.0), + addContactButton.topAnchor.constraint(equalTo: addressView.bottomAnchor, constant: 10.0), + addContactButton.leadingAnchor.constraint(equalTo: addressView.leadingAnchor), + addContactButton.bottomAnchor.constraint(equalTo: bottomAnchor) ] NSLayoutConstraint.activate(constraints) @@ -92,7 +97,7 @@ final class TransactionDetailsEmojiView: UIView { // MARK: - Actions private func updateEmojiIdView() { - guard let emojiIdViewModel = emojiIdViewModel else { return } - emojiIdView.update(viewModel: emojiIdViewModel, textCentered: false) + guard let addressViewModel else { return } + addressView.update(viewModel: addressViewModel) } } diff --git a/MobileWallet/Screens/Home/Transaction Details/Views/TransactionDetailsValueView.swift b/MobileWallet/Screens/Home/Transaction Details/Views/TransactionDetailsValueView.swift index c183310b..c1261a4d 100644 --- a/MobileWallet/Screens/Home/Transaction Details/Views/TransactionDetailsValueView.swift +++ b/MobileWallet/Screens/Home/Transaction Details/Views/TransactionDetailsValueView.swift @@ -69,8 +69,8 @@ final class TransactionDetailsValueView: DynamicThemeView { @View private(set) var feeButton: TextButton = { let view = TextButton() view.setTitle(localized("common.fee"), for: .normal) - view.titleLabel?.font = Theme.shared.fonts.txFeeButton - view.setRightImage(Theme.shared.images.txFee) + view.font = .Avenir.roman.withSize(13.0) + view.image = .Icons.General.roundedQuestionMark return view }() diff --git a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift index 1da4980b..8521e312 100644 --- a/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift +++ b/MobileWallet/Screens/Home/Transaction History/TransactionHistoryViewController.swift @@ -101,7 +101,6 @@ final class TransactionHistoryViewController: SecureViewController() + private(set) var addressComponents: TariAddressComponents? private var walletAddress: TariAddress? private var yat: String? private var bleTask: BLECentralTask? + private var cancellables = Set() // MARK: - Initialisers @@ -101,7 +100,7 @@ final class ProfileModel { .store(in: &cancellables) $yatButtonState - .sink { [weak self] in try? self?.updatePresentedData(yatButtonState: $0) } + .sink { [weak self] in self?.update(yatButtonState: $0) } .store(in: &cancellables) } @@ -119,7 +118,20 @@ final class ProfileModel { } func reconnectYat() { - yatAddress = try? walletAddress?.byteVector.hex + yatAddress = try? walletAddress?.components.fullRaw + } + + func update(name: String?) { + guard let name, !name.isEmpty else { + errorMessage = MessageModel( + title: localized("profile_view.error.no_name.title"), + message: localized("profile_view.error.no_name.description"), + closeButtonTitle: localized("profile_view.error.no_name.button"), + type: .normal + ) + return + } + self.name = name } private func updateData() { @@ -127,9 +139,11 @@ final class ProfileModel { name = UserSettingsManager.name do { - self.walletAddress = try Tari.shared.walletAddress + walletAddress = try Tari.shared.walletAddress + guard let walletAddress else { return } + addressComponents = try walletAddress.components } catch { - emojiData = nil + addressComponents = nil errorMessage = MessageModel(title: localized("profile_view.error.qr_code.title"), message: localized("wallet.error.failed_to_access"), type: .error) } } @@ -140,7 +154,7 @@ final class ProfileModel { yatButtonState = .loading - Yat.api.emojiID.lookupEmojiIDPaymentPublisher(emojiId: connectedYat, tags: YatRecordTag.XTRAddress.rawValue) + Yat.api.emojiID.lookupEmojiIDPaymentPublisher(emojiId: connectedYat, tags: YatRecordTag.XTMAddress.rawValue) .sink( receiveCompletion: { [weak self] in self?.handle(completion: $0) }, receiveValue: { [weak self] in self?.handle(paymentAddressResponse: $0, yat: connectedYat) } @@ -148,6 +162,17 @@ final class ProfileModel { .store(in: &cancellables) } + private func update(yatButtonState: YatButtonState) { + switch yatButtonState { + case .on: + guard let yat else { return } + addressType = .yat(yat) + case .off, .loading, .hidden: + guard let addressComponents else { return } + addressType = .address(components: addressComponents) + } + } + func generateQrCode() { action = .showQRPopUp @@ -188,8 +213,8 @@ final class ProfileModel { private func makeDeeplink() throws -> URL? { guard let alias = name else { return nil } - let hex = try Tari.shared.walletAddress.byteVector.hex - let deeplinkModel = UserProfileDeeplink(alias: alias, tariAddress: hex) + let rawAddress = try Tari.shared.walletAddress.components.fullRaw + let deeplinkModel = UserProfileDeeplink(alias: alias, tariAddress: rawAddress) return try DeepLinkFormatter.deeplink(model: deeplinkModel) } @@ -198,17 +223,6 @@ final class ProfileModel { self.errorMessage = ErrorMessageManager.errorModel(forError: error) } - private func updatePresentedData(yatButtonState: YatButtonState) throws { - switch yatButtonState { - 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")) - case .on: - guard let yat = self.yat else { return } - emojiData = EmojiData(emojiID: yat, hex: nil, copyText: localized("emoji.yat.copy"), tooltipText: nil) - } - } - // MARK: - Handlers private func handle(paymentAddressResponse: PaymentAddressResponse, yat: String) { @@ -216,12 +230,12 @@ final class ProfileModel { self.yat = yat yatButtonState = .off - guard let walletAddress = paymentAddressResponse.result?[YatRecordTag.XTRAddress.rawValue]?.address else { + guard let walletAddress = paymentAddressResponse.result?[YatRecordTag.XTMAddress.rawValue]?.address else { isYatOutOfSync = true return } - isYatOutOfSync = walletAddress != (try? self.walletAddress?.byteVector.hex) + isYatOutOfSync = walletAddress != (try? self.walletAddress?.components.fullRaw) } private func handle(completion: Subscribers.Completion) { diff --git a/MobileWallet/Screens/Profile/ProfileView.swift b/MobileWallet/Screens/Profile/ProfileView.swift index 3903f7d3..48b642d0 100644 --- a/MobileWallet/Screens/Profile/ProfileView.swift +++ b/MobileWallet/Screens/Profile/ProfileView.swift @@ -52,7 +52,18 @@ final class ProfileView: BaseNavigationContentView { return view }() - @View private var emojiIdView = EmojiIdView() + @View private var addressView: RoundedAddressView = { + let view = RoundedAddressView() + view.isCompact = UIScreen.isSmallScreen + return view + }() + + @View private var yatView: RoundedAddressView = { + let view = RoundedAddressView() + view.isCompact = UIScreen.isSmallScreen + return view + }() + @View var yatButton: BaseButton = BaseButton() @View private var yatSpinnerView: AnimationView = { @@ -169,6 +180,11 @@ final class ProfileView: BaseNavigationContentView { } } + var onViewDetailsButtonTap: (() -> Void)? { + get { addressView.onViewDetailsButtonTap } + set { addressView.onViewDetailsButtonTap = newValue } + } + var onEditButtonTap: (() -> Void)? var onWalletButtonTap: (() -> Void)? var onConnectYatButtonTap: (() -> Void)? @@ -209,11 +225,12 @@ final class ProfileView: BaseNavigationContentView { private func setupConstraints() { - [usernameLabel, emojiIdView, yatButton, yatSpinnerView, yatOutOfSyncLabel, shareSectionSeparator, shareSectionTitleLabel, shareSectionDescriptionLabel, auroraButtonsStackView, shareButtonsStackView].forEach(addSubview) + [usernameLabel, addressView, yatView, 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) + let auroraButtonsTopConstraintOnYatLabelHidden = auroraButtonsStackView.topAnchor.constraint(equalTo: addressView.bottomAnchor, constant: 20.0) self.auroraButtonsTopConstraintOnYatLabelHidden = auroraButtonsTopConstraintOnYatLabelHidden auroraButtonsTopConstraintOnYatLabelShown = auroraButtonsStackView.topAnchor.constraint(equalTo: yatOutOfSyncLabel.bottomAnchor, constant: 20.0) @@ -221,19 +238,21 @@ final class ProfileView: BaseNavigationContentView { 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), - yatButton.leadingAnchor.constraint(equalTo: emojiIdView.trailingAnchor, constant: 4.0), - yatButton.centerYAnchor.constraint(equalTo: emojiIdView.centerYAnchor), + addressView.topAnchor.constraint(equalTo: usernameLabel.bottomAnchor, constant: 20.0), + addressView.centerXAnchor.constraint(equalTo: centerXAnchor), + yatView.topAnchor.constraint(equalTo: addressView.topAnchor), + yatView.leadingAnchor.constraint(equalTo: addressView.leadingAnchor), + yatView.trailingAnchor.constraint(equalTo: addressView.trailingAnchor), + yatView.bottomAnchor.constraint(equalTo: addressView.bottomAnchor), + yatButton.leadingAnchor.constraint(equalTo: addressView.trailingAnchor, constant: 4.0), + yatButton.centerYAnchor.constraint(equalTo: addressView.centerYAnchor), yatButton.heightAnchor.constraint(equalToConstant: 32.0), yatButton.widthAnchor.constraint(equalToConstant: 32.0), yatSpinnerView.centerXAnchor.constraint(equalTo: yatButton.centerXAnchor), yatSpinnerView.centerYAnchor.constraint(equalTo: yatButton.centerYAnchor), yatSpinnerView.heightAnchor.constraint(equalToConstant: 28.0), yatSpinnerView.widthAnchor.constraint(equalToConstant: 28.0), - yatOutOfSyncLabel.topAnchor.constraint(equalTo: emojiIdView.bottomAnchor, constant: 20.0), + yatOutOfSyncLabel.topAnchor.constraint(equalTo: addressView.bottomAnchor, constant: 20.0), yatOutOfSyncLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), yatOutOfSyncLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), auroraButtonsTopConstraintOnYatLabelHidden, @@ -315,10 +334,16 @@ final class ProfileView: BaseNavigationContentView { } } - func update(emojiID: String, hex: String?, copyText: String, tooltopText: String?) { - emojiIdView.copyText = copyText - emojiIdView.tooltipText = tooltopText - emojiIdView.update(viewModel: EmojiIdView.ViewModel(emojiID: emojiID, hex: hex)) + func update(addressViewModel: AddressView.ViewModel, isTariAddress: Bool) { + if isTariAddress { + addressView.update(viewModel: addressViewModel) + addressView.isHidden = false + yatView.isHidden = true + } else { + yatView.update(viewModel: addressViewModel) + addressView.isHidden = true + yatView.isHidden = false + } } private func updateYatButton(isOn: Bool) { diff --git a/MobileWallet/Screens/Profile/ProfileViewController.swift b/MobileWallet/Screens/Profile/ProfileViewController.swift index db6c016b..5e2260d0 100644 --- a/MobileWallet/Screens/Profile/ProfileViewController.swift +++ b/MobileWallet/Screens/Profile/ProfileViewController.swift @@ -65,7 +65,7 @@ final class ProfileViewController: SecureViewController { override func viewDidLoad() { super.viewDidLoad() - setupBindings() + setupCallbacks() } override func viewDidAppear(_ animated: Bool) { @@ -75,17 +75,17 @@ final class ProfileViewController: SecureViewController { // MARK: - Setups - private func setupBindings() { + private func setupCallbacks() { model.$name .receive(on: DispatchQueue.main) .sink { [weak self] in self?.mainView.update(username: $0) } .store(in: &cancellables) - model.$emojiData + model.$addressType .compactMap { $0 } .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?.handle(addressType: $0) } .store(in: &cancellables) model.$isYatOutOfSync @@ -107,7 +107,7 @@ final class ProfileViewController: SecureViewController { model.$yatAddress .compactMap { $0 } .receive(on: DispatchQueue.main) - .sink { [weak self] in self?.showYatOnboardingFlow(publicKey: $0) } + .sink { [weak self] in self?.showYatOnboardingFlow(rawAddress: $0) } .store(in: &cancellables) model.$action @@ -143,10 +143,28 @@ final class ProfileViewController: SecureViewController { mainView.onBleButtonTap = { [weak self] in self?.model.shareContactUsingBLE() } + + guard let addressComponents = model.addressComponents else { return } + mainView.onViewDetailsButtonTap = AddressViewDefaultActions.showDetailsAction(addressComponents: addressComponents) + + NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) + .sink { [weak self] _ in self?.model.updateYatIdData() } + .store(in: &cancellables) } // MARK: - Actions + private func handle(addressType: ProfileModel.AddressType) { + switch addressType { + case let .address(components): + let viewModel = AddressView.ViewModel(prefix: components.networkAndFeatures, text: .truncated(prefix: components.coreAddressPrefix, suffix: components.coreAddressSuffix), isDetailsButtonVisible: true) + mainView.update(addressViewModel: viewModel, isTariAddress: true) + case let .yat(yat): + let viewModel = AddressView.ViewModel(prefix: nil, text: .single(yat), isDetailsButtonVisible: false) + mainView.update(addressViewModel: viewModel, isTariAddress: false) + } + } + private func handle(yatButtonState: ProfileModel.YatButtonState) { switch yatButtonState { case .hidden: @@ -164,9 +182,9 @@ final class ProfileViewController: SecureViewController { PopUpPresenter.show(message: error) } - private func showYatOnboardingFlow(publicKey: String) { + private func showYatOnboardingFlow(rawAddress: String) { Yat.integration.showOnboarding(onViewController: self, records: [ - YatRecordInput(tag: .XTRAddress, value: publicKey) + YatRecordInput(tag: .XTMAddress, value: rawAddress) ]) } @@ -207,7 +225,7 @@ final class ProfileViewController: SecureViewController { ] FormOverlayPresenter.showForm(title: localized("profile_view.form.title"), textFieldModels: models, presenter: self, onClose: { [weak self] in - self?.model.name = name + self?.model.update(name: name) }) } diff --git a/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationController.swift b/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationController.swift deleted file mode 100644 index b3eb24f8..00000000 --- a/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationController.swift +++ /dev/null @@ -1,93 +0,0 @@ -// QRCodePresenterController.swift - -/* - Package MobileWallet - Created by Adrian Truszczynski on 18/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 UIKit - -@available(*, deprecated, message: "This class is deprecated and will be removed later. Please user PopUpPresenter.showQRCodeDialog() instead.") -final class QRCodePresentationController: SecureViewController { - - // MARK: - Properties - - var onShareButtonTap: (() -> Void)? - - // MARK: - Initialisers - - init(image: UIImage) { - super.init(nibName: nil, bundle: nil) - modalTransitionStyle = .crossDissolve - modalPresentationStyle = .overFullScreen - - mainView.qrCodeView.state = .image(image) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - View Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - setupBindings() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - mainView.showContent() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - mainView.hideContent() - } - - // MARK: - Setups - - private func setupBindings() { - - mainView.shareButton.onTap = { [weak self] in - self?.onShareButtonTap?() - } - - mainView.closeButton.onTap = { [weak self] in - self?.dismiss(animated: true) - } - } -} diff --git a/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationView.swift b/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationView.swift deleted file mode 100644 index c2eea54d..00000000 --- a/MobileWallet/Screens/Profile/RequestTari/QrCode/QRCodePresentationView.swift +++ /dev/null @@ -1,151 +0,0 @@ -// QRCodePresentationView.swift - -/* - Package MobileWallet - Created by Adrian Truszczynski on 18/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 UIKit -import TariCommon - -final class QRCodePresentationView: DynamicThemeView { - - // MARK: - Subviews - - @View private var contentView: UIView = { - let view = UIView() - view.layer.cornerRadius = 26.0 - return view - }() - - @View var qrCodeView = QRCodeView() - - @View var shareButton: ActionButton = { - let view = ActionButton() - view.setTitle(localized("request.qr_code.buttons.share"), for: .normal) - return view - }() - - @View var closeButton: TextButton = { - let view = TextButton() - view.setTitle(localized("request.qr_code.buttons.close"), for: .normal) - view.setVariation(.warning) - return view - }() - - // MARK: - Properties - - private var contentViewTopConstraint: NSLayoutConstraint? - private var contentViewBottomConstraint: NSLayoutConstraint? - - // MARK: - Initialisers - - override init() { - super.init() - setupViews() - setupConstraints() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupViews() { - backgroundColor = .Static.popupOverlay - } - - private func setupConstraints() { - - addSubview(contentView) - [qrCodeView, shareButton, closeButton].forEach(contentView.addSubview) - - let contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: bottomAnchor) - - self.contentViewTopConstraint = contentViewTopConstraint - contentViewBottomConstraint = contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -18.0) - - let constraints = [ - contentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 14.0), - contentView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -14.0), - contentViewTopConstraint, - qrCodeView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10.0), - qrCodeView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - qrCodeView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.667), - qrCodeView.heightAnchor.constraint(equalTo: qrCodeView.widthAnchor), - shareButton.topAnchor.constraint(equalTo: qrCodeView.bottomAnchor, constant: 30.0), - shareButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - shareButton.widthAnchor.constraint(equalToConstant: 165.0), - closeButton.topAnchor.constraint(equalTo: shareButton.bottomAnchor, constant: 10.0), - closeButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - closeButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -15.0), - closeButton.widthAnchor.constraint(equalToConstant: 165.0) - ] - - NSLayoutConstraint.activate(constraints) - } - - // MARK: - Actions - - func showContent() { - - contentViewTopConstraint?.isActive = false - contentViewBottomConstraint?.isActive = true - - UIView.animate(withDuration: 0.3) { [weak self] in - self?.layoutIfNeeded() - } - } - - func hideContent() { - - contentViewBottomConstraint?.isActive = false - contentViewTopConstraint?.isActive = true - - UIView.animate(withDuration: 0.3) { [weak self] in - self?.layoutIfNeeded() - } - } - - // MARK: - Updates - - override func update(theme: ColorTheme) { - super.update(theme: theme) - contentView.backgroundColor = theme.backgrounds.primary - qrCodeView.apply(shadow: theme.shadows.box) - } -} diff --git a/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountModel.swift b/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountModel.swift index aec0d5d1..c6a3f0f5 100644 --- a/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountModel.swift +++ b/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountModel.swift @@ -103,8 +103,8 @@ final class RequestTariAmountModel { // MARK: - Factories private func makeDeeplink() -> URL? { - guard let hex = try? Tari.shared.walletAddress.byteVector.hex, let tariAmount = try? MicroTari(tariValue: amountFormatter.amount) else { return nil } - let model = TransactionsSendDeeplink(receiverAddress: hex, amount: tariAmount.rawValue, note: nil) + guard let receiverAddress = try? Tari.shared.walletAddress.components.fullRaw, let tariAmount = try? MicroTari(tariValue: amountFormatter.amount) else { return nil } + let model = TransactionsSendDeeplink(receiverAddress: receiverAddress, amount: tariAmount.rawValue, note: nil) return try? DeepLinkFormatter.deeplink(model: model) } } diff --git a/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountView.swift b/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountView.swift index ae095c07..d7a38415 100644 --- a/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountView.swift +++ b/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountView.swift @@ -66,7 +66,6 @@ final class RequestTariAmountView: DynamicThemeView { @View var shareButton: ActionButton = { let view = ActionButton() view.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal) - view.imageEdgeInsets = .zero return view }() @@ -81,8 +80,8 @@ final class RequestTariAmountView: DynamicThemeView { var areButtonsEnabled: Bool = false { didSet { - generateQrButton.variation = areButtonsEnabled ? .normal : .disabled - shareButton.variation = areButtonsEnabled ? .normal : .disabled + generateQrButton.isEnabled = areButtonsEnabled + shareButton.isEnabled = areButtonsEnabled } } diff --git a/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountViewController.swift b/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountViewController.swift index 61e4aad4..6622f3b9 100644 --- a/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountViewController.swift +++ b/MobileWallet/Screens/Profile/RequestTari/RequestTariAmountViewController.swift @@ -107,15 +107,20 @@ final class RequestTariAmountViewController: UIViewController { private func showQrCode(image: UIImage) { - let controller = QRCodePresentationController(image: image) - - controller.onShareButtonTap = { [weak controller, weak self] in - controller?.dismiss(animated: true) { - self?.model.shareActionRequest() - } - } - - present(controller, animated: true) + let content = PopUpPresenter.showQRCodeDialog( + additionalButtons: [ + PopUpDialogButtonModel( + title: localized("request.qr_code.buttons.share"), + type: .normal, + callback: { [weak self] in + PopUpPresenter.dismissPopup() + self?.model.shareActionRequest() + } + ) + ] + ) + + content.qrCode = image } private func showShareDialog(data: RequestTariAmountModel.DeeplinkData) { diff --git a/MobileWallet/Screens/QR Code Scanner/QRCodeScannerModel.swift b/MobileWallet/Screens/QR Code Scanner/QRCodeScannerModel.swift index 61c5acc2..66b49fe7 100644 --- a/MobileWallet/Screens/QR Code Scanner/QRCodeScannerModel.swift +++ b/MobileWallet/Screens/QR Code Scanner/QRCodeScannerModel.swift @@ -48,7 +48,7 @@ enum QRCodeData { final class QRCodeScannerModel { struct ActionModel { - let title: String? + let title: String let isValid: Bool } @@ -180,9 +180,13 @@ final class QRCodeScannerModel { private func handle(unexpectedDeeplink: DeepLinkable, scanResult: VideoCaptureManager.ScanResult) { Task { - let actionTitle = try? await actionTitle(deeplink: unexpectedDeeplink) - actionModel = ActionModel(title: actionTitle, isValid: true) - self.scannedData = scanResult + do { + let actionTitle = try await actionTitle(deeplink: unexpectedDeeplink) + actionModel = ActionModel(title: actionTitle, isValid: true) + self.scannedData = scanResult + } catch { + handleInvalidData() + } } } @@ -193,7 +197,8 @@ final class QRCodeScannerModel { case .transactionSend: guard let deeplink = deeplink as? TransactionsSendDeeplink else { return "" } try await transactionFormatter.updateContactsData() - let contactName = try transactionFormatter.contact(hex: deeplink.receiverAddress)?.name ?? TariAddress(hex: deeplink.receiverAddress).emojis.obfuscatedText + let address = try TariAddress(base58: deeplink.receiverAddress) + let contactName = try transactionFormatter.contact(uniqueIdentifier: address.components.uniqueIdentifier)?.name ?? address.components.formattedCoreAddress return localized("qr_code_scanner.labels.actions.transaction_send", arguments: contactName) case .baseNodesAdd: return localized("qr_code_scanner.labels.actions.base_node_add") diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift index 63906f6b..3050bcc5 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift @@ -166,7 +166,15 @@ final class RestoreWalletFromSeedsModel { } } + func deleteWallet() { + Tari.shared.deleteWallet() + Tari.shared.canAutomaticalyReconnectWallet = false + } + private func restoreWallet(seedWords: [String]) { + + deleteWallet() + do { try Tari.shared.restoreWallet(seedWords: seedWords) try selectCustomBaseNode() diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsView.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsView.swift index eb3a65dd..58c70d9a 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsView.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsView.swift @@ -58,7 +58,7 @@ final class RestoreWalletFromSeedsView: BaseNavigationContentView { @View private(set) var selectBaseNodeButton: TextButton = { let view = TextButton() - view.setVariation(.secondary) + view.style = .secondary return view }() @@ -133,7 +133,7 @@ final class RestoreWalletFromSeedsView: BaseNavigationContentView { } func update(buttonIsEnabledStatus: Bool) { - submitButton.variation = buttonIsEnabledStatus ? .normal : .disabled + submitButton.isEnabled = buttonIsEnabledStatus } // MARK: - First Responder diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift index c4cd3afb..b967e362 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift @@ -143,6 +143,10 @@ final class RestoreWalletFromSeedsViewController: SecureViewController() snapshot.appendSections([0]) snapshot.appendItems(seedWords) - dataSource?.apply(delayedSnapshot: snapshot, animatingDifferences: true) { [weak self] in + dataSource?.apply(delayedSnapshot: snapshot, animatingDifferences: false) { [weak self] in guard let self = self else { return } self.collectionView.scrollToBottom(animated: true) self.heightConstraint?.constant = self.collectionView.contentSize.height diff --git a/MobileWallet/Screens/RestoreWalletFromSeedsProgress/SeedWordsRecoveryProgressViewController.swift b/MobileWallet/Screens/RestoreWalletFromSeedsProgress/SeedWordsRecoveryProgressViewController.swift index 9719a549..acc78246 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeedsProgress/SeedWordsRecoveryProgressViewController.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeedsProgress/SeedWordsRecoveryProgressViewController.swift @@ -46,6 +46,7 @@ final class SeedWordsRecoveryProgressViewController: SecureViewController Void)? + var onFailure: (() -> Void)? private let model = SeedWordsRecoveryProgressModel() private var cancelables: Set = [] @@ -86,6 +87,7 @@ final class SeedWordsRecoveryProgressViewController: SecureViewController PaymentInfo? { guard let amount = calculateAmount(), let feePerGram = feePerGram else { return nil } - return PaymentInfo(address: paymentInfo.address, alias: paymentInfo.alias, yatID: paymentInfo.yatID, amount: amount, feePerGram: feePerGram, note: paymentInfo.note) + return PaymentInfo(addressComponents: paymentInfo.addressComponents, alias: paymentInfo.alias, yatID: paymentInfo.yatID, amount: amount, feePerGram: feePerGram, note: paymentInfo.note) } private func updateNextStepElements(isEnabled: Bool) { - continueButton.variation = isEnabled ? .normal : .disabled - sliderBar.isEnabled = isEnabled - } - - private func showTransactionProgress() { - guard let paymentInfo = updatedPaymentInfo() else { return } - TransactionProgressPresenter.showTransactionProgress(presenter: self, paymentInfo: paymentInfo, isOneSidedPayment: oneSidedPaymentSwitch.isOn) - } - - @objc private func onOneSidedPaymentSwitchAction() { - UIView.animate(withDuration: 0.3, delay: 0.0, options: [.beginFromCurrentState]) { - self.continueButton.alpha = self.oneSidedPaymentSwitch.isOn ? 0.0 : 1.0 - self.sliderBar.alpha = self.oneSidedPaymentSwitch.isOn ? 1.0 : 0.0 - } + continueButton.isEnabled = isEnabled } private func updateFeeButtonText(fee: MicroTari?) { @@ -536,10 +511,10 @@ extension AddAmountViewController { navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true - navigationBar.addSubview(emojiIdView) - - emojiIdView.centerXAnchor.constraint(equalTo: navigationBar.contentView.centerXAnchor).isActive = true - emojiIdView.centerYAnchor.constraint(equalTo: navigationBar.contentView.centerYAnchor).isActive = true + navigationBar.addSubview(addressView) + + addressView.centerXAnchor.constraint(equalTo: navigationBar.contentView.centerXAnchor).isActive = true + addressView.centerYAnchor.constraint(equalTo: navigationBar.contentView.centerYAnchor).isActive = true // contiue button mainView.addSubview(continueButton) @@ -719,21 +694,6 @@ extension AddAmountViewController { oneSidedPaymentHelpButton.onTap = { PopUpPresenter.show(message: MessageModel(title: localized("add_amount.pop_up.one_sided_payment.title"), message: localized("add_amount.pop_up.one_sided_payment.description"), type: .normal)) } - - oneSidedPaymentSwitch.addTarget(self, action: #selector(onOneSidedPaymentSwitchAction), for: .valueChanged) - } - - private func setupSliderBar() { - - mainView.addSubview(sliderBar) - - let constraints = [ - sliderBar.topAnchor.constraint(equalTo: continueButton.topAnchor), - sliderBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 25.0), - sliderBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -25.0) - ] - - NSLayoutConstraint.activate(constraints) } private func setupCallbacks() { @@ -746,9 +706,7 @@ extension AddAmountViewController { self?.showModifyFeeDialog() } - sliderBar.onSlideToEnd = { [weak self] in - self?.showTransactionProgress() - } + addressView.onViewDetailsButtonTap = AddressViewDefaultActions.showDetailsAction(addressComponents: paymentInfo.addressComponents) $fee .receive(on: DispatchQueue.main) diff --git a/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift b/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift index cc1cff6e..ed0cdfc8 100644 --- a/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift +++ b/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift @@ -52,7 +52,7 @@ final class AddNoteViewController: DynamicThemeViewController, UIScrollViewDeleg private let sidePadding = Theme.shared.sizes.appSidePadding @View private var navigationBar = NavigationBar() - @View private var emojiIdView = EmojiIdView() + @View private var addressView = AddressView() fileprivate let scrollView = UIScrollView() fileprivate let stackView = UIStackView() fileprivate let sendButton = SlideView() @@ -145,21 +145,22 @@ final class AddNoteViewController: DynamicThemeViewController, UIScrollViewDeleg var alias: String? do { - alias = try paymentInfo.alias ?? Tari.shared.contacts.findContact(hex: paymentInfo.address)?.alias + alias = try paymentInfo.alias ?? Tari.shared.contacts.findContact(uniqueIdentifier: paymentInfo.addressComponents.uniqueIdentifier)?.alias } catch { } guard let alias = alias, !alias.trimmingCharacters(in: .whitespaces).isEmpty else { - do { - let tariAddress = try TariAddress(hex: paymentInfo.address) - emojiIdView.setup(emojiID: try tariAddress.emojis, hex: paymentInfo.address, textCentered: true, inViewController: self) - } catch { - PopUpPresenter.show(message: MessageModel(title: localized("navigation_bar.error.show_emoji.title"), message: localized("navigation_bar.error.show_emoji.description"), type: .error)) - } + let addressComponents = paymentInfo.addressComponents + addressView.update( + viewModel: AddressView.ViewModel( + prefix: addressComponents.networkAndFeatures, + text: .truncated(prefix: addressComponents.coreAddressPrefix, suffix: addressComponents.coreAddressSuffix), + isDetailsButtonVisible: true) + ) return } - navigationBar.title = alias + addressView.update(viewModel: AddressView.ViewModel(prefix: nil, text: .single(alias), isDetailsButtonVisible: true)) } func updateTitleColorAndSetSendButtonState() { @@ -270,7 +271,7 @@ final class AddNoteViewController: DynamicThemeViewController, UIScrollViewDeleg message += " \(embedUrl)" } - let paymentInfo = PaymentInfo(address: paymentInfo.address, alias: paymentInfo.alias, yatID: paymentInfo.yatID, amount: paymentInfo.amount, feePerGram: paymentInfo.feePerGram, note: message) + let paymentInfo = PaymentInfo(addressComponents: paymentInfo.addressComponents, alias: paymentInfo.alias, yatID: paymentInfo.yatID, amount: paymentInfo.amount, feePerGram: paymentInfo.feePerGram, note: message) TransactionProgressPresenter.showTransactionProgress(presenter: self, paymentInfo: paymentInfo, isOneSidedPayment: isOneSidedPayment) } @@ -297,7 +298,7 @@ extension AddNoteViewController { private func setupNavigationBar() { mainView.addSubview(navigationBar) - navigationBar.addSubview(emojiIdView) + navigationBar.addSubview(addressView) navigationBar.isSeparatorVisible = false @@ -305,11 +306,13 @@ extension AddNoteViewController { navigationBar.topAnchor.constraint(equalTo: view.topAnchor), navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - emojiIdView.centerXAnchor.constraint(equalTo: navigationBar.contentView.centerXAnchor), - emojiIdView.centerYAnchor.constraint(equalTo: navigationBar.contentView.centerYAnchor) + addressView.centerXAnchor.constraint(equalTo: navigationBar.contentView.centerXAnchor), + addressView.centerYAnchor.constraint(equalTo: navigationBar.contentView.centerYAnchor) ] NSLayoutConstraint.activate(constraints) + + addressView.onViewDetailsButtonTap = AddressViewDefaultActions.showDetailsAction(addressComponents: paymentInfo.addressComponents) } fileprivate func setupNoteTitle() { @@ -425,12 +428,8 @@ extension AddNoteViewController { giphyVC.view.bottomAnchor.constraint(equalTo: giphyCarouselContainerView.bottomAnchor).isActive = true giphyVC.view.heightAnchor.constraint(equalToConstant: 64).isActive = true - searchGiphyButton.setImage(Theme.shared.images.searchIcon, for: .normal) - searchGiphyButton.titleLabel?.font = Theme.shared.fonts.searchGiphyButtonTitle - searchGiphyButton.setTitle(localized("add_note.search_giphy_button"), for: .normal) searchGiphyButton.translatesAutoresizingMaskIntoConstraints = false searchGiphyButton.layer.cornerRadius = 3 - searchGiphyButton.titleEdgeInsets = .init(top: 0, left: 5, bottom: 0, right: 0) giphyCarouselContainerView.addSubview(searchGiphyButton) searchGiphyButton.heightAnchor.constraint(equalToConstant: 18).isActive = true searchGiphyButton.leadingAnchor.constraint(equalTo: giphyCarouselContainerView.leadingAnchor).isActive = true @@ -440,6 +439,18 @@ extension AddNoteViewController { searchGiphyButton.addTarget(self, action: #selector(showGiphyPanel), for: .touchUpInside) searchGiphyButton.isHidden = true + var searchGiphyButtonConfiguration = UIButton.Configuration.filled() + searchGiphyButtonConfiguration.image = Theme.shared.images.searchIcon + searchGiphyButtonConfiguration.attributedTitle = AttributedString(localized("add_note.search_giphy_button"), attributes: AttributeContainer([ + .font: UIFont.Avenir.black.withSize(9.0) + ])) + searchGiphyButtonConfiguration.imagePadding = 5.0 + searchGiphyButtonConfiguration.baseForegroundColor = theme.neutral.primary + searchGiphyButtonConfiguration.baseBackgroundColor = theme.text.heading + searchGiphyButtonConfiguration.contentInsets = .zero + + searchGiphyButton.configuration = searchGiphyButtonConfiguration + poweredByGiphyImageView.translatesAutoresizingMaskIntoConstraints = false giphyCarouselContainerView.addSubview(poweredByGiphyImageView) let poweredByWidth: CGFloat = 125 diff --git a/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift b/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift index 87d5790e..86019cac 100644 --- a/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift +++ b/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift @@ -67,7 +67,8 @@ final class AddRecipientModel { @Published private(set) var listSections: [AddRecipientView.Section] = [] @Published private(set) var action: Action? - @Published private(set) var yatID: String? + @Published private(set) var isYatFound: Bool = false + @Published private(set) var isAddressPreviewAvaiable: Bool = false @Published private(set) var walletAddressPreview: String? @Published private(set) var canMoveToNextStep: Bool = false @Published private(set) var errorMessage: String? @@ -79,6 +80,7 @@ final class AddRecipientModel { private let contactsManager = ContactsManager() @Published private var address: TariAddress? + @Published private var yatID: String? @Published private var contactModels: [ContactsManager.Model] = [] private weak var bleTask: BLECentralTask? @@ -121,6 +123,14 @@ final class AddRecipientModel { .map { $0 != nil } .assign(to: \.canMoveToNextStep, on: self) .store(in: &cancellables) + + $yatID + .sink { [weak self] in self?.isYatFound = $0 != nil } + .store(in: &cancellables) + + Publishers.CombineLatest($yatID, $address) + .sink { [weak self] in self?.isAddressPreviewAvaiable = $0 != nil && $1 != nil } + .store(in: &cancellables) } // MARK: - Actions @@ -143,10 +153,13 @@ final class AddRecipientModel { let addresses = try transactions .sorted { try $0.timestamp > $1.timestamp } .map { try $0.address } - .reduce(into: [TariAddress]()) { result, address in - guard !result.contains(address) else { return } - result.append(address) + .reduce(into: (identifiers: [String](), output: [TariAddress]())) { result, address in + let addressComponents = try address.components + guard !result.identifiers.contains(addressComponents.uniqueIdentifier), !addressComponents.isUnknownAddress else { return } + result.identifiers.append(addressComponents.uniqueIdentifier) + result.output.append(address) } + .output .prefix(3) return Array(addresses) @@ -157,7 +170,7 @@ final class AddRecipientModel { let allContacts = contactsManager.tariContactModels do { - return try fetchRecentTariAddresses().compactMap { address in try allContacts.first { try $0.internalModel?.hex == address.byteVector.hex }} + return try fetchRecentTariAddresses().compactMap { address in try allContacts.first { try $0.internalModel?.addressComponents.uniqueIdentifier == address.components.uniqueIdentifier }} } catch { return [] } @@ -174,15 +187,17 @@ final class AddRecipientModel { if let rawAmount = deeplink.amount { amount = MicroTari(rawAmount) } - handleAddressSelection(paymentInfo: PaymentInfo(address: deeplink.receiverAddress, alias: nil, yatID: nil, amount: amount, feePerGram: nil, note: deeplink.note)) + guard let addressComponents = try? TariAddress(base58: deeplink.receiverAddress).components else { return } + handleAddressSelection(paymentInfo: PaymentInfo(addressComponents: addressComponents, alias: nil, yatID: nil, amount: amount, feePerGram: nil, note: deeplink.note)) } else if let deeplink = deeplink as? UserProfileDeeplink { - handleAddressSelection(paymentInfo: PaymentInfo(address: deeplink.tariAddress, alias: deeplink.alias, yatID: nil, amount: nil, feePerGram: nil, note: nil)) + guard let addressComponents = try? TariAddress(base58: deeplink.tariAddress).components else { return } + handleAddressSelection(paymentInfo: PaymentInfo(addressComponents: addressComponents, alias: deeplink.alias, yatID: nil, amount: nil, feePerGram: nil, note: nil)) } } func select(elementID: UUID) { guard let model = contactDictornary[elementID]?.internalModel else { return } - handleAddressSelection(paymentInfo: PaymentInfo(address: model.hex, alias: nil, yatID: yatID, amount: nil, feePerGram: nil, note: nil)) + handleAddressSelection(paymentInfo: PaymentInfo(addressComponents: model.addressComponents, alias: nil, yatID: yatID, amount: nil, feePerGram: nil, note: nil)) } func fetchTransactionDataViaBLE() { @@ -195,7 +210,7 @@ final class AddRecipientModel { Task { do { - guard let data = try await bleTask.findAndRead(), let rawDeeplink = String(data: data, encoding: .utf8), let url = URL(string: rawDeeplink) else { return } + guard let rawDeeplink = try await bleTask.findAndRead()?.string, let url = URL(string: rawDeeplink) else { return } let deeplink = try DeepLinkFormatter.model(type: UserProfileDeeplink.self, deeplink: url) incomingUserProfile = deeplink action = .show(dialog: .bleTransactionConfirmationDialog(receiverName: deeplink.alias)) @@ -217,7 +232,9 @@ final class AddRecipientModel { } self.incomingUserProfile = nil - handleAddressSelection(paymentInfo: PaymentInfo(address: incomingUserProfile.tariAddress, alias: incomingUserProfile.alias, yatID: nil, amount: nil, feePerGram: nil, note: nil)) + + guard let addressComponents = try? TariAddress(base58: incomingUserProfile.tariAddress).components else { return } + handleAddressSelection(paymentInfo: PaymentInfo(addressComponents: addressComponents, alias: incomingUserProfile.alias, yatID: nil, amount: nil, feePerGram: nil, note: nil)) } func cancelIncomingTransaction() { @@ -226,17 +243,17 @@ final class AddRecipientModel { func toogleYatPreview() { let isAddressVisible = walletAddressPreview != nil - walletAddressPreview = isAddressVisible ? nil : try? address?.byteVector.hex + walletAddressPreview = isAddressVisible ? nil : try? address?.components.fullRaw } func requestContinue() { - guard let hex = try? address?.byteVector.hex else { + guard let addressComponents = try? address?.components else { errorMessage = localized("add_recipient.error.invalid_emoji_id") return } - handleAddressSelection(paymentInfo: PaymentInfo(address: hex, alias: nil, yatID: yatID, amount: nil, feePerGram: nil, note: nil)) + handleAddressSelection(paymentInfo: PaymentInfo(addressComponents: addressComponents, alias: nil, yatID: yatID, amount: nil, feePerGram: nil, note: nil)) } // MARK: - Handlers @@ -254,8 +271,8 @@ final class AddRecipientModel { $0.filter { guard $0.name.range(of: searchText, options: .caseInsensitive) == nil else { return true } guard let internalModel = $0.internalModel else { return false } - guard internalModel.emojiID.range(of: searchText, options: .caseInsensitive) == nil else { return true } - return internalModel.hex.range(of: searchText, options: .caseInsensitive) != nil + guard internalModel.addressComponents.fullEmoji.range(of: searchText, options: .caseInsensitive) == nil else { return true } + return internalModel.addressComponents.fullRaw.range(of: searchText, options: .caseInsensitive) != nil } } } @@ -265,7 +282,7 @@ final class AddRecipientModel { self.yatID = nil guard yatID.containsOnlyEmoji, (1...maxYatIDLenght).contains(yatID.count) else { return } - Yat.api.emojiID.lookupEmojiIDPaymentPublisher(emojiId: yatID, tags: YatRecordTag.XTRAddress.rawValue) + Yat.api.emojiID.lookupEmojiIDPaymentPublisher(emojiId: yatID, tags: YatRecordTag.XTMAddress.rawValue) .sink( receiveCompletion: { _ in }, receiveValue: { [weak self] in self?.handle(apiResponse: $0, yatID: yatID) } @@ -274,7 +291,7 @@ final class AddRecipientModel { } private func handle(apiResponse: PaymentAddressResponse, yatID: String) { - guard let walletAddress = apiResponse.result?[YatRecordTag.XTRAddress.rawValue]?.address else { return } + guard let walletAddress = apiResponse.result?[YatRecordTag.XTMAddress.rawValue]?.address else { return } generateAddress(text: walletAddress) self.yatID = yatID } @@ -292,19 +309,7 @@ final class AddRecipientModel { guard !data.element.isEmpty else { return } let items: [AddRecipientView.ItemType] = data.element.map { - - let model = $0.model - let name = (!model.name.isEmpty ? model.name : model.internalModel?.emojiID.obfuscatedText) ?? "" - - let viewModel = ContactBookCell.ViewModel( - id: $0.identifier, - name: name, - avatarText: model.avatar, - avatarImage: model.avatarImage, - isFavorite: false, - contactTypeImage: model.type.image, - isSelectable: false - ) + let viewModel = ContactBookCell.ViewModel(id: $0.identifier, addressViewModel: $0.model.contactBookCellAddressViewModel, isFavorite: false, contactTypeImage: nil, isSelectable: false) return .contact(model: viewModel) } @@ -336,23 +341,29 @@ final class AddRecipientModel { // MARK: - Helpers private func generateAddress(text: String) { - guard let address = try? makeAddress(text: text), verify(address: address) else { + + guard let address = try? makeAddress(text: text) else { address = nil + errorMessage = nil return } - self.address = address + + let isValid = verify(address: address) + self.address = isValid ? address : nil } private func makeAddress(text: String) throws -> TariAddress { - do { return try TariAddress(emojiID: text) } catch {} - return try TariAddress(hex: text) + let trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines) + return try TariAddress.makeTariAddress(input: trimmedText) } private func verify(address: TariAddress) -> Bool { - guard let hex = try? address.byteVector.hex, let userHex = try? Tari.shared.walletAddress.byteVector.hex, hex != userHex else { + + guard let uniqueIdentifier = try? address.components.uniqueIdentifier, let userUniqueIdentifier = try? Tari.shared.walletAddress.components.uniqueIdentifier, uniqueIdentifier != userUniqueIdentifier else { errorMessage = localized("add_recipient.error.can_not_send_yourself", arguments: NetworkManager.shared.selectedNetwork.tickerSymbol) return false } + errorMessage = nil return true } diff --git a/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift b/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift index 0b2877e5..fb11633a 100644 --- a/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift +++ b/MobileWallet/Screens/Send/AddRecipient/AddRecipientView.swift @@ -102,12 +102,18 @@ final class AddRecipientView: DynamicThemeView { didSet { update(sections: viewModels) } } - var isPreviewButtonVisible: Bool = false { - didSet { updateSearchFieldState() } + var isYatLogoVisible: Bool { + get { searchView.isYatLogoVisible } + set { searchView.isYatLogoVisible = newValue } + } + + var isPreviewButtonVisible: Bool { + get { searchView.isPreviewButtonVisible } + set { searchView.isPreviewButtonVisible = newValue } } var isContinueButtonEnabled: Bool = false { - didSet { continueButton.variation = isContinueButtonEnabled ? .normal : .disabled } + didSet { continueButton.isEnabled = isContinueButtonEnabled } } var errorMessage: String? { @@ -219,10 +225,6 @@ final class AddRecipientView: DynamicThemeView { dataSource?.applySnapshotUsingReloadData(snapshot) } - private func updateSearchFieldState() { - searchView.isPreviewButtonVisible = isPreviewButtonVisible - } - private func updateToolbarsAlpha(scrollView: UIScrollView) { let maxAlpha = 0.9 updateSearchViewToolbarAlpha(scrollView: scrollView, maxAlpha: maxAlpha) @@ -249,7 +251,6 @@ final class AddRecipientView: DynamicThemeView { Task { await UIView.animate(duration: 0.3) { self.errorMessageView.isHidden = false - self.topStackView.layoutIfNeeded() } UIView.animate(withDuration: 0.3) { self.errorMessageView.alpha = 1.0 @@ -260,10 +261,8 @@ final class AddRecipientView: DynamicThemeView { await UIView.animate(duration: 0.3) { self.errorMessageView.alpha = 0.0 } - UIView.animate(withDuration: 0.3) { self.errorMessageView.isHidden = true - self.topStackView.layoutIfNeeded() } } } diff --git a/MobileWallet/Screens/Send/AddRecipient/AddRecipientViewController.swift b/MobileWallet/Screens/Send/AddRecipient/AddRecipientViewController.swift index ef2511b0..13ca3be0 100644 --- a/MobileWallet/Screens/Send/AddRecipient/AddRecipientViewController.swift +++ b/MobileWallet/Screens/Send/AddRecipient/AddRecipientViewController.swift @@ -94,9 +94,13 @@ final class AddRecipientViewController: UIViewController { .sink { [weak self] in self?.mainView.isContinueButtonEnabled = $0 } .store(in: &cancellables) - model.$yatID + model.$isYatFound + .receive(on: DispatchQueue.main) + .assign(to: \.isYatLogoVisible, on: mainView) + .store(in: &cancellables) + + model.$isAddressPreviewAvaiable .receive(on: DispatchQueue.main) - .map { $0 != nil } .assign(to: \.isPreviewButtonVisible, on: mainView) .store(in: &cancellables) @@ -106,7 +110,7 @@ final class AddRecipientViewController: UIViewController { .store(in: &cancellables) model.$errorMessage - .compactMap { $0 } + .removeDuplicates() .receive(on: DispatchQueue.main) .sink { [weak self] in self?.mainView.errorMessage = $0 } .store(in: &cancellables) diff --git a/MobileWallet/Screens/Settings/BackUpSettings/SecureBackupViewController.swift b/MobileWallet/Screens/Settings/BackUpSettings/SecureBackupViewController.swift index 517bc0df..6035adc4 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/SecureBackupViewController.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/SecureBackupViewController.swift @@ -92,7 +92,7 @@ final class SecureBackupViewController: SettingsParentViewController { guard let password = enterPasswordField.password else { return } view.endEditing(true) - continueButton.variation = .disabled + continueButton.isEnabled = false BackupManager.shared.password = password pendingView.showPendingView { [weak self] in @@ -266,7 +266,7 @@ extension SecureBackupViewController { private func setupContinueButton() { continueButton.setTitle(localized("secure_backup.secure_your_backup"), for: .normal) continueButton.addTarget(self, action: #selector(continueButtonAction), for: .touchUpInside) - continueButton.variation = .disabled + continueButton.isEnabled = false mainView.addSubview(continueButton) continueButton.translatesAutoresizingMaskIntoConstraints = false @@ -292,10 +292,10 @@ extension SecureBackupViewController: PasswordFieldDelegate { func passwordFieldDidChange(_ passwordField: PasswordField) { guard let password = passwordField.password else { return } if confirmPasswordField.isWarning || enterPasswordField.isWarning { - continueButton.variation = .disabled + continueButton.isEnabled = false return } - continueButton.variation = (confirmPasswordField.password == enterPasswordField.password && !password.isEmpty) ? .normal : .disabled + continueButton.isEnabled = confirmPasswordField.password == enterPasswordField.password && !password.isEmpty } } diff --git a/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListView.swift b/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListView.swift index b6d6346a..c31c368c 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListView.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListView.swift @@ -109,7 +109,7 @@ final class SeedWordsListView: BaseNavigationContentView { } var isContinueButtonEnabled: Bool = false { - didSet { continueButton.variation = isContinueButtonEnabled ? .normal : .disabled } + didSet { continueButton.isEnabled = isContinueButtonEnabled } } var isAnimationEnabled: Bool = false diff --git a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsView.swift b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsView.swift index 813e68aa..e582ce14 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsView.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsView.swift @@ -45,8 +45,7 @@ final class VerifySeedWordsView: BaseNavigationContentView { // MARK: - Subviews - @View private var scrollView = UIScrollView() - @View private var contentView = UIView() + @View private var scrollView = ContentScrollView() @View private var headerLabel: UILabel = { let view = UILabel() @@ -126,57 +125,53 @@ final class VerifySeedWordsView: BaseNavigationContentView { private func setupConstraints() { + @View var spacerView = UIView() + addSubview(scrollView) - scrollView.addSubview(contentView) - [headerLabel, tokensView, tokensViewInfoLabel, successImageView, errorLabel, selectableTokensView, continueButton].forEach(contentView.addSubview) + [headerLabel, tokensView, tokensViewInfoLabel, successImageView, errorLabel, spacerView, selectableTokensView, continueButton].forEach(scrollView.contentView.addSubview) let scrollViewConstants = [ scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), scrollView.bottomAnchor.constraint(equalTo: bottomAnchor), - contentView.topAnchor.constraint(equalTo: scrollView.topAnchor), - contentView.leadingAnchor.constraint(equalTo: leadingAnchor), - contentView.trailingAnchor.constraint(equalTo: trailingAnchor), - contentView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor) + scrollView.contentView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor) ] let constraints = [ - headerLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20.0), - headerLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0), - headerLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0), - tokensView.topAnchor.constraint(equalTo: headerLabel.bottomAnchor, constant: 20.0), - tokensView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0), - tokensView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0), + headerLabel.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor, constant: 16.0), + headerLabel.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor, constant: 20.0), + headerLabel.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor, constant: -20.0), + tokensView.topAnchor.constraint(equalTo: headerLabel.bottomAnchor, constant: 16.0), + tokensView.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor, constant: 20.0), + tokensView.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor, constant: -20.0), tokensView.heightAnchor.constraint(equalToConstant: 272.0), tokensViewInfoLabel.leadingAnchor.constraint(equalTo: tokensView.leadingAnchor, constant: 20.0), tokensViewInfoLabel.trailingAnchor.constraint(equalTo: tokensView.trailingAnchor, constant: -20.0), tokensViewInfoLabel.centerYAnchor.constraint(equalTo: tokensView.centerYAnchor), successImageView.topAnchor.constraint(equalTo: selectableTokensView.topAnchor), - successImageView.centerXAnchor.constraint(equalTo: centerXAnchor), + successImageView.centerXAnchor.constraint(equalTo: scrollView.contentView.centerXAnchor), successImageView.widthAnchor.constraint(equalToConstant: 29.0), successImageView.heightAnchor.constraint(equalToConstant: 29.0), errorLabel.topAnchor.constraint(equalTo: selectableTokensView.topAnchor), - errorLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0), - errorLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0), + errorLabel.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor, constant: 20.0), + errorLabel.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor, constant: -20.0), errorLabel.heightAnchor.constraint(equalToConstant: 37.0), - selectableTokensView.topAnchor.constraint(equalTo: tokensView.bottomAnchor, constant: 25.0), - selectableTokensView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0), - selectableTokensView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0), - selectableTokensView.bottomAnchor.constraint(equalTo: continueButton.topAnchor, constant: -20.0), - continueButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0), - continueButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0), - continueButton.bottomAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.bottomAnchor, constant: -20.0) + selectableTokensView.topAnchor.constraint(equalTo: tokensView.bottomAnchor, constant: 16.0), + selectableTokensView.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor, constant: 20.0), + selectableTokensView.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor, constant: -20.0), + spacerView.topAnchor.constraint(equalTo: selectableTokensView.bottomAnchor), + spacerView.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor), + spacerView.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor), + continueButton.topAnchor.constraint(equalTo: spacerView.bottomAnchor, constant: 16.0), + continueButton.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor, constant: 16.0), + continueButton.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor, constant: -20.0), + continueButton.bottomAnchor.constraint(equalTo: scrollView.contentView.safeAreaLayoutGuide.bottomAnchor, constant: -16.0) ] NSLayoutConstraint.activate(scrollViewConstants + constraints) } - override func layoutSubviews() { - super.layoutSubviews() - scrollView.contentSize.height = contentView.bounds.height - } - // MARK: - Updates override func update(theme: ColorTheme) { diff --git a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift index 9e1ecbeb..2f717f16 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift @@ -92,8 +92,7 @@ final class VerifySeedWordsViewController: SecureViewController AnyCancellable { - Tari.shared.connectionMonitor.$torBootstrapProgress - .map { Float($0) / 100.0 } - .receive(on: DispatchQueue.main) - .sink { [weak self] in self?.navigationBar.progress = $0 } - } - - func onCustomBridgeSuccessAction() { - - navigationBar.progress = 1.0 - - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - self.navigationBar.progress = nil - self.view.isUserInteractionEnabled = true - self.tableView.reloadData() - } - } - - func onCustomBridgeFailureAction(error: Error) { - navigationBar.progress = nil - view.isUserInteractionEnabled = true - let errorMessage = ErrorMessageManager.errorModel(forError: error) - - Task { @MainActor in - PopUpPresenter.show(message: errorMessage) - } - } -} diff --git a/MobileWallet/Screens/Settings/MoreSettings/Views/AboutViewHeader.swift b/MobileWallet/Screens/Settings/MoreSettings/Views/AboutViewHeader.swift index 6fffe2bf..89836c3e 100644 --- a/MobileWallet/Screens/Settings/MoreSettings/Views/AboutViewHeader.swift +++ b/MobileWallet/Screens/Settings/MoreSettings/Views/AboutViewHeader.swift @@ -60,7 +60,8 @@ final class AboutViewHeader: DynamicThemeHeaderFooterView { @View private var button: TextButton = { let view = TextButton() view.setTitle(localized("about.button.creative_commons"), for: .normal) - view.setVariation(.secondary, font: .Avenir.light.withSize(14.0)) + view.style = .secondary + view.font = .Avenir.light.withSize(14.0) return view }() diff --git a/MobileWallet/Screens/Settings/SettingsViewController.swift b/MobileWallet/Screens/Settings/SettingsViewController.swift index b1466234..5918e452 100644 --- a/MobileWallet/Screens/Settings/SettingsViewController.swift +++ b/MobileWallet/Screens/Settings/SettingsViewController.swift @@ -39,7 +39,6 @@ */ import LocalAuthentication -import YatLib import Combine import TariCommon @@ -220,7 +219,7 @@ final class SettingsViewController: SettingsParentTableViewController { } private func onScreenRecordingSettingsAction() { - let controller = ScreenRecordingSettingsConstructor.buildScene() + let controller = ScreenRecordingSettingsConstructor.buildScene(backButtonType: .back) navigationController?.pushViewController(controller, animated: true) } @@ -259,26 +258,6 @@ final class SettingsViewController: SettingsParentTableViewController { navigationController?.pushViewController(controller, animated: true) } - private func onConnectYatAction() { - - let address: String - - do { - address = try Tari.shared.walletAddress.byteVector.hex - } catch { - showNoConnectionError() - return - } - - Yat.integration.showOnboarding(onViewController: self, records: [ - YatRecordInput(tag: .XTRAddress, value: address) - ]) - } - - private func showNoConnectionError() { - PopUpPresenter.show(message: MessageModel(title: localized("common.error"), message: localized("settings.error.connect_yats_no_connection"), type: .error)) - } - private func updateItems(syncStatus: BackupManager.BackupSyncState) { backUpWalletItem.percent = 0.0 @@ -322,8 +301,9 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { let cell = tableView.dequeueReusableCell(type: SettingsProfileCell.self, indexPath: indexPath) do { let name = UserSettingsManager.name - let address = try Tari.shared.walletAddress.emojis.obfuscatedText - cell.update(avatar: address.firstOrEmpty, name: name, address: address) + let addressComponents = try Tari.shared.walletAddress.components + let addressViewModel = AddressView.ViewModel(prefix: addressComponents.networkAndFeatures, text: .truncated(prefix: addressComponents.coreAddressPrefix, suffix: addressComponents.coreAddressSuffix), isDetailsButtonVisible: false) + cell.update(name: name, addressViewModel: addressViewModel) } catch { let message = ErrorMessageManager.errorModel(forError: error) PopUpPresenter.show(message: message) @@ -388,8 +368,6 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { switch indexPath.row { case 0: onProfileAction() - case 1: - onConnectYatAction() default: break } diff --git a/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift b/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift index 41e7f17a..4cbccf3d 100644 --- a/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift +++ b/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift @@ -45,26 +45,17 @@ final class SettingsProfileCell: DynamicThemeCell { // MARK: - Subviews - @View private var avatarView = RoundedAvatarView() - @View private var nameLabel: UILabel = { let view = UILabel() view.font = .Avenir.medium.withSize(16.0) return view }() - @View private var addressLabel: UILabel = { - let view = UILabel() - view.font = .Avenir.medium.withSize(17.0) - return view - }() - - @View private var centralContentView = UIView() + @View private var addressView = AddressView() - @View private var scanImage: UIImageView = { + @View private var arrowView: UIImageView = { let view = UIImageView() - view.image = .Icons.General.QR - view.contentMode = .scaleAspectFit + view.image = .Icons.General.cellArrow return view }() @@ -83,29 +74,16 @@ final class SettingsProfileCell: DynamicThemeCell { private func setupConstraints() { - [avatarView, centralContentView, scanImage].forEach(contentView.addSubview) - [nameLabel, addressLabel].forEach(centralContentView.addSubview) + [nameLabel, addressView, arrowView].forEach(contentView.addSubview) let constraints = [ - avatarView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 30.0), - avatarView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 30.0), - avatarView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -30.0), - avatarView.heightAnchor.constraint(equalToConstant: 65.0), - avatarView.widthAnchor.constraint(equalToConstant: 65.0), - centralContentView.leadingAnchor.constraint(equalTo: avatarView.trailingAnchor, constant: 10.0), - centralContentView.centerYAnchor.constraint(equalTo: avatarView.centerYAnchor), - nameLabel.topAnchor.constraint(equalTo: centralContentView.topAnchor), - nameLabel.leadingAnchor.constraint(equalTo: centralContentView.leadingAnchor), - nameLabel.trailingAnchor.constraint(equalTo: centralContentView.trailingAnchor), - addressLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 5.0), - addressLabel.leadingAnchor.constraint(equalTo: centralContentView.leadingAnchor), - addressLabel.trailingAnchor.constraint(equalTo: centralContentView.trailingAnchor), - addressLabel.bottomAnchor.constraint(equalTo: centralContentView.bottomAnchor), - scanImage.leadingAnchor.constraint(equalTo: centralContentView.trailingAnchor, constant: 10.0), - scanImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -22.0), - scanImage.centerYAnchor.constraint(equalTo: avatarView.centerYAnchor), - scanImage.heightAnchor.constraint(equalToConstant: 30.0), - scanImage.widthAnchor.constraint(equalToConstant: 30.0) + nameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 30.0), + nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), + addressView.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 5.0), + addressView.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor), + addressView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -30.0), + arrowView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), + arrowView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor) ] NSLayoutConstraint.activate(constraints) @@ -116,14 +94,12 @@ final class SettingsProfileCell: DynamicThemeCell { override func update(theme: ColorTheme) { super.update(theme: theme) backgroundColor = theme.backgrounds.primary - scanImage.tintColor = theme.icons.default nameLabel.textColor = theme.text.heading - addressLabel.textColor = theme.text.heading + arrowView.tintColor = theme.text.heading } - func update(avatar: String?, name: String?, address: String?) { - avatarView.avatar = .text(avatar) + func update(name: String?, addressViewModel: AddressView.ViewModel) { nameLabel.text = name - addressLabel.text = address + addressView.update(viewModel: addressViewModel) } } diff --git a/MobileWallet/UIElements/Buttons/ActionButton.swift b/MobileWallet/UIElements/Buttons/ActionButton.swift index 09274fdc..55c145a3 100644 --- a/MobileWallet/UIElements/Buttons/ActionButton.swift +++ b/MobileWallet/UIElements/Buttons/ActionButton.swift @@ -38,190 +38,200 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import UIKit +import TariCommon import Lottie -enum ActionButtonVariation { - case normal - case destructive - case loading - case disabled -} - final class ActionButton: DynamicThemeBaseButton { - static let RADIUS_POINTS: CGFloat = 4.0 - static let HEIGHT: CGFloat = 53.0 - private let gradientLayer = CAGradientLayer() - private let pendingAnimationView = AnimationView() + enum Style { + case normal + case destructive + case loading + } + + // MARK: - Subviews + + @View private var gradientView: GradientView = { + let view = GradientView() + view.orientation = .diagonal + view.isUserInteractionEnabled = false + return view + }() + + @View private var pendingAnimationView: AnimationView = { + let view = AnimationView() + view.animation = .named(.pendingCircleAnimation) + view.backgroundBehavior = .pauseAndRestore + view.loopMode = .loop + return view + }() - var variation: ActionButtonVariation = .normal { - didSet { updateStyle(theme: theme) } + // MARK: - Properties + + var isAnimated: Bool = true + + var style: Style = .normal { + didSet { update(style: style, theme: theme) } } + // MARK: - Initialisers + override init() { super.init() - commonSetup() + setupViews() + setupConstraints() + setupCallbacks() } - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - commonSetup() + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - private func commonSetup() { - bounds = CGRect(x: bounds.maxX, y: bounds.maxY, width: bounds.width, height: ActionButton.HEIGHT) - layer.cornerRadius = ActionButton.RADIUS_POINTS - heightAnchor.constraint(equalToConstant: ActionButton.HEIGHT).isActive = true - titleLabel?.font = Theme.shared.fonts.actionButton + // MARK: - Setups + + private func setupViews() { + + layer.cornerRadius = 4.0 titleLabel?.adjustsFontSizeToFitWidth = true - contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0) clipsToBounds = true - gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0) - gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) + configuration = .filled() + configuration?.contentInsets = NSDirectionalEdgeInsets(top: 0.0, leading: 8.0, bottom: 0.0, trailing: 8.0) + configuration?.titleLineBreakMode = .byTruncatingTail - gradientLayer.locations = [0.0, 1.0] - layer.insertSublayer(gradientLayer, at: 0) - } + configuration?.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { + var attributes = $0 + attributes.font = .Avenir.heavy.withSize(16.0) + return attributes + } - override func setImage(_ image: UIImage?, for state: UIControl.State) { - if let color = titleColor(for: .normal), let newImage = image { - super.setImage(newImage.withTintColor(color), for: state) - } else { - super.setImage(image, for: state) + configurationUpdateHandler = { [weak self] in + self?.update(state: $0.state) } + } + + private func setupConstraints() { + + [gradientView, pendingAnimationView].forEach(addSubview) + + let constraints = [ + gradientView.topAnchor.constraint(equalTo: topAnchor), + gradientView.leadingAnchor.constraint(equalTo: leadingAnchor), + gradientView.trailingAnchor.constraint(equalTo: trailingAnchor), + gradientView.bottomAnchor.constraint(equalTo: bottomAnchor), + pendingAnimationView.widthAnchor.constraint(equalToConstant: 45.0), + pendingAnimationView.heightAnchor.constraint(equalToConstant: 45.0), + pendingAnimationView.centerXAnchor.constraint(equalTo: centerXAnchor), + pendingAnimationView.centerYAnchor.constraint(equalTo: centerYAnchor), + heightAnchor.constraint(equalToConstant: 53.0) + ] - imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 7) - titleEdgeInsets = UIEdgeInsets(top: 0, left: 7, bottom: 0, right: 0) + NSLayoutConstraint.activate(constraints) } - override func layoutSubviews() { - super.layoutSubviews() - gradientLayer.frame = bounds - updateStyle(theme: theme) + private func setupCallbacks() { + addTarget(self, action: #selector(onTapCallback), for: .touchUpInside) } - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - super.touchesBegan(touches, with: event) + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + updateGradent(theme: theme) + updateDisabledState(theme: theme) + } + + private func updateGradent(theme: ColorTheme) { + gradientView.locations = [ + GradientLocationData(color: theme.buttons.primaryStart, location: 0.0), + GradientLocationData(color: theme.buttons.primaryEnd, location: 1.0) + ] + } - UIView.animate(withDuration: 0.1, delay: 0, options: .curveEaseIn, animations: { [weak self] in - guard let self = self else { return } - self.alpha = 0.94 - self.transform = CGAffineTransform(scaleX: 0.96, y: 0.96) - }) + private func updateDisabledState(theme: ColorTheme) { + setTitleColor(theme.buttons.disabledText, for: .disabled) } - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - super.touchesEnded(touches, with: event) + private func update(state: UIButton.State) { + + switch state { + case .normal: + update(style: style, theme: theme) + case .disabled: + gradientView.isHidden = true + configuration?.background.backgroundColor = theme.buttons.disabled + default: + break + } - UIView.animate(withDuration: 0.1, delay: 0, options: .curveEaseIn, animations: { [weak self] in - guard let self = self else { return } - self.alpha = 1 - self.transform = CGAffineTransform(scaleX: 1, y: 1) - }) + guard isAnimated else { return } - isUserInteractionEnabled = false - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in - self?.isUserInteractionEnabled = true + if state == .highlighted { + Task { await animateIn() } + } else { + animateOut() } } - private func removeStyle() { - gradientLayer.removeFromSuperlayer() - pendingAnimationView.removeFromSuperview() - titleLabel?.isHidden = false - } + private func update(style: Style, theme: ColorTheme) { - private func updateStyle(theme: ColorTheme) { - removeStyle() + guard isEnabled else { return } - switch variation { + switch style { case .normal: - isEnabled = true - layer.insertSublayer(gradientLayer, at: 0) - imageView?.tintColor = theme.buttons.primaryText + titleLabel?.isHidden = false + gradientView.isHidden = false + pendingAnimationView.isHidden = true + pendingAnimationView.stop() + configuration?.baseForegroundColor = theme.buttons.primaryText case .destructive: - isEnabled = true - backgroundColor = theme.system.red - imageView?.tintColor = theme.buttons.primaryText + titleLabel?.isHidden = false + gradientView.isHidden = true + pendingAnimationView.isHidden = true + pendingAnimationView.stop() + configuration?.baseForegroundColor = theme.buttons.primaryText + configuration?.background.backgroundColor = theme.system.red case .loading: - isEnabled = false - layer.insertSublayer(gradientLayer, at: 0) titleLabel?.isHidden = true - imageView?.tintColor = .clear - setupPendingAnimation() - case .disabled: - isEnabled = false - backgroundColor = theme.buttons.disabled - imageView?.tintColor = theme.buttons.disabledText + gradientView.isHidden = false + pendingAnimationView.isHidden = false + pendingAnimationView.play() } - - gradientLayer.colors = [theme.buttons.primaryStart, theme.buttons.primaryEnd].compactMap { $0?.cgColor } } - func animateIn() { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: { [weak self] in - guard let self = self else { return } - self.isHidden = false - UIView.animate( - withDuration: 0.2, - delay: 0, - options: .curveEaseOut, - animations: { [weak self] in - guard let self = self else { return } - self.transform = CGAffineTransform(scaleX: 1, y: 1) - } - ) - }) - } + // MARK: - Callbacks - func animateOut() { - // Wait till after pulse affect - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: { - UIView.animate( - withDuration: 0.2, - delay: 0, - options: .curveEaseOut, - animations: { [weak self] in - guard let self = self else { return } - self.transform = CGAffineTransform(scaleX: 0.01, y: 0.01) - }, completion: { [weak self] (_) in - guard let self = self else { return } - self.isHidden = true - }) - }) - } + @objc private func onTapCallback() { - func hideButtonWithAlpha(comletion: (() -> Void)? = nil) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: { - UIView.animate(withDuration: 0.5, animations: { [weak self] in - self?.alpha = 0.0 - self?.layoutIfNeeded() - }) { _ in - comletion?() - } - }) - } + guard isAnimated else { return } - private func setupPendingAnimation() { - pendingAnimationView.backgroundBehavior = .pauseAndRestore - pendingAnimationView.animation = Animation.named(.pendingCircleAnimation) + Task { + await animateIn() + animateOut() + } + } - addSubview(pendingAnimationView) - pendingAnimationView.translatesAutoresizingMaskIntoConstraints = false - pendingAnimationView.widthAnchor.constraint(equalToConstant: 45).isActive = true - pendingAnimationView.heightAnchor.constraint(equalToConstant: 45).isActive = true - pendingAnimationView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true - pendingAnimationView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + // MARK: - Animations - pendingAnimationView.play(fromProgress: 0, toProgress: 1, loopMode: .loop) + private func animateIn() async { + await withCheckedContinuation { continuation in + UIView.animate( + withDuration: 0.1, + delay: 0.0, + options: [.curveEaseInOut, .beginFromCurrentState], + animations: { + self.alpha = 0.9 + self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95) + }, + completion: { _ in continuation.resume() } + ) + } } - override func update(theme: ColorTheme) { - super.update(theme: theme) - setTitleColor(theme.buttons.primaryText, for: .normal) - setTitleColor(theme.buttons.disabledText, for: .disabled) - updateStyle(theme: theme) + private func animateOut() { + UIView.animate(withDuration: 0.1, delay: 0.0, options: [.curveEaseInOut, .beginFromCurrentState]) { + self.alpha = 1.0 + self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) + } } } diff --git a/MobileWallet/UIElements/Buttons/LoadingGIFButton.swift b/MobileWallet/UIElements/Buttons/LoadingGIFButton.swift index 7213fa49..2ea1892b 100644 --- a/MobileWallet/UIElements/Buttons/LoadingGIFButton.swift +++ b/MobileWallet/UIElements/Buttons/LoadingGIFButton.swift @@ -71,7 +71,7 @@ class LoadingGIFButton: DynamicThemeBaseButton { let retryTitle = localized("loading_gif_button.title.retry") setTitle(loadingTitile, for: .disabled) setTitle(retryTitle, for: .normal) - bounds = CGRect(x: bounds.maxX, y: bounds.maxY, width: bounds.width, height: ActionButton.HEIGHT) + bounds = CGRect(x: bounds.maxX, y: bounds.maxY, width: bounds.width, height: 53.0) heightAnchor.constraint(equalToConstant: LoadingGIFButton.HEIGHT).isActive = true backgroundColor = .clear contentHorizontalAlignment = .left diff --git a/MobileWallet/UIElements/Buttons/RoundedLabeledButton.swift b/MobileWallet/UIElements/Buttons/RoundedLabeledButton.swift index 8aa18abf..2fb51aae 100644 --- a/MobileWallet/UIElements/Buttons/RoundedLabeledButton.swift +++ b/MobileWallet/UIElements/Buttons/RoundedLabeledButton.swift @@ -52,6 +52,12 @@ final class RoundedLabeledButton: DynamicThemeView { return view }() + @View private var iconView: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFit + return view + }() + @View private var label: UILabel = { let view = UILabel() view.font = .Avenir.medium.withSize(14.0) @@ -68,7 +74,7 @@ final class RoundedLabeledButton: DynamicThemeView { } var padding: CGFloat = 0.0 { - didSet { roundedView.imageEdgeInsets = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding) } + didSet { updateIconSize() } } var isSelected: Bool = false { @@ -76,6 +82,7 @@ final class RoundedLabeledButton: DynamicThemeView { } private var roundedViewHeightConstraints: NSLayoutConstraint? + private var iconConstraints: [NSLayoutConstraint] = [] // MARK: - Initliasers @@ -93,11 +100,16 @@ final class RoundedLabeledButton: DynamicThemeView { private func setupConstraints() { - [roundedView, label].forEach(addSubview) + [roundedView, iconView, label].forEach(addSubview) let roundedViewHeightConstraints = roundedView.heightAnchor.constraint(equalToConstant: 0.0) self.roundedViewHeightConstraints = roundedViewHeightConstraints + iconConstraints = [ + iconView.widthAnchor.constraint(equalTo: roundedView.widthAnchor), + iconView.heightAnchor.constraint(equalTo: roundedView.heightAnchor) + ] + let constraints = [ roundedView.topAnchor.constraint(equalTo: topAnchor), roundedView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor), @@ -105,13 +117,15 @@ final class RoundedLabeledButton: DynamicThemeView { roundedView.centerXAnchor.constraint(equalTo: centerXAnchor), roundedView.heightAnchor.constraint(equalTo: roundedView.widthAnchor), roundedViewHeightConstraints, + iconView.centerXAnchor.constraint(equalTo: roundedView.centerXAnchor), + iconView.centerYAnchor.constraint(equalTo: roundedView.centerYAnchor), label.topAnchor.constraint(equalTo: roundedView.bottomAnchor, constant: 5.0), label.leadingAnchor.constraint(equalTo: leadingAnchor), label.trailingAnchor.constraint(equalTo: trailingAnchor), label.bottomAnchor.constraint(equalTo: bottomAnchor) ] - NSLayoutConstraint.activate(constraints) + NSLayoutConstraint.activate(constraints + iconConstraints) } private func setupCallbacks() { @@ -128,14 +142,18 @@ final class RoundedLabeledButton: DynamicThemeView { updateColors(theme: theme) } + func update(image: UIImage?, text: String?) { + iconView.image = image + label.text = text + } + private func updateColors(theme: ColorTheme) { roundedView.backgroundColor = isSelected ? theme.brand.purple : theme.backgrounds.primary - roundedView.tintColor = isSelected ? theme.backgrounds.primary : theme.icons.default + iconView.tintColor = isSelected ? theme.backgrounds.primary : theme.icons.default label.textColor = isSelected ? theme.text.heading : theme.text.body } - func update(image: UIImage?, text: String?) { - roundedView.setImage(image, for: .normal) - label.text = text + private func updateIconSize() { + iconConstraints.forEach { $0.constant = -2.0 * padding } } } diff --git a/MobileWallet/UIElements/Buttons/SlideView.swift b/MobileWallet/UIElements/Buttons/SlideView.swift index dba85d22..2108a0a9 100644 --- a/MobileWallet/UIElements/Buttons/SlideView.swift +++ b/MobileWallet/UIElements/Buttons/SlideView.swift @@ -72,10 +72,11 @@ final class SlideView: DynamicThemeView { return label }() let thumbnailImageView: UIImageView = { - let view = SlideViewRoundImageView() + let view = UIImageView() view.isUserInteractionEnabled = true view.contentMode = .center view.image = Theme.shared.images.forwardArrow + view.layer.cornerRadius = 4.0 return view }() let sliderHolderView: UIView = { @@ -375,10 +376,3 @@ final class SlideView: DynamicThemeView { updateSliderState(theme: theme) } } - -class SlideViewRoundImageView: UIImageView { - override func layoutSubviews() { - super.layoutSubviews() - self.layer.cornerRadius = ActionButton.RADIUS_POINTS - } -} diff --git a/MobileWallet/UIElements/Buttons/TextButton.swift b/MobileWallet/UIElements/Buttons/TextButton.swift index 21d593b5..05defba4 100644 --- a/MobileWallet/UIElements/Buttons/TextButton.swift +++ b/MobileWallet/UIElements/Buttons/TextButton.swift @@ -40,99 +40,131 @@ import UIKit -enum TextButtonVariation { - case primary - case secondary - case warning -} - final class TextButton: DynamicThemeBaseButton { - private static let imageHorizontalSpaceing: CGFloat = 2.0 - var spacing: CGFloat = imageHorizontalSpaceing + enum Style { + case primary + case secondary + case warning + } - private var variation: TextButtonVariation = .primary { + // MARK: - Properties + + var font: UIFont = .Avenir.medium.withSize(14.0) { + didSet { setNeedsUpdateConfiguration() } + } + + var style: Style = .primary { didSet { updateTextColor(theme: theme) } } + var image: UIImage? { + didSet { configuration?.image = image } + } + + var imageSpacing: CGFloat = 4.0 { + didSet { updateImageSpacing() } + } + + // MARK: - Initialisers + override init() { super.init() - commonSetup() + setupConfiguration() + updateImageSpacing() + updateTextColor(theme: theme) + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - override func layoutSubviews() { - super.layoutSubviews() + // MARK: - Setups - if let image = image(for: .normal) { - setRightImage(image) + private func setupConfiguration() { + + configuration = .plain() + configuration?.imagePlacement = .trailing + + configuration?.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { [weak self] in + var attributes = $0 + attributes.font = self?.font + return attributes } - } - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - commonSetup() + configurationUpdateHandler = { [weak self] in + self?.update(state: $0.state) + } } - private func commonSetup() { - if let label = titleLabel { - label.heightAnchor.constraint(equalToConstant: label.font.pointSize * 1.2).isActive = true - } - setVariation(variation) + private func setupCallbacks() { + addTarget(self, action: #selector(onTapCallback), for: .touchUpInside) } - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - super.touchesBegan(touches, with: event) + // MARK: - Updates - UIView.animate(withDuration: 0.1, delay: 0, options: .curveEaseIn, animations: { - self.alpha = 0.6 - if let imageView = self.imageView { - imageView.alpha = 0.6 - } - }) + override func update(theme: ColorTheme) { + super.update(theme: theme) + updateTextColor(theme: theme) } - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - super.touchesEnded(touches, with: event) - - UIView.animate(withDuration: 0.1, delay: 0, options: .curveEaseIn, animations: { - self.alpha = 1 - if let imageView = self.imageView { - imageView.alpha = 1 - } - }) + private func updateTextColor(theme: ColorTheme) { + switch style { + case .primary: + configuration?.baseForegroundColor = theme.text.body + case .secondary: + configuration?.baseForegroundColor = theme.text.links + case .warning: + configuration?.baseForegroundColor = theme.system.red + } } - func setVariation(_ variation: TextButtonVariation, font: UIFont? = Theme.shared.fonts.textButton) { - self.variation = variation - titleLabel?.font = font + private func updateImageSpacing() { + configuration?.imagePadding = imageSpacing } - func setRightImage(_ image: UIImage?) { + private func update(state: UIButton.State) { - guard let image = image else { return } + guard self.state != state else { return } - if let color = titleColor(for: .normal) { - setImage(image.withTintColor(color, renderingMode: .alwaysOriginal), for: .normal) + if state == .highlighted { + Task { await animateIn() } } else { - setImage(image, for: .normal) + animateOut() } + } - imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: spacing) - titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0) + // MARK: - Callbacks - transform = CGAffineTransform(scaleX: -1.0, y: 1.0) - titleLabel?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0) - imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0) + @objc private func onTapCallback() { + Task { + await animateIn() + animateOut() + } } - private func updateTextColor(theme: ColorTheme) { - switch variation { - case .primary: - setTitleColor(theme.text.body, for: .normal) - case .secondary: - setTitleColor(theme.text.links, for: .normal) - case .warning: - setTitleColor(theme.system.red, for: .normal) + // MARK: - Animations + + private func animateIn() async { + await withCheckedContinuation { continuation in + UIView.animate( + withDuration: 0.1, + delay: 0.0, + options: [.curveEaseInOut, .beginFromCurrentState], + animations: { + self.alpha = 0.9 + self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95) + }, + completion: { _ in continuation.resume() } + ) + } + } + + private func animateOut() { + UIView.animate(withDuration: 0.1, delay: 0.0, options: [.curveEaseInOut, .beginFromCurrentState]) { + self.alpha = 1.0 + self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) } } } diff --git a/MobileWallet/UIElements/EmojiIdView.swift b/MobileWallet/UIElements/EmojiIdView.swift deleted file mode 100644 index 02ebd23e..00000000 --- a/MobileWallet/UIElements/EmojiIdView.swift +++ /dev/null @@ -1,749 +0,0 @@ -/* - Package MobileWallet - Created by Gabriel Lupu on 20/02/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 TariCommon - -final class EmojiIdView: DynamicThemeView { - - struct ViewModel { - let emojiID: String - let hex: String? - } - - weak var blackoutParent: UIView? - private lazy var blackoutView: UIView = { - let view = UIView() - view.backgroundColor = .Static.popupOverlay - guard let bounds = UIApplication.shared.firstWindow?.bounds else { return view } - view.frame = bounds - view.alpha = 0.0 - return view - }() - - var expanded: Bool = false - var blackoutWhileExpanded = true - var copyText: String = localized("emoji.copy") - var tooltipText: String? = localized("profile_view.error.qr_code.description.with_param", arguments: NetworkManager.shared.selectedNetwork.tickerSymbol) - - var tapToExpand: ((_ expanded: Bool) -> Void)? - private var initialWidth: CGFloat = CGFloat(172) - - private let condensedEmojiIdContainer = UIView() - private lazy var condensedEmojiIdLabel = UILabel() - - private var expandedEmojiIdScrollView = UIScrollView() - private lazy var expandedEmojiIdLabel = UILabel() - - private var hexPubKeyTipView: UIView? - private var hexPubKeyTipLabel: UILabel? - private var hexPubKeyTipViewBottomConstraint: NSLayoutConstraint? - private var hexPubKeyTipViewHiddenBottomConstraint = CGFloat(250) - - private var labelInitialWidth: NSLayoutConstraint? - private var labelWidthConstraint: NSLayoutConstraint? - private var labelCenterConstraint: NSLayoutConstraint? - - private let emojiMenu = EmojiMenuView() - - private var emojiText: String = "" - private var pubKeyHex: String? - - private var tapActionIsDisabled = false - - private weak var copiedLabel: UILabel? - private weak var containerView: UIView? - private weak var greenView: UIView? - - var cornerRadius: CGFloat = 6.0 { - didSet { - self.layer.cornerRadius = cornerRadius - condensedEmojiIdLabel.layer.cornerRadius = cornerRadius - } - } - - private weak var superVC: UIViewController? - @View private var secureContentView = SecureWrapperView() - - // MARK: - Initialisers - - override init() { - super.init() - setupSecureContentView() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setups - - private func setupSecureContentView() { - - guard let window = UIApplication.shared.firstWindow else { return } - window.addSubview(secureContentView) - - let constraints = [ - secureContentView.topAnchor.constraint(equalTo: window.topAnchor), - secureContentView.leadingAnchor.constraint(equalTo: window.leadingAnchor), - secureContentView.trailingAnchor.constraint(equalTo: window.trailingAnchor), - secureContentView.bottomAnchor.constraint(equalTo: window.bottomAnchor) - ] - - secureContentView.isHidden = true - NSLayoutConstraint.activate(constraints) - } - - // MARK: - Updates - - override func update(theme: ColorTheme) { - super.update(theme: theme) - - condensedEmojiIdContainer.apply(shadow: theme.shadows.box) - condensedEmojiIdLabel.backgroundColor = theme.backgrounds.primary - condensedEmojiIdLabel.textColor = theme.text.lightText - - expandedEmojiIdScrollView.backgroundColor = theme.backgrounds.primary - expandedEmojiIdLabel.backgroundColor = theme.backgrounds.primary - expandedEmojiIdLabel.textColor = theme.neutral.inactive - - updateTipView(theme: theme) - updateCopyView(theme: theme) - } - - private func updateTipView(theme: ColorTheme) { - hexPubKeyTipView?.backgroundColor = theme.backgrounds.primary - hexPubKeyTipLabel?.textColor = theme.text.body - } - - private func updateCopyView(theme: ColorTheme) { - copiedLabel?.textColor = theme.text.links - containerView?.layer.borderColor = theme.system.green?.cgColor - containerView?.backgroundColor = .Static.white.withAlphaComponent(0.75) - greenView?.backgroundColor = theme.system.green?.withAlphaComponent(0.12) - } - - func update(viewModel: ViewModel, textCentered: Bool = true) { - emojiText = viewModel.emojiID - pubKeyHex = viewModel.hex - setupView(textCentered: textCentered) - } - - private func setupView(textCentered: Bool = true, inViewController vc: UIViewController? = nil, - initialWidth: CGFloat = 185.0, initialHeight: CGFloat = 40.0, showContainerViewBlur: Bool = true, cornerRadius: CGFloat = 6.0) { - self.backgroundColor = .clear - self.cornerRadius = cornerRadius - blackoutView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(_:)))) - superVC = vc - superVC?.navigationController?.navigationBar.layer.zPosition = 0 - blackoutWhileExpanded = showContainerViewBlur - // tap gesture - self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:)))) - prepareCondensedEmojiId(textCentered: textCentered, width: initialWidth, height: initialHeight) - prepareExpandedEmojiId(height: initialHeight) - } - - func setup(emojiID: String, hex: String, textCentered: Bool, inViewController vc: UIViewController? = nil, - initialWidth: CGFloat = 185.0, initialHeight: CGFloat = 40.0, showContainerViewBlur: Bool = true, cornerRadius: CGFloat = 6.0) { - backgroundColor = .clear - self.cornerRadius = cornerRadius - blackoutView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(_:)))) - superVC = vc - superVC?.navigationController?.navigationBar.layer.zPosition = 0 - emojiText = emojiID - pubKeyHex = hex - blackoutWhileExpanded = showContainerViewBlur - // tap gesture - self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:)))) - prepareCondensedEmojiId(textCentered: textCentered, - width: initialWidth, - height: initialHeight) - prepareExpandedEmojiId(height: initialHeight) - } - - private func prepareCondensedEmojiId(textCentered: Bool, - width: CGFloat, - height: CGFloat) { - self.addSubview(condensedEmojiIdContainer) - condensedEmojiIdContainer.translatesAutoresizingMaskIntoConstraints = false - condensedEmojiIdContainer.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - if textCentered { - condensedEmojiIdContainer.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true - } else { - condensedEmojiIdContainer.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - } - self.initialWidth = width - labelInitialWidth = condensedEmojiIdContainer.widthAnchor.constraint(equalToConstant: initialWidth) - labelInitialWidth?.isActive = true - condensedEmojiIdContainer.heightAnchor.constraint(equalToConstant: height).isActive = true - condensedEmojiIdContainer.clipsToBounds = true - condensedEmojiIdContainer.layer.masksToBounds = false - condensedEmojiIdContainer.addSubview(condensedEmojiIdLabel) - - condensedEmojiIdLabel.layer.masksToBounds = true - condensedEmojiIdLabel.textAlignment = .center - condensedEmojiIdLabel.text = getCondensedEmojiId() + " " - condensedEmojiIdLabel.letterSpacing(value: 1.6) - condensedEmojiIdLabel.translatesAutoresizingMaskIntoConstraints = false - - condensedEmojiIdLabel.leadingAnchor.constraint(equalTo: condensedEmojiIdContainer.leadingAnchor).isActive = true - condensedEmojiIdLabel.trailingAnchor.constraint(equalTo: condensedEmojiIdContainer.trailingAnchor).isActive = true - condensedEmojiIdLabel.topAnchor.constraint(equalTo: condensedEmojiIdContainer.topAnchor).isActive = true - condensedEmojiIdLabel.bottomAnchor.constraint(equalTo: condensedEmojiIdContainer.bottomAnchor).isActive = true - condensedEmojiIdLabel.heightAnchor.constraint(equalToConstant: height).isActive = true - - labelCenterConstraint = condensedEmojiIdLabel.centerXAnchor.constraint(equalTo: condensedEmojiIdContainer.centerXAnchor) - labelCenterConstraint?.isActive = true - } - - private func prepareExpandedEmojiId(height: CGFloat) { - // prepare expanded emoji id scroll view - expandedEmojiIdScrollView.layer.cornerRadius = cornerRadius * 1.2 - expandedEmojiIdScrollView.clipsToBounds = true - expandedEmojiIdScrollView.layer.masksToBounds = true - expandedEmojiIdScrollView.showsHorizontalScrollIndicator = false - expandedEmojiIdScrollView.bounces = true - - // prepare expanded emoji id label - condensedEmojiIdLabel.font = UIFont.systemFont(ofSize: 14.0) - condensedEmojiIdLabel.adjustsFontSizeToFitWidth = false - // condensedEmojiIdLabel.copyText = emojiText - expandedEmojiIdLabel.numberOfLines = 1 - expandedEmojiIdLabel.adjustsFontSizeToFitWidth = false - expandedEmojiIdLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:)))) - expandedEmojiIdLabel.isUserInteractionEnabled = true - expandedEmojiIdLabel.layer.masksToBounds = true - expandedEmojiIdLabel.textAlignment = .center - expandedEmojiIdLabel.text = getExpandedEmojiId() + " " - expandedEmojiIdLabel.font = UIFont.systemFont(ofSize: 14.0) - expandedEmojiIdLabel.letterSpacing(value: 1.6) - - expandedEmojiIdScrollView.addSubview(expandedEmojiIdLabel) - } - - @objc func tap(_ gestureRecognizer: UITapGestureRecognizer) { - if tapActionIsDisabled { return } - if expanded { - shrink(callTapCompletion: true) - } else { - expand(callTapCompletion: true) - } - } - - private func update(scrollViewFrame: CGRect) { - - expandedEmojiIdScrollView.frame = scrollViewFrame - expandedEmojiIdLabel.sizeToFit() - - let padding: CGFloat = 14.0 - let height = 40.0 - let contentWidth = max(expandedEmojiIdLabel.bounds.width, expandedEmojiIdScrollView.bounds.width - 2.0 * padding) - - expandedEmojiIdLabel.frame = CGRect(x: padding, y: 0.0, width: contentWidth, height: height) - expandedEmojiIdScrollView.contentSize = CGSize(width: expandedEmojiIdLabel.frame.width + 2.0 * padding, height: height) - } - - func expand(completion: (() -> Void)? = nil, callTapCompletion: Bool = false, animated: Bool = true) { - guard let scrollViewFrame = condensedEmojiIdContainer.globalFrame else { return } - secureContentView.isHidden = false - secureContentView.isUserInteractionEnabled = blackoutWhileExpanded - tapActionIsDisabled = true - expanded = true - // If they're typing somewhere, close the keyboard - superVC?.view.endEditing(true) - // fade out label container - // fade in blackout - fadeView(view: condensedEmojiIdContainer, fadeOut: true) - if blackoutWhileExpanded { - addViewToSecureContentView(view: blackoutView) - fadeView(view: blackoutView, fadeOut: false, maxAlpha: 0.65) - showCopyEmojiIdButton() - showHexPubKeyCopyTip() - } - // add and show scroll view - let padding = UIDevice.current.userInterfaceIdiom == .pad ? 60 : Theme.shared.sizes.appSidePadding - let scrollViewTargetWidth = blackoutView.frame.width - padding * 2 - update(scrollViewFrame: scrollViewFrame) - // animate scroll view frame - let scrollViewTargetFrame = CGRect( - x: padding, - y: scrollViewFrame.origin.y, - width: scrollViewTargetWidth, - height: scrollViewFrame.height - ) - addViewToSecureContentView(view: expandedEmojiIdScrollView) - if animated { - expandedEmojiIdScrollView.alpha = 0 - expandedEmojiIdScrollView.setContentOffset( - CGPoint(x: expandedEmojiIdLabel.frame.width / 2, y: 0), - animated: false - ) - - fadeView(view: expandedEmojiIdScrollView, fadeOut: false) - UIView.animate(withDuration: animated ? CATransaction.animationDuration() : 0.0) { [weak self] in - self?.update(scrollViewFrame: scrollViewTargetFrame) - } - UIView.animate(withDuration: 0.5, animations: { [weak self] in - self?.expandedEmojiIdScrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false) - }) { [weak self] (_) in - self?.tapActionIsDisabled = false - } - } else { - expandedEmojiIdScrollView.alpha = 1 - update(scrollViewFrame: scrollViewTargetFrame) - tapActionIsDisabled = false - } - if callTapCompletion == true { - tapToExpand?(true) - } - } - - override var alpha: CGFloat { - get { - return super.alpha - } - set { - super.alpha = newValue - expandedEmojiIdScrollView.alpha = newValue - } - } - - func shrink(completion: (() -> Void)? = nil, callTapCompletion: Bool = false, animated: Bool = true) { - guard let scrollViewFrame = condensedEmojiIdContainer.globalFrame else { return } - tapActionIsDisabled = true - expanded = false - let scrolled = expandedEmojiIdScrollView.contentOffset.x > 0 - expandedEmojiIdScrollView.setContentOffset( - CGPoint(x: 0, y: 0), - animated: animated - ) - // hide copy emoji id button & public key hex tip - if blackoutWhileExpanded { - DispatchQueue.main.asyncAfter(deadline: .now() + ((scrolled && animated) ? 0.30 : 0)) { [weak self] in - self?.hideExpandedViews( - animated: true, - scrollViewFrame: scrollViewFrame, - callTapCompletion: callTapCompletion - ) - self?.hideCopyEmojiIdButton { - completion?() - } - self?.hideHexPubKeyCopyTip() - } - } else { - DispatchQueue.main.asyncAfter(deadline: .now() + ((scrolled && animated) ? 0.30 : 0)) { [weak self] in - self?.hideExpandedViews( - animated: animated, - scrollViewFrame: scrollViewFrame, - callTapCompletion: callTapCompletion - ) - completion?() - } - } - } - - private func hideExpandedViews(animated: Bool, - scrollViewFrame: CGRect, - callTapCompletion: Bool) { - if !animated { - if blackoutWhileExpanded { - blackoutView.removeFromSuperview() - secureContentView.isHidden = true - } - expandedEmojiIdScrollView.removeFromSuperview() - condensedEmojiIdContainer.alpha = 1 - if callTapCompletion == true { - tapToExpand?(false) - } - return - } - if self.blackoutWhileExpanded { - self.fadeView(view: self.blackoutView, fadeOut: true) { [weak self] in - self?.blackoutView.removeFromSuperview() - self?.secureContentView.isHidden = true - } - } - // fade out scroll view - self.fadeView(view: self.expandedEmojiIdScrollView, fadeOut: true) { [weak self] in - self?.expandedEmojiIdScrollView.removeFromSuperview() - } - // fade in condensed emoji id - self.fadeView(view: self.condensedEmojiIdContainer, fadeOut: false) { [weak self] in - self?.tapActionIsDisabled = false - self?.secureContentView.isHidden = true - if callTapCompletion == true { - self?.tapToExpand?(false) - } - } - } - - private func getCondensedEmojiId() -> String { - - guard emojiText.count > 6 else { return emojiText } - - let firstThreeChar = emojiText.prefix(3) - let lastThreeChar = emojiText.suffix(3) - return "\(firstThreeChar)•••\(lastThreeChar)" - } - - private func getExpandedEmojiId() -> String { - return emojiText.insertSeparator(" | ", atEvery: 3) - } - - override func willMove(toWindow newWindow: UIWindow?) { - super.willMove(toWindow: newWindow) - if newWindow == nil { - superVC?.navigationController?.navigationBar.layer.zPosition = 0 - hideCopyEmojiIdButton() - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if clipsToBounds || isHidden || alpha == 0 { - return nil - } - - for subview in subviews.reversed() { - let subPoint = subview.convert(point, from: self) - if let result = subview.hitTest(subPoint, with: event) { - return result - } - } - return super.hitTest(point, with: event) - } - - deinit { - UIApplication.shared.menuTabBarController?.tabBar.alpha = 1 - UIApplication.shared.menuTabBarController?.tabBar.isUserInteractionEnabled = true - secureContentView.removeFromSuperview() - } -} - -// MARK: blackout behavior - -extension EmojiIdView { - - private func showCopyEmojiIdButton(completion: (() -> Void)? = nil) { - emojiMenu.alpha = 0.0 - emojiMenu.title = copyText - - emojiMenu.completion = { [weak self] isLongPress in - guard let self = self else { return } - self.tapActionIsDisabled = true - if isLongPress { - self.copyToClipboard(string: self.pubKeyHex ?? self.emojiText) - } else { - self.copyToClipboard(string: self.emojiText) - } - self.hideCopyEmojiIdButton() - self.showCopiedView { [weak self] in - self?.shrink(callTapCompletion: true) - } - } - - addViewToSecureContentView(view: emojiMenu) - guard let globalFrame = condensedEmojiIdContainer.globalFrame else { return } - let emojiMenuSize = CGSize(width: 119, height: 37) - emojiMenu.alpha = 0.0 - emojiMenu.frame.origin.y = globalFrame.maxY - emojiMenu.frame.size = emojiMenuSize - emojiMenu.center.x = center.x - emojiMenu.button.alpha = 1 - - UIView.animate(withDuration: 0.5, - delay: 0.3, - usingSpringWithDamping: 0.5, - initialSpringVelocity: 5, - options: .curveEaseInOut, - animations: { [weak self] in - guard let self = self else { return } - self.emojiMenu.alpha = 1.0 - self.emojiMenu.center.y += 4.0 - }) { (_) in - completion?() - } - - fadeView(view: emojiMenu, fadeOut: false) - } - - private func showHexPubKeyCopyTip() { - hexPubKeyTipView = UIView() - hexPubKeyTipLabel = UILabel() - guard let tipView = hexPubKeyTipView, - let tipLabel = hexPubKeyTipLabel else { - return - } - - let parentView = secureContentView.view - - addViewToSecureContentView(view: tipView) - parentView.bringSubviewToFront(tipView) - - tipView.layer.cornerRadius = 4 - - tipView.translatesAutoresizingMaskIntoConstraints = false - tipView.widthAnchor.constraint( - equalTo: parentView.widthAnchor, - constant: -Theme.shared.sizes.appSidePadding * 2 - ).isActive = true - tipView.centerXAnchor.constraint( - equalTo: parentView.centerXAnchor - ).isActive = true - hexPubKeyTipViewBottomConstraint = tipView.bottomAnchor.constraint( - equalTo: parentView.safeBottomAnchor, - constant: hexPubKeyTipViewHiddenBottomConstraint - ) - hexPubKeyTipViewBottomConstraint?.isActive = true - - tipView.isHidden = tooltipText == nil - tipLabel.text = tooltipText - tipLabel.font = Theme.shared.fonts.profileMiddleLabel - tipLabel.translatesAutoresizingMaskIntoConstraints = false - tipView.addSubview(tipLabel) - tipLabel.interlineSpacing(spacingValue: 2) - tipLabel.leadingAnchor.constraint( - equalTo: tipView.leadingAnchor, - constant: 12 - ).isActive = true - tipLabel.trailingAnchor.constraint( - equalTo: tipView.trailingAnchor, - constant: -12 - ).isActive = true - tipLabel.topAnchor.constraint( - equalTo: tipView.topAnchor, - constant: 12 - ).isActive = true - tipView.bottomAnchor.constraint( - equalTo: tipLabel.bottomAnchor, - constant: 12 - ).isActive = true - tipLabel.numberOfLines = 0 - tipLabel.lineBreakMode = .byWordWrapping - tipLabel.sizeToFit() - parentView.layoutIfNeeded() - - hexPubKeyTipViewBottomConstraint?.constant = -Theme.shared.sizes.appSidePadding / 2 - UIView.animate( - withDuration: 0.5, - delay: 0.3, - options: .curveEaseInOut) { - parentView.layoutIfNeeded() - } - - update(theme: theme) - } - - private func hideCopyEmojiIdButton(completion: (() -> Void)? = nil) { - fadeView(view: emojiMenu, fadeOut: true) { [weak self] in - self?.emojiMenu.removeFromSuperview() - completion?() - } - } - - private func hideHexPubKeyCopyTip() { - hexPubKeyTipViewBottomConstraint?.constant = hexPubKeyTipViewHiddenBottomConstraint - UIView.animate( - withDuration: 0.5, - delay: 0, - options: .curveEaseInOut) { - UIApplication.shared.firstWindow?.layoutIfNeeded() - } completion: { [weak self] (_) in - self?.hexPubKeyTipView?.removeFromSuperview() - self?.hexPubKeyTipViewBottomConstraint = nil - self?.hexPubKeyTipLabel = nil - self?.hexPubKeyTipView = nil - } - } - - private func fadeView(view: UIView, fadeOut: Bool, maxAlpha: CGFloat = 1.0, completion: (() -> Void)? = nil) { - UIView.animate(withDuration: 0.35, animations: { - view.alpha = fadeOut ? 0.0 : maxAlpha - }) { _ in - completion?() - } - } - - private func addViewToSecureContentView(view: UIView) { - secureContentView.view.addSubview(view) - UIApplication.shared.firstWindow?.bringSubviewToFront(secureContentView) - } -} - -private class EmojiMenuView: UIView { - - let button = TextButton() - var completion: ((Bool) -> Void)? - - var title: String? { - didSet { - button.setTitle(localized(title ?? ""), for: .normal) - button.titleLabel?.font = Theme.shared.fonts.copyButton - } - } - - override init(frame: CGRect) { - super.init(frame: frame) - backgroundColor = .clear - setupButton() - } - - @objc func onTap(_ sender: Any?) { - completion?(false) - } - - @objc func onLongPress(gestureRecognizer: UIGestureRecognizer) { - if gestureRecognizer.state == .began { - completion?(true) - } - } - - override func draw(_ rect: CGRect) { - let bubbleSpace = CGRect(x: self.bounds.origin.x, y: self.bounds.origin.y + 5, width: self.bounds.width, height: self.bounds.height - 5) - let bubblePath = UIBezierPath(roundedRect: bubbleSpace, cornerRadius: 4.0) - - UIColor.white.setStroke() - UIColor.white.setFill() - bubblePath.stroke() - bubblePath.fill() - - let trianglePath = UIBezierPath() - let startPoint = CGPoint(x: (bounds.maxX / 2) - 3, y: bounds.minY + 5) - let tipPoint = CGPoint(x: bounds.maxX / 2, y: bounds.minY) - let endPoint = CGPoint(x: (bounds.maxX / 2) + 3, y: bounds.minY + 5) - - trianglePath.move(to: startPoint) - trianglePath.addLine(to: tipPoint) - trianglePath.addLine(to: endPoint) - trianglePath.close() - UIColor.white.setStroke() - UIColor.white.setFill() - trianglePath.stroke() - trianglePath.fill() - } - - private func setupButton() { - button.setVariation(.secondary) - button.translatesAutoresizingMaskIntoConstraints = false - addSubview(button) - - button.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true - button.topAnchor.constraint(equalTo: topAnchor, constant: 3).isActive = true - button.widthAnchor.constraint(equalTo: widthAnchor).isActive = true - button.heightAnchor.constraint(equalTo: heightAnchor, constant: -3).isActive = true - - button.addTarget(self, action: #selector(onTap(_:)), for: .touchUpInside) - button.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(onLongPress))) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -// MARK: "COPIED" function & view - -extension EmojiIdView { - - func copyToClipboard(string: String) { - let board = UIPasteboard.general - UIImpactFeedbackGenerator(style: .light).impactOccurred() - board.string = string - } - - private func showCopiedView(completion: (() -> Void)? = nil) { - // frames - let containerFrame = CGRect( - x: expandedEmojiIdScrollView.frame.origin.x, - y: expandedEmojiIdScrollView.frame.origin.y, - width: expandedEmojiIdScrollView.frame.size.width, - height: expandedEmojiIdScrollView.frame.size.height - ) - let subviewFrame = CGRect( - x: 0, - y: 0, - width: expandedEmojiIdScrollView.frame.size.width, - height: expandedEmojiIdScrollView.frame.size.height - ) - // container - let containerView = UIView() - containerView.isUserInteractionEnabled = true - containerView.alpha = 0.0 - containerView.layer.borderWidth = 2 - containerView.translatesAutoresizingMaskIntoConstraints = false - containerView.layer.cornerRadius = cornerRadius - containerView.frame = containerFrame - // green background - let greenView = UIView() - greenView.translatesAutoresizingMaskIntoConstraints = false - greenView.frame = subviewFrame - // label - let copiedLabel = UILabel() - copiedLabel.text = localized("emoji.copied") - copiedLabel.font = Theme.shared.fonts.copiedLabel - // copiedLabel.translatesAutoresizingMaskIntoConstraints = false - copiedLabel.frame = subviewFrame - copiedLabel.textAlignment = .center - // add subviews - containerView.addSubview(greenView) - containerView.addSubview(copiedLabel) - - self.copiedLabel = copiedLabel - self.containerView = containerView - self.greenView = greenView - - updateCopyView(theme: theme) - - addViewToSecureContentView(view: containerView) - secureContentView.view.bringSubviewToFront(containerView) - - UIView.animate(withDuration: CATransaction.animationDuration(), - animations: { - containerView.alpha = 1.0 - }) {(_) in - UIView.animate(withDuration: CATransaction.animationDuration(), - delay: 0.5, - animations: { - containerView.alpha = 0.0 - }) { (_) in - containerView.removeFromSuperview() - greenView.removeFromSuperview() - copiedLabel.removeFromSuperview() - completion?() - } - } - } -} diff --git a/MobileWallet/UIElements/GradientView.swift b/MobileWallet/UIElements/GradientView.swift index 62f8c95c..93bc6d45 100644 --- a/MobileWallet/UIElements/GradientView.swift +++ b/MobileWallet/UIElements/GradientView.swift @@ -50,6 +50,7 @@ final class GradientView: UIView { enum Orientation { case horizontal case vertical + case diagonal } // MARK: - Subviews @@ -100,6 +101,9 @@ final class GradientView: UIView { case .vertical: gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5) gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5) + case .diagonal: + gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0) + gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) } } diff --git a/MobileWallet/UIElements/RadialGradientView/RadialGradientView.swift b/MobileWallet/UIElements/RadialGradientView/RadialGradientView.swift index f599a83c..fef31df5 100644 --- a/MobileWallet/UIElements/RadialGradientView/RadialGradientView.swift +++ b/MobileWallet/UIElements/RadialGradientView/RadialGradientView.swift @@ -44,7 +44,7 @@ final class RadialGradientView: UIView { // MARK: - Properties - override class var layerClass: AnyClass { CAGradientLayer.self } + override static var layerClass: AnyClass { CAGradientLayer.self } // MARK: - Setups diff --git a/MobileWallet/UIElements/TabBar/MenuTabBarController.swift b/MobileWallet/UIElements/TabBar/MenuTabBarController.swift index a55909ee..a860eded 100644 --- a/MobileWallet/UIElements/TabBar/MenuTabBarController.swift +++ b/MobileWallet/UIElements/TabBar/MenuTabBarController.swift @@ -134,10 +134,10 @@ private class Transition: NSObject, UIViewControllerAnimatedTransitioning { guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), let fromView = fromVC.view, - let fromIndex = getIndex(forViewController: fromVC), + let fromIndex = index(ofViewController: fromVC), let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to), let toView = toVC.view, - let toIndex = getIndex(forViewController: toVC) + let toIndex = index(ofViewController: toVC) else { transitionContext.completeTransition(false) return @@ -162,11 +162,8 @@ private class Transition: NSObject, UIViewControllerAnimatedTransitioning { } } - func getIndex(forViewController vc: UIViewController) -> Int? { - viewControllers? - .enumerated() - .first { $0.element == vc } - .map { $0.offset } + private func index(ofViewController viewController: UIViewController) -> Int? { + viewControllers?.firstIndex(of: viewController) } } diff --git a/MobileWallet/UIElements/Transaction/TransactionProgressPresenter.swift b/MobileWallet/UIElements/Transaction/TransactionProgressPresenter.swift index c7513f2e..cdf8f157 100644 --- a/MobileWallet/UIElements/Transaction/TransactionProgressPresenter.swift +++ b/MobileWallet/UIElements/Transaction/TransactionProgressPresenter.swift @@ -54,11 +54,12 @@ enum TransactionProgressPresenter { let controller: TransactionViewControllable if let yatID = paymentInfo.yatID { - let inputData = YatTransactionModel.InputData(address: paymentInfo.address, amount: amount, feePerGram: feePerGram, message: message, yatID: yatID, isOneSidedPayment: isOneSidedPayment) + // FIXME: Yat features doesn't support base58 and TariAddressComponent yet. + let inputData = YatTransactionModel.InputData(address: paymentInfo.addressComponents.fullRaw, amount: amount, feePerGram: feePerGram, message: message, yatID: yatID, isOneSidedPayment: isOneSidedPayment) controller = YatTransactionConstructor.buildScene(inputData: inputData) presenter.present(controller, animated: false) } else { - let inputData = SendingTariModel.InputData(address: paymentInfo.address, amount: amount, feePerGram: feePerGram, message: message, isOneSidedPayment: isOneSidedPayment) + let inputData = SendingTariModel.InputData(address: paymentInfo.addressComponents.fullRaw, amount: amount, feePerGram: feePerGram, message: message, isOneSidedPayment: isOneSidedPayment) controller = SendingTariConstructor.buildScene(inputData: inputData) presenter.navigationController?.pushViewController(controller, animated: false) } diff --git a/MobileWallet/en.lproj/Localizable.strings b/MobileWallet/en.lproj/Localizable.strings index a8e5f886..db6f1e38 100644 --- a/MobileWallet/en.lproj/Localizable.strings +++ b/MobileWallet/en.lproj/Localizable.strings @@ -74,10 +74,12 @@ /* Transaction Data */ "transaction.one_sided_payment.inbound_user_placeholder" = "Someone"; -"transaction.one_sided_payment.avatar" = "➡️"; "transaction.one_sided_payment.note.normal" = "You received a one-sided payment"; "transaction.one_sided_payment.note.recovered" = "You recovered some funds"; -"transaction.coinbase.user_placeholder" = "🪙 Coinbase"; +"transaction.coinbase.title.inbound" = "Mining Reward!"; +"transaction.coinbase.title.outbound.part.1.bold" = "You"; +"transaction.coinbase.title.outbound.part.2" = "paid the"; +"transaction.coinbase.title.outbound.part.3.bold" = "Miner"; "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"; @@ -232,8 +234,6 @@ "settings.item.block_explorer" = "Block Explorer"; "settings.item.about" = "About"; -"settings.error.connect_yats_no_connection" = "The app isn't currently connected to a peer-to-peer network. Please check your internet connection or change the base node and try again."; - /* Splash view */ "splash.disclaimer.with_params" = "By creating a wallet, you agree to the terms of the %1$@ and %2$@"; "splash.disclaimer.param.privacy_policy" = "Privacy Policy"; @@ -344,13 +344,6 @@ "verify_phrase.header" = "Select the words in the correct order."; "verify_phrase.title" = "Verify Seed Phrase"; -/* Emoji view */ -"emoji.copied" = "COPIED"; -"emoji.copy" = "Copy Emoji ID"; -"emoji.yat.copy" = "Copy Yat"; -"emoji.paste" = "Paste copied Emoji ID"; -"emoji.hex_tip" = "ℹ️ You can long-press the Copy Emoji ID button to copy the address in hexadecimal format."; - /* Add recipient view */ "add_recipient.inputbox.placeholder" = "Enter Emoji ID or Contact Name"; @@ -383,6 +376,9 @@ "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.no_name.title" = "Name can’t be empty"; +"profile_view.error.no_name.description" = "You can’t delete your name. Give it a quick update instead!"; +"profile_view.error.no_name.button" = "Okay, got it!"; "profile_view.button.wallet" = "Wallet"; "profile_view.button.connect_yat" = "Connect Yats"; "profile_view.button.share.qr_code" = "QR Code"; @@ -578,6 +574,9 @@ "connection_status.popUp.label.base_node_sync.pending" = "Syncing with Base Node"; "connection_status.popUp.label.base_node_sync.success" = "Synced with Base Node"; "connection_status.popUp.label.base_node_sync.failure" = "Failed sync with Base Node"; +"connection_status.popUp.label.chain_tip.prefix" = "Chain Tip:"; +"connection_status.popUp.label.chain_tip.values" = "#%d (%d)"; +"connection_status.popUp.label.chain_tip.waiting_for_connection" = "Waiting for connection..."; /* Select Network View */ "select_network.title" = "Select Network"; @@ -640,7 +639,6 @@ "request.deeplink.message" = "Hi! Please send me %@ Tari(s) using this link:"; "request.buttons.generate_qr" = "Generate QR Code"; "request.qr_code.buttons.share" = "Share"; -"request.qr_code.buttons.close" = "Close"; /* Add base node dialog */ @@ -747,6 +745,10 @@ "error.tx_rejection.6" = "The transaction failed to the invalid input data. This situation shouldn't happen. Please send a bug report or contact us directly."; "error.tx_rejection.7" = "The coinbase was abandoned."; +"error.contact_book.no_name.title" = "Name can’t be empty"; +"error.contact_book.no_name.description" = "A contact without a name? That won’t work. Please add one to keep things running smoothly."; +"error.contact_book.no_name.close_button" = "Will do!"; + /* Dropbox Backup Error */ "error.dropbox_backup.unable_to_create_temp_folder" = "Internal Data Error. Please try again later or contact support."; @@ -825,14 +827,12 @@ "contact_book.section.favorites.placeholder.message.part1" = "You can"; "contact_book.section.favorites.placeholder.message.part2.bold" = "favorite contacts"; "contact_book.section.favorites.placeholder.message.part3" = "to easily access them later. Tap on the contact’s name and then on the star icon to add them to your favorites list."; -"contact_book.menu.option.details" = "Show details"; "contact_book.details.title" = "Contact Profile"; "contact_book.details.menu.option.send" = "Send Tari"; "contact_book.details.menu.option.add_to_favorites" = "Add to Favorites"; "contact_book.details.menu.option.remove_from_favorites" = "Remove from Favorites"; "contact_book.details.menu.option.link" = "Link Contact"; "contact_book.details.menu.option.unlink" = "Unlink Contact"; -"contact_book.details.menu.option.remove_from_favorites" = "Remove from Favorites"; "contact_book.details.menu.option.transaction_list" = "Transaction History"; "contact_book.details.menu.option.delete" = "Delete Contact"; "contact_book.details.menu.option.bitcoin" = "Open Bitcoin Wallet"; @@ -863,13 +863,13 @@ "contact_book.link_contacts.placeholder.buttons.add_contact" = "Add a contact"; "contact_book.link_contacts.placeholder.buttons.permission_settings" = "Go to permission settings"; "contact_book.link_contacts.popup.confirmation.title" = "Link Contact"; -"contact_book.link_contacts.popup.confirmation.message.part1" = "Click “confirm” below to link %@ to"; +"contact_book.link_contacts.popup.confirmation.message.part1" = "Click “confirm” below to link\n%@\nto"; "contact_book.link_contacts.popup.success.title" = "Success!"; -"contact_book.link_contacts.popup.success.message.part1" = "You have successfully linked %@ to"; +"contact_book.link_contacts.popup.success.message.part1" = "You have successfully linked\n%@\nto"; "contact_book.unlink_contact.popup.confirmation.title" = "Unlink Contact"; -"contact_book.unlink_contact.popup.confirmation.message.part1" = "Click “confirm” to unlink %@ from"; +"contact_book.unlink_contact.popup.confirmation.message.part1" = "Click “confirm” to unlink\n%@\nfrom"; "contact_book.unlink_contact.popup.success.title" = "Success!"; -"contact_book.unlink_contact.popup.success.message.part1" = "You have successfully unlinked %@ from"; +"contact_book.unlink_contact.popup.success.message.part1" = "You have successfully unlinked\n%@\nfrom"; "contact_book.contact_type.internal" = "Tari Contact"; "contact_book.contact_type.external" = "External Contact"; "contact_book.contact_type.linked" = "Linked Contact"; @@ -915,3 +915,27 @@ "address_poisoning.label.transaction_count" = "No of transactions: %d"; "address_poisoning.label.last_transction" = "Last transaction date: %@"; "address_poisoning.label.last_transction.never" = "Never"; + +/* Screen Recording Pop-Up */ + +"screen_recording.pop_up.title" = "Screenshots Disabled"; +"screen_recording.pop_up.message.normal" = "Oops! Screenshots are disabled in this app to keep your information safe. Thanks for keeping things secure!"; +"screen_recording.pop_up.message.simple" = "Oops! Screenshots are disabled in this app to keep your information safe. You can always change this later in settings. Thanks for keeping things secure!"; +"screen_recording.pop_up.button.ok" = "No problem, mate!"; +"screen_recording.pop_up.button.enable" = "Enable screenshots"; + +/* Address View */ + +"address_view.details.label.title" = "Address Details"; +"address_view.details.label.network.title" = "Network:"; +"address_view.details.label.features.title" = "Features:"; +"address_view.details.label.view_key.title" = "View Key:"; +"address_view.details.label.address.title" = "Core Address:"; +"address_view.details.label.checksum.title" = "Checksum:"; +"address_view.details.button.copy.base58" = "Copy Base58"; +"address_view.details.button.copy.emojis" = "Copy Emojis"; + +/* Tari Address Featutres */ + +"address_features.one_sided" = "One Sided Payments"; +"address_features.interactive" = "Interactive Payments"; diff --git a/Podfile b/Podfile index c90447bc..a28800a7 100644 --- a/Podfile +++ b/Podfile @@ -4,17 +4,18 @@ use_frameworks! inhibit_all_warnings! target 'MobileWallet' do - pod 'Tor', '408.10.1' - pod 'lottie-ios' + pod 'Tor', '408.12.1' + pod 'lottie-ios', '3.2.3' pod 'SwiftEntryKit', '2.0.0' - pod 'ReachabilitySwift' + pod 'ReachabilitySwift', '5.0.0' pod 'Sentry', '8.14.2' pod 'SwiftKeychainWrapper', '3.4.0' pod 'Giphy', '2.1.22' pod 'IPtProxy', '3.3.0' pod 'Zip', '2.1.2' pod 'SwiftyDropbox', '8.2.1' - pod 'YatLib', '0.3.3' + pod 'Base58Swift', '2.1.10' + pod 'YatLib', '0.4.1' pod 'TariCommon', '0.2.0' end diff --git a/Podfile.lock b/Podfile.lock index 634f9c89..2debf769 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,5 +1,8 @@ PODS: - Alamofire (5.4.4) + - Base58Swift (2.1.10): + - BigInt (~> 5.0.0) + - BigInt (5.0.0) - Giphy (2.1.22): - libwebp - IPtProxy (3.3.0) @@ -25,32 +28,35 @@ PODS: - SwiftyDropbox (8.2.1): - Alamofire (~> 5.4.3) - TariCommon (0.2.0) - - Tor (408.10.1): - - Tor/CTor (= 408.10.1) - - Tor/Core (408.10.1) - - Tor/CTor (408.10.1): + - Tor (408.12.1): + - Tor/CTor (= 408.12.1) + - Tor/Core (408.12.1) + - Tor/CTor (408.12.1): - Tor/Core - - YatLib (0.3.3): + - YatLib (0.4.1): - TariCommon (~> 0.2.0) - Zip (2.1.2) DEPENDENCIES: + - Base58Swift (= 2.1.10) - Giphy (= 2.1.22) - IPtProxy (= 3.3.0) - - lottie-ios - - ReachabilitySwift + - lottie-ios (= 3.2.3) + - ReachabilitySwift (= 5.0.0) - Sentry (= 8.14.2) - SwiftEntryKit (= 2.0.0) - SwiftKeychainWrapper (= 3.4.0) - SwiftyDropbox (= 8.2.1) - TariCommon (= 0.2.0) - - Tor (= 408.10.1) - - YatLib (= 0.3.3) + - Tor (= 408.12.1) + - YatLib (= 0.4.1) - Zip (= 2.1.2) SPEC REPOS: trunk: - Alamofire + - Base58Swift + - BigInt - Giphy - IPtProxy - libwebp @@ -68,6 +74,8 @@ SPEC REPOS: SPEC CHECKSUMS: Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9 + Base58Swift: 53d551f0b33d9478fa63b3445e453a772d6b31a7 + BigInt: 74b4d88367b0e819d9f77393549226d36faeb0d8 Giphy: 6757d929878ef45f70ed62a02a2c9a03994e7b6c IPtProxy: 2136e276560ffd3a1d017683259f52552fc7c2e5 libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c @@ -79,10 +87,10 @@ SPEC CHECKSUMS: SwiftKeychainWrapper: 6fc49fbf7d4a6b0772917acb0e53a1639f6078d6 SwiftyDropbox: f6c55aae36c4ea944fe6b35f807c19897f78b09b TariCommon: 2c8bb97359f59d6b302d2e96c191ff95931da8ac - Tor: 4aa4aee031f892efa43d1afb01c90565990c6312 - YatLib: f56aa60679b20e989a41b6bc92c0081c013b523a + Tor: 958cbe68eef90659339403e40e20d7610a9456dd + YatLib: dda2cfe9e47790f27a13074ea815eac19a919043 Zip: b3fef584b147b6e582b2256a9815c897d60ddc67 -PODFILE CHECKSUM: 43b8e66d361bf301a04d308d4daf6088dedeab0f +PODFILE CHECKSUM: dc384166ada1bb7778b3b6cec7e0de637592c8db -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/README.md b/README.md index 20016bb7..4b55cc73 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Third-party frameworks and libraries are managed using a pre-compiled [Tari](htt - pod 'IPtProxy' - pod 'Zip' - pod 'SwiftyDropbox' +- pod 'Base58Swift' - pod 'YatLib' - pod 'TariCommon' ``` diff --git a/UnitTests/DeepLinkFormatterTests.swift b/UnitTests/DeepLinkFormatterTests.swift index 8186acc0..91796805 100644 --- a/UnitTests/DeepLinkFormatterTests.swift +++ b/UnitTests/DeepLinkFormatterTests.swift @@ -237,29 +237,29 @@ final class DeepLinkFormatterTests: XCTestCase { func testValidContactListAddDeeplinkDecoding() { - let inputDeeplink = URL(string: "tari://test_network/contacts?list[0][alias]=MrWhite&list[0][hex]=FirstHex&list[1][alias]=MrOrange&list[1][hex]=SecondHex")! + let inputDeeplink = URL(string: "tari://test_network/contacts?list[0][alias]=MrWhite&list[0][tariAddress]=FirstHex&list[1][alias]=MrOrange&list[1][tariAddress]=SecondHex")! let expectedResult = ContactListDeeplink(list: [ - ContactListDeeplink.Contact(alias: "MrWhite", hex: "FirstHex"), - ContactListDeeplink.Contact(alias: "MrOrange", hex: "SecondHex") + ContactListDeeplink.Contact(alias: "MrWhite", tariAddress: "FirstHex"), + ContactListDeeplink.Contact(alias: "MrOrange", tariAddress: "SecondHex") ]) let result = try! DeepLinkFormatter.model(type: ContactListDeeplink.self, deeplink: inputDeeplink) XCTAssertEqual(result.list.count, expectedResult.list.count) XCTAssertEqual(result.list[0].alias, expectedResult.list[0].alias) - XCTAssertEqual(result.list[0].hex, expectedResult.list[0].hex) + XCTAssertEqual(result.list[0].tariAddress, expectedResult.list[0].tariAddress) XCTAssertEqual(result.list[1].alias, expectedResult.list[1].alias) - XCTAssertEqual(result.list[1].hex, expectedResult.list[1].hex) + XCTAssertEqual(result.list[1].tariAddress, expectedResult.list[1].tariAddress) } func testValidContactListAddDeeplinkEncoding() { let inputModel = ContactListDeeplink(list: [ - ContactListDeeplink.Contact(alias: "MrWhite", hex: "FirstHex"), - ContactListDeeplink.Contact(alias: "MrOrange", hex: "SecondHex") + ContactListDeeplink.Contact(alias: "MrWhite", tariAddress: "FirstHex"), + ContactListDeeplink.Contact(alias: "MrOrange", tariAddress: "SecondHex") ]) - let expectedResult = URL(string: "tari://test_network/contacts?list[0][alias]=MrWhite&list[0][hex]=FirstHex&list[1][alias]=MrOrange&list[1][hex]=SecondHex")! + let expectedResult = URL(string: "tari://test_network/contacts?list[0][alias]=MrWhite&list[0][tariAddress]=FirstHex&list[1][alias]=MrOrange&list[1][tariAddress]=SecondHex")! let result = try! DeepLinkFormatter.deeplink(model: inputModel) XCTAssertEqual(result, expectedResult) @@ -267,7 +267,7 @@ final class DeepLinkFormatterTests: XCTestCase { func testContactListAddDeeplinkWithInvalidKey() { - let inputDeeplink = URL(string: "tari://test_network/contacts?notlist[0][alias]=MrWhite&list[0][hex]=FirstHex&list[1][alias]=MrOrange&list[1][hex]=SecondHex")! + let inputDeeplink = URL(string: "tari://test_network/contacts?notlist[0][alias]=MrWhite&list[0][tariAddress]=FirstHex&list[1][alias]=MrOrange&list[1][tariAddress]=SecondHex")! let invalidKey = "alias" var result: ContactListDeeplink? diff --git a/UnitTests/NetworkManagerTests.swift b/UnitTests/NetworkManagerTests.swift index 3c6502c2..f9e716ba 100644 --- a/UnitTests/NetworkManagerTests.swift +++ b/UnitTests/NetworkManagerTests.swift @@ -45,7 +45,7 @@ final class NetworkManagerTests: XCTestCase { // MARK: - Properties - private let defaultNetwork = TariNetwork.stagenet + private let defaultNetwork = TariNetwork.nextnet private var networkManager: NetworkManager! // MARK: - Setups diff --git a/dependencies.env b/dependencies.env index 5012281e..bba551d6 100644 --- a/dependencies.env +++ b/dependencies.env @@ -1 +1 @@ -FFI_VERSION="1.0.0-rc.8" \ No newline at end of file +FFI_VERSION="1.4.1-rc.0" \ No newline at end of file