diff --git a/.gitignore b/.gitignore index 7e2762a0..5c50bfb1 100644 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,7 @@ fastlane/screenshots/*.html* fastlane/test_output .DS_Store fastlane/Appfile -MobileWallet/TariLib/libtari_wallet_ffi.a +MobileWallet/TariLib/libtari_wallet_ffi_ios.xcframework MobileWallet/Constants.plist .idea/ fastlane/.env* diff --git a/.swiftlint.yml b/.swiftlint.yml index aa96c16b..c3ab9a60 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,28 +1,32 @@ line_length: # Target: 120/200 - - 245 - - 325 + - 230 + - 250 type_body_length: # Target: 250/350 + - 380 - 400 - - 500 function_body_length: # Target: 50/100 + - 105 - 120 - - 220 file_length: # Target: 400/1000 - - 910 - - 1510 + - 675 + - 1000 disabled_rules: - identifier_name - - cyclomatic_complexity - multiple_closures_with_trailing_closure - function_parameter_count - todo - - nesting - - type_name excluded: - Pods - - UnitTests \ No newline at end of file + - UnitTests + # Files scheduled for refactor + - ./**/TariSettings.swift + - ./**/SettingsViewController.swift + - ./**/AddAmountViewController.swift + - ./**/EmojiIdView.swift + - ./**/AnimatedBalanceLabel.swift + - ./**/TxsListViewController.swift \ No newline at end of file diff --git a/MobileWallet.xcodeproj/project.pbxproj b/MobileWallet.xcodeproj/project.pbxproj index badc6e09..9fb626f5 100644 --- a/MobileWallet.xcodeproj/project.pbxproj +++ b/MobileWallet.xcodeproj/project.pbxproj @@ -11,9 +11,7 @@ 001F6CDC238011CA00FA7002 /* PublicKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F6CDB238011CA00FA7002 /* PublicKey.swift */; }; 001F6CE02380198D00FA7002 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F6CDF2380198D00FA7002 /* Contacts.swift */; }; 001F6CE42380253B00FA7002 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F6CE32380253B00FA7002 /* Contact.swift */; }; - 00230B0923770CEA00F23E34 /* CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00230B0823770CEA00F23E34 /* CALayer.swift */; }; 00262EB4236F024400A6C8A0 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00262EB3236F024400A6C8A0 /* Theme.swift */; }; - 00325ECA239E4A1000F76918 /* FadedOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00325EC9239E4A1000F76918 /* FadedOverlayView.swift */; }; 0039B1B223FA925500F91E0F /* PasteEmojisView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0039B1B123FA925500F91E0F /* PasteEmojisView.swift */; }; 004277F323E0407900AE7BD9 /* TextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004277F223E0407900AE7BD9 /* TextButton.swift */; }; 004277F723E0628A00AE7BD9 /* UIViewController+Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004277F623E0628A00AE7BD9 /* UIViewController+Content.swift */; }; @@ -25,7 +23,6 @@ 006D211D23CEDF16007D1C10 /* MicroTari.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006D211C23CEDF16007D1C10 /* MicroTari.swift */; }; 0070A7BC2379847100C25E1C /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0070A7BB2379847100C25E1C /* LocalAuthentication.framework */; }; 0072BF24247E735700BD28FB /* ReminderNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0072BF23247E735700BD28FB /* ReminderNotifications.swift */; }; - 0074619B239A57B000F00966 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0074619A239A57B000F00966 /* UIBarButtonItem.swift */; }; 0087A1A923F4235400B89EE7 /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0087A1A523F4235400B89EE7 /* ScanViewController.swift */; }; 0087A1AF23F4235400B89EE7 /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0087A1A723F4235400B89EE7 /* ContactCell.swift */; }; 0087A1B223F4235400B89EE7 /* AddRecipientViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0087A1A823F4235400B89EE7 /* AddRecipientViewController.swift */; }; @@ -34,7 +31,6 @@ 00A66523243B3E380046E730 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A66522243B3E380046E730 /* ErrorView.swift */; }; 00A66527243C766D0046E730 /* ConnectionMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A66526243C766D0046E730 /* ConnectionMonitor.swift */; }; 00A994332396434B007D9990 /* PulseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A994322396434B007D9990 /* PulseButton.swift */; }; - 00B084C0249E471800F7B9BD /* EmojiSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B084BF249E471800F7B9BD /* EmojiSet.swift */; }; 00B084C5249E4F6900F7B9BD /* SeedWords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B084C4249E4F6900F7B9BD /* SeedWords.swift */; }; 00B4D6F6241B778D00ED8318 /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B4D6F5241B778D00ED8318 /* BackgroundTaskManager.swift */; }; 00B4D6F8241B77A900ED8318 /* ScheduleReminderNotificationsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B4D6F7241B77A900ED8318 /* ScheduleReminderNotificationsOperation.swift */; }; @@ -143,9 +139,8 @@ 3A39AF1C27B1570F00A32F46 /* SettingsViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A39AF1B27B1570F00A32F46 /* SettingsViewFooter.swift */; }; 3A39E57229012C8B000BBEBF /* DropboxBackupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A39E57129012C8B000BBEBF /* DropboxBackupService.swift */; }; 3A3D70B9291A625700A8CD7A /* BackupStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3D70B8291A625700A8CD7A /* BackupStatus.swift */; }; - 3A3D70BB291A637100A8CD7A /* MenuTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3D70BA291A637100A8CD7A /* MenuTableView.swift */; }; + 3A3D70BB291A637100A8CD7A /* BaseMenuTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3D70BA291A637100A8CD7A /* BaseMenuTableView.swift */; }; 3A3E7AD6284E03140065F3C0 /* UTXOTileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3E7AD5284E03140065F3C0 /* UTXOTileView.swift */; }; - 3A3E8BF12924F84300490E57 /* Constants.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3A3E8BF02924F84300490E57 /* Constants.plist */; }; 3A3E8BF32924FB4C00490E57 /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3E8BF22924FB4C00490E57 /* MigrationManager.swift */; }; 3A4205922798001A00A8D49C /* AmountKeyboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4205912798001A00A8D49C /* AmountKeyboardView.swift */; }; 3A420594279802E500A8D49C /* BaseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A420593279802E500A8D49C /* BaseButton.swift */; }; @@ -369,15 +364,38 @@ 49996E1923F164BA002B6696 /* AnimatedBalanceLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49996E1823F164BA002B6696 /* AnimatedBalanceLabel.swift */; }; 4C2C95C7237962CB005058AB /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C2C95C6237962CB005058AB /* libc++.tbd */; }; 4C363B762574F15E00D99868 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C363B372574EFE500D99868 /* OpenSSL.xcframework */; }; - 4CCF87ED23E8273900C8CDB1 /* libtari_wallet_ffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CCF87EC23E8273900C8CDB1 /* libtari_wallet_ffi.a */; }; 4CD20A362407967B007B64D8 /* TransportConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD20A352407967B007B64D8 /* TransportConfig.swift */; }; 4CDEC323273A5E3600999DCB /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDEC322273A5E3500999DCB /* Balance.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 */; }; + 540CB6F529C1DE42003FACEF /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540CB6F429C1DE42003FACEF /* AddContactView.swift */; }; + 540CB6F729C1DE83003FACEF /* AddContactConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540CB6F629C1DE83003FACEF /* AddContactConstructor.swift */; }; 5410F5D12951F04B006976DC /* TariWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5410F5D02951F04B006976DC /* TariWindow.swift */; }; + 5421551029A4AE37000A3F49 /* ContactBookContactListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5421550F29A4AE37000A3F49 /* ContactBookContactListViewController.swift */; }; + 5421551229A4AE4B000A3F49 /* ContactBookContactListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5421551129A4AE4B000A3F49 /* ContactBookContactListView.swift */; }; + 5421551429A4AE92000A3F49 /* SearchTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5421551329A4AE92000A3F49 /* SearchTextField.swift */; }; + 5430B97729B7400900C80AA2 /* LinkContactsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5430B97629B7400900C80AA2 /* LinkContactsModel.swift */; }; + 5430B97929B7401200C80AA2 /* LinkContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5430B97829B7401200C80AA2 /* LinkContactsViewController.swift */; }; + 5430B97B29B7401C00C80AA2 /* LinkContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5430B97A29B7401C00C80AA2 /* LinkContactsView.swift */; }; + 5430B97D29B7402600C80AA2 /* LinkContactsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5430B97C29B7402600C80AA2 /* LinkContactsConstructor.swift */; }; + 54394EDE29CA133400E7CAEA /* LoadingImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */; }; 543DF19A293F37E70031EA70 /* CustomBridgesHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543DF199293F37E70031EA70 /* CustomBridgesHeaderView.swift */; }; 543DF19C293F3DA90031EA70 /* BridgesConfigurationFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543DF19B293F3DA90031EA70 /* BridgesConfigurationFooterView.swift */; }; 5443954629800F1600786682 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5443954529800F1600786682 /* OnboardingViewController.swift */; }; + 544692A429B6059C0081085D /* ContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544692A329B6059C0081085D /* ContactsManager.swift */; }; 544D9D4C296D9B2000D8ECEF /* UnblindedOutputs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544D9D4B296D9B2000D8ECEF /* UnblindedOutputs.swift */; }; 544D9D4E296D9B3300D8ECEF /* UnblindedOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544D9D4D296D9B3300D8ECEF /* UnblindedOutput.swift */; }; + 5453241A29A60C6E009281A7 /* TariPagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453241929A60C6E009281A7 /* TariPagerViewController.swift */; }; + 5453241C29A60CA0009281A7 /* TariPagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453241B29A60CA0009281A7 /* TariPagerView.swift */; }; + 5453241E29A67FFF009281A7 /* ContactCapsuleMenuBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453241D29A67FFF009281A7 /* ContactCapsuleMenuBackground.swift */; }; + 5453242029A68310009281A7 /* PageToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453241F29A68310009281A7 /* PageToolbarView.swift */; }; + 5453242229A68CA0009281A7 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453242129A68CA0009281A7 /* RoundedButton.swift */; }; + 5453242429A68D14009281A7 /* TariGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453242329A68D14009281A7 /* TariGradientView.swift */; }; + 5453242629A68D7D009281A7 /* RoundedAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5453242529A68D7D009281A7 /* RoundedAvatarView.swift */; }; + 5455969829B08D7B00D6719E /* FormTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5455969729B08D7B00D6719E /* FormTextField.swift */; }; + 5455969B29B0A6B500D6719E /* InternalContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5455969A29B0A6B500D6719E /* InternalContactsManager.swift */; }; + 5455969D29B0A73300D6719E /* ExternalContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5455969C29B0A73300D6719E /* ExternalContactsManager.swift */; }; 545A9D14294F6ED3008D24A6 /* ThemeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545A9D13294F6ED3008D24A6 /* ThemeSettingsViewController.swift */; }; 545A9D16294F6EEA008D24A6 /* ThemeSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545A9D15294F6EEA008D24A6 /* ThemeSettingsView.swift */; }; 545A9D18294F6F50008D24A6 /* ThemeSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545A9D17294F6F50008D24A6 /* ThemeSettingsModel.swift */; }; @@ -386,10 +404,23 @@ 545A9D1F294F9323008D24A6 /* RadioButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545A9D1E294F9323008D24A6 /* RadioButtonView.swift */; }; 545A9D21294F9CCE008D24A6 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545A9D20294F9CCE008D24A6 /* UserSettings.swift */; }; 545A9D24294F9E77008D24A6 /* UserSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545A9D23294F9E77008D24A6 /* UserSettingsManager.swift */; }; + 545B80A2298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545B80A1298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework */; }; + 5460258629A74D8200CF5764 /* ContactDetailsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5460258529A74D8200CF5764 /* ContactDetailsModel.swift */; }; + 5460258829A74DA200CF5764 /* ContactDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5460258729A74DA200CF5764 /* ContactDetailsViewController.swift */; }; + 5460258A29A74DAB00CF5764 /* ContactDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5460258929A74DAB00CF5764 /* ContactDetailsView.swift */; }; + 5460258C29A74DC100CF5764 /* ContactDetailsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5460258B29A74DC100CF5764 /* ContactDetailsConstructor.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 */; }; 546B032A2983F33600DBED8E /* OnboardingPagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546B03292983F33600DBED8E /* OnboardingPagerView.swift */; }; + 546CE7F429AF514A00264699 /* FormOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546CE7F329AF514A00264699 /* FormOverlay.swift */; }; + 546CE7F629AF517900264699 /* FormOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546CE7F529AF517900264699 /* FormOverlayView.swift */; }; + 546CE7F929AF523F00264699 /* ContactBookFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546CE7F829AF523F00264699 /* ContactBookFormView.swift */; }; + 547A85D329ACF2D100D94985 /* MenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547A85D229ACF2D100D94985 /* MenuCell.swift */; }; + 547A85D529AD23D800D94985 /* MenuTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547A85D429AD23D800D94985 /* MenuTableView.swift */; }; + 547A85D729AD248400D94985 /* MenuTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547A85D629AD248400D94985 /* MenuTableHeaderView.swift */; }; + 548C137B29BF64AD00ACDF0C /* UITableView+Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548C137A29BF64AD00ACDF0C /* UITableView+Common.swift */; }; + 54909AAF29C05822002D1070 /* ContactType+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54909AAE29C05822002D1070 /* ContactType+Data.swift */; }; 5491696E2940F78000783E54 /* LogFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5491696D2940F78000783E54 /* LogFilesManager.swift */; }; 549E0E0D29754E9C00828743 /* PartialBackupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E0E0C29754E9B00828743 /* PartialBackupModel.swift */; }; 54A6E9F0297F1F9900A60853 /* StagedWalletSecurityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6E9EF297F1F9900A60853 /* StagedWalletSecurityManager.swift */; }; @@ -399,10 +430,22 @@ 54AA5D5A298122B70031A396 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AA5D59298122B70031A396 /* OnboardingView.swift */; }; 54AA5D5C2981267C0031A396 /* OnboardingPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AA5D5B2981267C0031A396 /* OnboardingPageViewController.swift */; }; 54AA5D5E298126CA0031A396 /* OnboardingPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AA5D5D298126CA0031A396 /* OnboardingPageView.swift */; }; + 54B7F4452996583800BB484B /* ContactBookCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7F4442996583800BB484B /* ContactBookCell.swift */; }; + 54B854B929BF993600A2367A /* ContactBookListPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B854B829BF993600A2367A /* ContactBookListPlaceholder.swift */; }; + 54CB354429B905AA00551D5A /* PopPresenter+ContactBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CB354329B905AA00551D5A /* PopPresenter+ContactBook.swift */; }; + 54D419B32995092F00D496B4 /* ContactBookModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D419B22995092F00D496B4 /* ContactBookModel.swift */; }; + 54D419B52995093800D496B4 /* ContactBookViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D419B42995093800D496B4 /* ContactBookViewController.swift */; }; + 54D419B72995094100D496B4 /* ContactBookView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D419B62995094100D496B4 /* ContactBookView.swift */; }; + 54D419B92995094B00D496B4 /* ContactBookConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D419B82995094B00D496B4 /* ContactBookConstructor.swift */; }; + 54DD2E8629C37A7600C8C0D9 /* UINavigationController+Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DD2E8529C37A7600C8C0D9 /* UINavigationController+Common.swift */; }; + 54DDB18129A366700021DFD4 /* ContactCapsuleMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DDB18029A366700021DFD4 /* ContactCapsuleMenu.swift */; }; + 54DDB18329A373C90021DFD4 /* ContactBookMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DDB18229A373C90021DFD4 /* ContactBookMenuButton.swift */; }; 54DE32D02930BE3D0060108A /* ColorTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DE32CF2930BE3D0060108A /* ColorTheme.swift */; }; 54DE32D22930BE7F0060108A /* ThemeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DE32D12930BE7F0060108A /* ThemeCoordinator.swift */; }; 54DE32D42930C2640060108A /* DynamicThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DE32D32930C2640060108A /* DynamicThemeView.swift */; }; 54DEF9F32987DA8700C4B749 /* UIScreen+Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DEF9F22987DA8700C4B749 /* UIScreen+Tools.swift */; }; + 54E1CB1E29C7292700E6777C /* EmojiTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E1CB1D29C7292700E6777C /* EmojiTextField.swift */; }; + 54F23DE729BBA0C3001E39A2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 54F23DE629BBA0C3001E39A2 /* InfoPlist.strings */; }; A01F54A8255EAB7E00F49AFA /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = A01F54A7255EAB7E00F49AFA /* Localization.swift */; }; A0779C612552C1AF00614EF3 /* DeleteWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0779C602552C1AF00614EF3 /* DeleteWalletViewController.swift */; }; BF0A7766241EEA0A00861A3E /* FaceID.json in Resources */ = {isa = PBXBuildFile; fileRef = BF0A7765241EEA0A00861A3E /* FaceID.json */; }; @@ -460,9 +503,7 @@ 001F6CDB238011CA00FA7002 /* PublicKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKey.swift; sourceTree = ""; }; 001F6CDF2380198D00FA7002 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = ""; }; 001F6CE32380253B00FA7002 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; - 00230B0823770CEA00F23E34 /* CALayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CALayer.swift; sourceTree = ""; }; 00262EB3236F024400A6C8A0 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; - 00325EC9239E4A1000F76918 /* FadedOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadedOverlayView.swift; sourceTree = ""; }; 0039B1B123FA925500F91E0F /* PasteEmojisView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteEmojisView.swift; sourceTree = ""; }; 004277F223E0407900AE7BD9 /* TextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextButton.swift; sourceTree = ""; }; 004277F623E0628A00AE7BD9 /* UIViewController+Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Content.swift"; sourceTree = ""; }; @@ -474,7 +515,6 @@ 006D211C23CEDF16007D1C10 /* MicroTari.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicroTari.swift; sourceTree = ""; }; 0070A7BB2379847100C25E1C /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; }; 0072BF23247E735700BD28FB /* ReminderNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderNotifications.swift; sourceTree = ""; }; - 0074619A239A57B000F00966 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = ""; }; 0087A1A523F4235400B89EE7 /* ScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = ""; }; 0087A1A723F4235400B89EE7 /* ContactCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = ""; }; 0087A1A823F4235400B89EE7 /* AddRecipientViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipientViewController.swift; sourceTree = ""; }; @@ -483,7 +523,6 @@ 00A66522243B3E380046E730 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; 00A66526243C766D0046E730 /* ConnectionMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionMonitor.swift; sourceTree = ""; }; 00A994322396434B007D9990 /* PulseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PulseButton.swift; sourceTree = ""; }; - 00B084BF249E471800F7B9BD /* EmojiSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiSet.swift; sourceTree = ""; }; 00B084C4249E4F6900F7B9BD /* SeedWords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedWords.swift; sourceTree = ""; }; 00B4D6F5241B778D00ED8318 /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = ""; }; 00B4D6F7241B77A900ED8318 /* ScheduleReminderNotificationsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleReminderNotificationsOperation.swift; sourceTree = ""; }; @@ -597,7 +636,7 @@ 3A39AF1B27B1570F00A32F46 /* SettingsViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewFooter.swift; sourceTree = ""; }; 3A39E57129012C8B000BBEBF /* DropboxBackupService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropboxBackupService.swift; sourceTree = ""; }; 3A3D70B8291A625700A8CD7A /* BackupStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupStatus.swift; sourceTree = ""; }; - 3A3D70BA291A637100A8CD7A /* MenuTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuTableView.swift; sourceTree = ""; }; + 3A3D70BA291A637100A8CD7A /* BaseMenuTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMenuTableView.swift; sourceTree = ""; }; 3A3E7AD5284E03140065F3C0 /* UTXOTileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOTileView.swift; sourceTree = ""; }; 3A3E8BF02924F84300490E57 /* Constants.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Constants.plist; sourceTree = ""; }; 3A3E8BF22924FB4C00490E57 /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = ""; }; @@ -821,19 +860,41 @@ 3AFC6E022745051400A20287 /* SettingsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = ""; }; 491AB89F23F3FF4400372189 /* AddAmountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAmountViewController.swift; sourceTree = ""; }; 49996E1823F164BA002B6696 /* AnimatedBalanceLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedBalanceLabel.swift; sourceTree = ""; }; - 4C2C95B723795930005058AB /* wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wallet.h; sourceTree = ""; }; 4C2C95B8237959B3005058AB /* MobileWallet-bridging-header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MobileWallet-bridging-header.h"; sourceTree = ""; }; 4C2C95C6237962CB005058AB /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 4C363B372574EFE500D99868 /* OpenSSL.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OpenSSL.xcframework; path = "Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework"; sourceTree = ""; }; - 4CCF87EC23E8273900C8CDB1 /* libtari_wallet_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtari_wallet_ffi.a; path = MobileWallet/TariLib/libtari_wallet_ffi.a; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; + 540CB6F429C1DE42003FACEF /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = ""; }; + 540CB6F629C1DE83003FACEF /* AddContactConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactConstructor.swift; sourceTree = ""; }; 5410F5D02951F04B006976DC /* TariWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariWindow.swift; sourceTree = ""; }; + 5421550F29A4AE37000A3F49 /* ContactBookContactListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookContactListViewController.swift; sourceTree = ""; }; + 5421551129A4AE4B000A3F49 /* ContactBookContactListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookContactListView.swift; sourceTree = ""; }; + 5421551329A4AE92000A3F49 /* SearchTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTextField.swift; sourceTree = ""; }; + 5430B97629B7400900C80AA2 /* LinkContactsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkContactsModel.swift; sourceTree = ""; }; + 5430B97829B7401200C80AA2 /* LinkContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkContactsViewController.swift; sourceTree = ""; }; + 5430B97A29B7401C00C80AA2 /* LinkContactsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkContactsView.swift; sourceTree = ""; }; + 5430B97C29B7402600C80AA2 /* LinkContactsConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkContactsConstructor.swift; sourceTree = ""; }; + 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingImageView.swift; sourceTree = ""; }; 543DF199293F37E70031EA70 /* CustomBridgesHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBridgesHeaderView.swift; sourceTree = ""; }; 543DF19B293F3DA90031EA70 /* BridgesConfigurationFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgesConfigurationFooterView.swift; sourceTree = ""; }; 5443954529800F1600786682 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; + 544692A329B6059C0081085D /* ContactsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsManager.swift; sourceTree = ""; }; 544D9D4B296D9B2000D8ECEF /* UnblindedOutputs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnblindedOutputs.swift; sourceTree = ""; }; 544D9D4D296D9B3300D8ECEF /* UnblindedOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnblindedOutput.swift; sourceTree = ""; }; + 5453241929A60C6E009281A7 /* TariPagerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariPagerViewController.swift; sourceTree = ""; }; + 5453241B29A60CA0009281A7 /* TariPagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariPagerView.swift; sourceTree = ""; }; + 5453241D29A67FFF009281A7 /* ContactCapsuleMenuBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCapsuleMenuBackground.swift; sourceTree = ""; }; + 5453241F29A68310009281A7 /* PageToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageToolbarView.swift; sourceTree = ""; }; + 5453242129A68CA0009281A7 /* RoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; + 5453242329A68D14009281A7 /* TariGradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariGradientView.swift; sourceTree = ""; }; + 5453242529A68D7D009281A7 /* RoundedAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedAvatarView.swift; sourceTree = ""; }; + 5455969729B08D7B00D6719E /* FormTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormTextField.swift; sourceTree = ""; }; + 5455969A29B0A6B500D6719E /* InternalContactsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalContactsManager.swift; sourceTree = ""; }; + 5455969C29B0A73300D6719E /* ExternalContactsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalContactsManager.swift; sourceTree = ""; }; 545A9D13294F6ED3008D24A6 /* ThemeSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsViewController.swift; sourceTree = ""; }; 545A9D15294F6EEA008D24A6 /* ThemeSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsView.swift; sourceTree = ""; }; 545A9D17294F6F50008D24A6 /* ThemeSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsModel.swift; sourceTree = ""; }; @@ -842,10 +903,23 @@ 545A9D1E294F9323008D24A6 /* RadioButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonView.swift; sourceTree = ""; }; 545A9D20294F9CCE008D24A6 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; 545A9D23294F9E77008D24A6 /* UserSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsManager.swift; sourceTree = ""; }; + 545B80A1298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libtari_wallet_ffi_ios.xcframework; path = MobileWallet/TariLib/libtari_wallet_ffi_ios.xcframework; sourceTree = ""; }; + 5460258529A74D8200CF5764 /* ContactDetailsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailsModel.swift; sourceTree = ""; }; + 5460258729A74DA200CF5764 /* ContactDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailsViewController.swift; sourceTree = ""; }; + 5460258929A74DAB00CF5764 /* ContactDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailsView.swift; sourceTree = ""; }; + 5460258B29A74DC100CF5764 /* ContactDetailsConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailsConstructor.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 = ""; }; 546B03292983F33600DBED8E /* OnboardingPagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPagerView.swift; sourceTree = ""; }; + 546CE7F329AF514A00264699 /* FormOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormOverlay.swift; sourceTree = ""; }; + 546CE7F529AF517900264699 /* FormOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormOverlayView.swift; sourceTree = ""; }; + 546CE7F829AF523F00264699 /* ContactBookFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookFormView.swift; sourceTree = ""; }; + 547A85D229ACF2D100D94985 /* MenuCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCell.swift; sourceTree = ""; }; + 547A85D429AD23D800D94985 /* MenuTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuTableView.swift; sourceTree = ""; }; + 547A85D629AD248400D94985 /* MenuTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuTableHeaderView.swift; sourceTree = ""; }; + 548C137A29BF64AD00ACDF0C /* UITableView+Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Common.swift"; sourceTree = ""; }; + 54909AAE29C05822002D1070 /* ContactType+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactType+Data.swift"; sourceTree = ""; }; 5491696D2940F78000783E54 /* LogFilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogFilesManager.swift; sourceTree = ""; }; 549E0E0C29754E9B00828743 /* PartialBackupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialBackupModel.swift; sourceTree = ""; }; 54A6E9EF297F1F9900A60853 /* StagedWalletSecurityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StagedWalletSecurityManager.swift; sourceTree = ""; }; @@ -855,10 +929,22 @@ 54AA5D59298122B70031A396 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; 54AA5D5B2981267C0031A396 /* OnboardingPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPageViewController.swift; sourceTree = ""; }; 54AA5D5D298126CA0031A396 /* OnboardingPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPageView.swift; sourceTree = ""; }; + 54B7F4442996583800BB484B /* ContactBookCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookCell.swift; sourceTree = ""; }; + 54B854B829BF993600A2367A /* ContactBookListPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookListPlaceholder.swift; sourceTree = ""; }; + 54CB354329B905AA00551D5A /* PopPresenter+ContactBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PopPresenter+ContactBook.swift"; sourceTree = ""; }; + 54D419B22995092F00D496B4 /* ContactBookModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookModel.swift; sourceTree = ""; }; + 54D419B42995093800D496B4 /* ContactBookViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookViewController.swift; sourceTree = ""; }; + 54D419B62995094100D496B4 /* ContactBookView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookView.swift; sourceTree = ""; }; + 54D419B82995094B00D496B4 /* ContactBookConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookConstructor.swift; sourceTree = ""; }; + 54DD2E8529C37A7600C8C0D9 /* UINavigationController+Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Common.swift"; sourceTree = ""; }; + 54DDB18029A366700021DFD4 /* ContactCapsuleMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCapsuleMenu.swift; sourceTree = ""; }; + 54DDB18229A373C90021DFD4 /* ContactBookMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookMenuButton.swift; sourceTree = ""; }; 54DE32CF2930BE3D0060108A /* ColorTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorTheme.swift; sourceTree = ""; }; 54DE32D12930BE7F0060108A /* ThemeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeCoordinator.swift; sourceTree = ""; }; 54DE32D32930C2640060108A /* DynamicThemeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicThemeView.swift; sourceTree = ""; }; 54DEF9F22987DA8700C4B749 /* UIScreen+Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScreen+Tools.swift"; sourceTree = ""; }; + 54E1CB1D29C7292700E6777C /* EmojiTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiTextField.swift; sourceTree = ""; }; + 54F23DE629BBA0C3001E39A2 /* InfoPlist.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = InfoPlist.strings; sourceTree = ""; }; A01F54A7255EAB7E00F49AFA /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; A0779C602552C1AF00614EF3 /* DeleteWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteWalletViewController.swift; sourceTree = ""; }; BF0A7765241EEA0A00861A3E /* FaceID.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = FaceID.json; sourceTree = ""; }; @@ -896,9 +982,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 545B80A2298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework in Frameworks */, 4C363B762574F15E00D99868 /* OpenSSL.xcframework in Frameworks */, 371189732488FF11004D0CE3 /* CloudKit.framework in Frameworks */, - 4CCF87ED23E8273900C8CDB1 /* libtari_wallet_ffi.a in Frameworks */, 00DC2E22238554FD00036DDC /* libsqlite3.tbd in Frameworks */, 0070A7BC2379847100C25E1C /* LocalAuthentication.framework in Frameworks */, 4C2C95C7237962CB005058AB /* libc++.tbd in Frameworks */, @@ -949,14 +1035,12 @@ isa = PBXGroup; children = ( 376C62AD247574850091BB28 /* Animation+EnumInit.swift */, - 00230B0823770CEA00F23E34 /* CALayer.swift */, 00BBD808237D80C800EBF5E6 /* Date.swift */, 373CCDC42490F3B600D0A2C9 /* FileManager+SecureCopy.swift */, BF8316FC23EF7EAA00235403 /* LAContext.swift */, 0042780223E8421100AE7BD9 /* String.swift */, 37CB9F892451A2AD00C495F2 /* String+Emoji.swift */, 37547D512460165500EB59CC /* UIApplication+KeyWindow.swift */, - 0074619A239A57B000F00966 /* UIBarButtonItem.swift */, 37E0B007249B700F00DFE315 /* UIFont+FontStyle.swift */, 37ABB68F24781CE800F08163 /* UILabel.swift */, 37C8BA46248126B4005BBC05 /* UIScrollView+RefreshControl.swift */, @@ -976,6 +1060,8 @@ 3A685425290AD04600032963 /* DateFormatter+Formats.swift */, 54A6E9F3297F260800A60853 /* UIViewController+Common.swift */, 54DEF9F22987DA8700C4B749 /* UIScreen+Tools.swift */, + 548C137A29BF64AD00ACDF0C /* UITableView+Common.swift */, + 54DD2E8529C37A7600C8C0D9 /* UINavigationController+Common.swift */, ); path = Extensions; sourceTree = ""; @@ -1118,20 +1204,14 @@ 00E2BCAF236B44CE00C2A105 /* UIElements */ = { isa = PBXGroup; children = ( - 546B03242983E91600DBED8E /* Navigation */, - 546B03212983B46E00DBED8E /* Labels */, - 3A4205912798001A00A8D49C /* AmountKeyboardView.swift */, - 00D988682449B04B000FDC9C /* AnimatedRefreshingView.swift */, - 3AECD48F284F3BB300D81C80 /* BaseNavigationContentView.swift */, - 373CCDBC2490B66E00D0A2C9 /* CircularProgressView.swift */, - BFAB5D1023FDEA69009E8563 /* EmojiIdView.swift */, - 00325EC9239E4A1000F76918 /* FadedOverlayView.swift */, - 3A13260227EDE42A00289C8C /* GradientView.swift */, - 375DB1DF246E90D100B2BEF4 /* NavigationBar.swift */, - 3A4205A027980FFA00A8D49C /* QRCodeView.swift */, + 546CE7F229AF50F300264699 /* Keyboard Form */, + 547A85D129ACF2C800D94985 /* Menu */, 00E2BCB8236B5BC000C2A105 /* Buttons */, 37B444A72489436100592D92 /* Checkbox */, 00E2BCB0236B44DB00C2A105 /* FloatingPanel */, + 546B03212983B46E00DBED8E /* Labels */, + 546B03242983E91600DBED8E /* Navigation */, + 5453241829A60C60009281A7 /* Pager */, 3723A7A924ACD029003382EB /* PasswordField */, 37B48A7B24B31AB300F8A8D2 /* PendingView */, 37BB6969245705D20013AC4D /* RadialGradientView */, @@ -1140,8 +1220,19 @@ 3A1325FF27EDE39F00289C8C /* Transaction */, 371A081624728FCB00F97713 /* TransitionUILabel */, 375AFDCD2475380B00C62CA1 /* WebBrowserView */, - 3AFC3D5F288EC3FA0046D386 /* UnselectableTextView.swift */, + 3A4205912798001A00A8D49C /* AmountKeyboardView.swift */, + 00D988682449B04B000FDC9C /* AnimatedRefreshingView.swift */, + 3AECD48F284F3BB300D81C80 /* BaseNavigationContentView.swift */, + 373CCDBC2490B66E00D0A2C9 /* CircularProgressView.swift */, + BFAB5D1023FDEA69009E8563 /* EmojiIdView.swift */, + 3A13260227EDE42A00289C8C /* GradientView.swift */, + 375DB1DF246E90D100B2BEF4 /* NavigationBar.swift */, + 3A4205A027980FFA00A8D49C /* QRCodeView.swift */, 5410F5D02951F04B006976DC /* TariWindow.swift */, + 3AFC3D5F288EC3FA0046D386 /* UnselectableTextView.swift */, + 5453242329A68D14009281A7 /* TariGradientView.swift */, + 5453242529A68D7D009281A7 /* RoundedAvatarView.swift */, + 54E1CB1D29C7292700E6777C /* EmojiTextField.swift */, ); path = UIElements; sourceTree = ""; @@ -1157,6 +1248,7 @@ 00E2BCB5236B469C00C2A105 /* Screens */ = { isa = PBXGroup; children = ( + 54D419B12995091B00D496B4 /* Contact Book */, 3ACDA70326B0317300F138B8 /* RestoreWalletFromSeedsProgress */, 3A4376E3269C0BF0006107B0 /* RestoreWalletFromSeeds */, 00BBD7FA237C462400EBF5E6 /* AppEntry */, @@ -1187,13 +1279,14 @@ 00E2BCB8236B5BC000C2A105 /* Buttons */ = { isa = PBXGroup; children = ( - 3A420593279802E500A8D49C /* BaseButton.swift */, 00E2BCB9236B5BE800C2A105 /* ActionButton.swift */, + 3A420593279802E500A8D49C /* BaseButton.swift */, + 3A93159A286C30AA0001660E /* LeftImageButton.swift */, 37875E4324D85C4300C0595B /* LoadingGIFButton.swift */, 00A994322396434B007D9990 /* PulseButton.swift */, + 5453242129A68CA0009281A7 /* RoundedButton.swift */, 0053872F24065F6C00901A68 /* SlideView.swift */, 004277F223E0407900AE7BD9 /* TextButton.swift */, - 3A93159A286C30AA0001660E /* LeftImageButton.swift */, ); path = Buttons; sourceTree = ""; @@ -1223,6 +1316,7 @@ children = ( 00B4D6FB241B97CD00ED8318 /* Tari.entitlements */, 4C2C95B8237959B3005058AB /* MobileWallet-bridging-header.h */, + 54F23DE629BBA0C3001E39A2 /* InfoPlist.strings */, 00E491AC2366E08F007B332D /* Info.plist */, 3A3E8BF02924F84300490E57 /* Constants.plist */, 00E491992366E08B007B332D /* AppDelegate.swift */, @@ -1243,8 +1337,8 @@ 27F235021D9E2060A3CBB465 /* Frameworks */ = { isa = PBXGroup; children = ( + 545B80A1298D40CF00405AE9 /* libtari_wallet_ffi_ios.xcframework */, 4C363B372574EFE500D99868 /* OpenSSL.xcframework */, - 4CCF87EC23E8273900C8CDB1 /* libtari_wallet_ffi.a */, 371189722488FF11004D0CE3 /* CloudKit.framework */, 0070A7BB2379847100C25E1C /* LocalAuthentication.framework */, F28D9135828811CA84244432 /* Pods_MobileWallet.framework */, @@ -1510,7 +1604,6 @@ 4CDEC322273A5E3500999DCB /* Balance.swift */, 3A8005CE28EAF9380022A38A /* BaseNodeConnectivityStatus.swift */, 00BBD80D237DA07400EBF5E6 /* ByteVector.swift */, - 00B084BF249E471800F7B9BD /* EmojiSet.swift */, 3A8005C728EAF1AB0022A38A /* RestoreWalletStatus.swift */, 3A6F3FF7283BF980005D1793 /* TariFeePerGramStats.swift */, 3A78860D2872FB39003B1F9A /* TariVectorWrapper.swift */, @@ -1548,6 +1641,9 @@ 3A35413526A738D4002AB5A8 /* RoundedInputField.swift */, 3A35413D26A73939002AB5A8 /* RoundedTextView.swift */, 3AFAEBF126B15A3300B82603 /* KeyboardAvoidingContentView.swift */, + 5421551329A4AE92000A3F49 /* SearchTextField.swift */, + 5455969729B08D7B00D6719E /* FormTextField.swift */, + 54394EDD29CA133400E7CAEA /* LoadingImageView.swift */, ); path = Views; sourceTree = ""; @@ -1570,7 +1666,8 @@ isa = PBXGroup; children = ( 3A39AF1B27B1570F00A32F46 /* SettingsViewFooter.swift */, - 3A3D70BA291A637100A8CD7A /* MenuTableView.swift */, + 3A3D70BA291A637100A8CD7A /* BaseMenuTableView.swift */, + 540CB6ED29C1D519003FACEF /* SettingsProfileCell.swift */, ); path = Views; sourceTree = ""; @@ -2288,12 +2385,43 @@ isa = PBXGroup; children = ( 3A874036278842E700D80823 /* Core */, - 4C2C95B723795930005058AB /* wallet.h */, 006D211B23CEDEEE007D1C10 /* Utils */, ); path = TariLib; sourceTree = ""; }; + 540CB6EF29C1DDBF003FACEF /* Add Contact */ = { + isa = PBXGroup; + children = ( + 540CB6F029C1DDCC003FACEF /* AddContactModel.swift */, + 540CB6F229C1DE26003FACEF /* AddContactViewController.swift */, + 540CB6F429C1DE42003FACEF /* AddContactView.swift */, + 540CB6F629C1DE83003FACEF /* AddContactConstructor.swift */, + ); + path = "Add Contact"; + sourceTree = ""; + }; + 5421550E29A4AE28000A3F49 /* Contact List */ = { + isa = PBXGroup; + children = ( + 5421550F29A4AE37000A3F49 /* ContactBookContactListViewController.swift */, + 5421551129A4AE4B000A3F49 /* ContactBookContactListView.swift */, + 54B854B829BF993600A2367A /* ContactBookListPlaceholder.swift */, + ); + path = "Contact List"; + sourceTree = ""; + }; + 5430B97529B73FF600C80AA2 /* Link Contacts */ = { + isa = PBXGroup; + children = ( + 5430B97629B7400900C80AA2 /* LinkContactsModel.swift */, + 5430B97829B7401200C80AA2 /* LinkContactsViewController.swift */, + 5430B97A29B7401C00C80AA2 /* LinkContactsView.swift */, + 5430B97C29B7402600C80AA2 /* LinkContactsConstructor.swift */, + ); + path = "Link Contacts"; + sourceTree = ""; + }; 543DF198293F37DB0031EA70 /* Bridges */ = { isa = PBXGroup; children = ( @@ -2327,6 +2455,26 @@ path = "Unblinded Outputs"; sourceTree = ""; }; + 5453241829A60C60009281A7 /* Pager */ = { + isa = PBXGroup; + children = ( + 5453241929A60C6E009281A7 /* TariPagerViewController.swift */, + 5453241B29A60CA0009281A7 /* TariPagerView.swift */, + 5453241F29A68310009281A7 /* PageToolbarView.swift */, + ); + path = Pager; + sourceTree = ""; + }; + 5455969929B0A67400D6719E /* Managers */ = { + isa = PBXGroup; + children = ( + 5455969A29B0A6B500D6719E /* InternalContactsManager.swift */, + 5455969C29B0A73300D6719E /* ExternalContactsManager.swift */, + 544692A329B6059C0081085D /* ContactsManager.swift */, + ); + path = Managers; + sourceTree = ""; + }; 545A9D12294F6EBD008D24A6 /* Theme Settings */ = { isa = PBXGroup; children = ( @@ -2357,6 +2505,31 @@ path = "User Settings"; sourceTree = ""; }; + 5460258329A74D3600CF5764 /* List */ = { + isa = PBXGroup; + children = ( + 5421550E29A4AE28000A3F49 /* Contact List */, + 54B7F4432996582B00BB484B /* Views */, + 54D419B22995092F00D496B4 /* ContactBookModel.swift */, + 54D419B42995093800D496B4 /* ContactBookViewController.swift */, + 54D419B62995094100D496B4 /* ContactBookView.swift */, + 54D419B82995094B00D496B4 /* ContactBookConstructor.swift */, + ); + path = List; + sourceTree = ""; + }; + 5460258429A74D6C00CF5764 /* Contact Details */ = { + isa = PBXGroup; + children = ( + 546CE7F729AF522800264699 /* Edit Form */, + 5460258529A74D8200CF5764 /* ContactDetailsModel.swift */, + 5460258729A74DA200CF5764 /* ContactDetailsViewController.swift */, + 5460258929A74DAB00CF5764 /* ContactDetailsView.swift */, + 5460258B29A74DC100CF5764 /* ContactDetailsConstructor.swift */, + ); + path = "Contact Details"; + sourceTree = ""; + }; 546B03212983B46E00DBED8E /* Labels */ = { isa = PBXGroup; children = ( @@ -2375,6 +2548,66 @@ path = Navigation; sourceTree = ""; }; + 546CE7F229AF50F300264699 /* Keyboard Form */ = { + isa = PBXGroup; + children = ( + 546CE7F329AF514A00264699 /* FormOverlay.swift */, + 546CE7F529AF517900264699 /* FormOverlayView.swift */, + ); + path = "Keyboard Form"; + sourceTree = ""; + }; + 546CE7F729AF522800264699 /* Edit Form */ = { + isa = PBXGroup; + children = ( + 546CE7F829AF523F00264699 /* ContactBookFormView.swift */, + ); + path = "Edit Form"; + sourceTree = ""; + }; + 547A85D129ACF2C800D94985 /* Menu */ = { + isa = PBXGroup; + children = ( + 547A85D429AD23D800D94985 /* MenuTableView.swift */, + 547A85D229ACF2D100D94985 /* MenuCell.swift */, + 547A85D629AD248400D94985 /* MenuTableHeaderView.swift */, + ); + path = Menu; + sourceTree = ""; + }; + 54909AAD29C05805002D1070 /* Extensions */ = { + isa = PBXGroup; + children = ( + 54909AAE29C05822002D1070 /* ContactType+Data.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 54B7F4432996582B00BB484B /* Views */ = { + isa = PBXGroup; + children = ( + 54B7F4442996583800BB484B /* ContactBookCell.swift */, + 54DDB18029A366700021DFD4 /* ContactCapsuleMenu.swift */, + 54DDB18229A373C90021DFD4 /* ContactBookMenuButton.swift */, + 5453241D29A67FFF009281A7 /* ContactCapsuleMenuBackground.swift */, + ); + path = Views; + sourceTree = ""; + }; + 54D419B12995091B00D496B4 /* Contact Book */ = { + isa = PBXGroup; + children = ( + 540CB6EF29C1DDBF003FACEF /* Add Contact */, + 54909AAD29C05805002D1070 /* Extensions */, + 5430B97529B73FF600C80AA2 /* Link Contacts */, + 5455969929B0A67400D6719E /* Managers */, + 5460258429A74D6C00CF5764 /* Contact Details */, + 5460258329A74D3600CF5764 /* List */, + 54CB354329B905AA00551D5A /* PopPresenter+ContactBook.swift */, + ); + path = "Contact Book"; + sourceTree = ""; + }; 54DE32CE2930BE170060108A /* Theme */ = { isa = PBXGroup; children = ( @@ -2606,7 +2839,6 @@ BF5537B92412B9BB0071328C /* purple_orb.mp4 in Resources */, 00A6651F2437AD9A0046E730 /* SplashAnimation.json in Resources */, BF0A779C241F904D00861A3E /* faceId@3x.png in Resources */, - 3A3E8BF12924F84300490E57 /* Constants.plist in Resources */, BF191A2923E5AFA200D33C85 /* img_0.png in Resources */, 0048AF43245ADCA300E1A359 /* env.json in Resources */, 00E491A82366E08F007B332D /* Assets.xcassets in Resources */, @@ -2620,6 +2852,7 @@ BF0A77A0241F904D00861A3E /* faceId.png in Resources */, 37C8BA352480F59B005BBC05 /* WaveEmojiAnimation.json in Resources */, BF0A77C42420DA3200861A3E /* TouchIdAnimation.json in Resources */, + 54F23DE729BBA0C3001E39A2 /* InfoPlist.strings in Resources */, BF191A2023E5AFA200D33C85 /* img_1.png in Resources */, BF0A77B52420CF1200861A3E /* NotificationAnimation.json in Resources */, BFE1FC0223E229FF00BA5EEC /* CheckMark.json in Resources */, @@ -2711,10 +2944,14 @@ 3A51B1AD28F00A4F0094FDD6 /* Logger.swift in Sources */, 3A7DABA028FDC9F3002CC013 /* LogConstructor.swift in Sources */, 3A7DAB9A28FDC972002CC013 /* LogViewController.swift in Sources */, + 540CB6EE29C1D519003FACEF /* SettingsProfileCell.swift in Sources */, 3AE885082837857B0070D1AC /* CurrencyLabelView.swift in Sources */, + 54394EDE29CA133400E7CAEA /* LoadingImageView.swift in Sources */, + 5460258C29A74DC100CF5764 /* ContactDetailsConstructor.swift in Sources */, 3A90653A29001D830084EE66 /* AppValues.swift in Sources */, 3A7E06DC287831D800D15190 /* PopUpCombineUTXOsConfirmationContentView.swift in Sources */, 3A6CD3B5279194980034DF81 /* RequestTariAmountView.swift in Sources */, + 5421551429A4AE92000A3F49 /* SearchTextField.swift in Sources */, 3A72DC3D26DFA02900E0BC43 /* NetworkManager.swift in Sources */, 0087A1A923F4235400B89EE7 /* ScanViewController.swift in Sources */, 3AE1D58227E0A8C8001A46C4 /* TransactionDetailsConstructor.swift in Sources */, @@ -2728,7 +2965,6 @@ 54DE32D22930BE7F0060108A /* ThemeCoordinator.swift in Sources */, 001F6CE42380253B00FA7002 /* Contact.swift in Sources */, 3AF97E6927CF76C800FF6A3F /* DeeplinkHandler.swift in Sources */, - 00325ECA239E4A1000F76918 /* FadedOverlayView.swift in Sources */, 00DD160F241CD41D00C955B4 /* BaseNode.swift in Sources */, 3AF97E6527CF767D00FF6A3F /* TransactionsSendDeeplink.swift in Sources */, 3A35413626A738D4002AB5A8 /* RoundedInputField.swift in Sources */, @@ -2748,9 +2984,9 @@ 54AA5D5A298122B70031A396 /* OnboardingView.swift in Sources */, 5443954629800F1600786682 /* OnboardingViewController.swift in Sources */, 3A0124A527B3EDEB00A481F4 /* TransactionViewControllable.swift in Sources */, + 547A85D529AD23D800D94985 /* MenuTableView.swift in Sources */, 3AF51819270D7EFC00746884 /* AddRecipientModel.swift in Sources */, 37ABB69424781F8600F08163 /* UILabelWithPadding.swift in Sources */, - 0074619B239A57B000F00966 /* UIBarButtonItem.swift in Sources */, 3A6A033C2802DD34000432B4 /* PopUpPresenter.swift in Sources */, 3ABBBA8028508AB000A3108D /* UTXOsWalletTextListViewCell.swift in Sources */, 3A8473CA28EC56180015E63A /* TariFeesService.swift in Sources */, @@ -2762,7 +2998,9 @@ 00B4D6FA241B910200ED8318 /* NotificationManager.swift in Sources */, 3AE88506283785100070D1AC /* PopUpModifyFeeContentView.swift in Sources */, 3ACDA85E2721794800F08C70 /* YatTransactionModel.swift in Sources */, + 54DDB18129A366700021DFD4 /* ContactCapsuleMenu.swift in Sources */, 3A21FC5328FFDD10004B09A0 /* UITableViewDiffableDataSource+Update.swift in Sources */, + 5453242429A68D14009281A7 /* TariGradientView.swift in Sources */, 37B48A8324B3968F00F8A8D2 /* AppKeychainWrapper.swift in Sources */, 54DEF9F32987DA8700C4B749 /* UIScreen+Tools.swift in Sources */, 00BBD801237C5B5200EBF5E6 /* CommandLineArgs.swift in Sources */, @@ -2790,9 +3028,11 @@ 3A983A6027C67C0500F0AB61 /* VerifySeedWordsConstructor.swift in Sources */, 0012562F2371D81500A9C067 /* SplashViewController.swift in Sources */, 3A70434128E21D7A00207D6F /* PendingInboundTransaction.swift in Sources */, + 5421551229A4AE4B000A3F49 /* ContactBookContactListView.swift in Sources */, 3A2F30B428F5940A0095E25D /* ConsoleLogger.swift in Sources */, 545A9D1F294F9323008D24A6 /* RadioButtonView.swift in Sources */, 3ABF91A9283FFA790001C766 /* AboutModel.swift in Sources */, + 5455969829B08D7B00D6719E /* FormTextField.swift in Sources */, 3A35413126A72DEE002AB5A8 /* SelectBaseNodeCell.swift in Sources */, 3A8473D228EC568A0015E63A /* TariMessageSignService.swift in Sources */, 543DF19C293F3DA90031EA70 /* BridgesConfigurationFooterView.swift in Sources */, @@ -2827,16 +3067,19 @@ 3A20A6B82722EE00002B15DB /* VideoView.swift in Sources */, 3A530BD6290AFB6600C423C7 /* ICloudDocsDownloadService.swift in Sources */, 001F6CE02380198D00FA7002 /* Contacts.swift in Sources */, + 547A85D729AD248400D94985 /* MenuTableHeaderView.swift in Sources */, 0072BF24247E735700BD28FB /* ReminderNotifications.swift in Sources */, 3A8473C628EC55DB0015E63A /* TariContactsService.swift in Sources */, 3A2F30B628F594520095E25D /* FileLogger.swift in Sources */, 3A8473D028EC56600015E63A /* TariRecoveryService.swift in Sources */, + 5421551029A4AE37000A3F49 /* ContactBookContactListViewController.swift in Sources */, 3AABFC9D28F59B2200D87773 /* StatusLoggerManager.swift in Sources */, 00665E7524E422C500030A13 /* CustomTabBar.swift in Sources */, 3A0EA97E26AA8E3C002612D4 /* TokenView.swift in Sources */, 3A74E7DB273BCFF000F290D2 /* SeedWordsMnemonicWordList.swift in Sources */, 54A6E9F4297F260800A60853 /* UIViewController+Common.swift in Sources */, 3A8CC6C2290BDC70008161DC /* BugReportService.swift in Sources */, + 5453241C29A60CA0009281A7 /* TariPagerView.swift in Sources */, 3A47ABCD27478208007F351B /* Tari.swift in Sources */, 3A8A03EB28F587FD003EC8FE /* AppConfigurator.swift in Sources */, 00BBD80E237DA07400EBF5E6 /* ByteVector.swift in Sources */, @@ -2850,6 +3093,7 @@ 00E2BCA9236AB28200C2A105 /* TxTableViewCell.swift in Sources */, 3708D756247FF81900807D72 /* SettingsParentViewController.swift in Sources */, 00E491A62366E08B007B332D /* MobileWallet.xcdatamodeld in Sources */, + 5460258A29A74DAB00CF5764 /* ContactDetailsView.swift in Sources */, 545A9D18294F6F50008D24A6 /* ThemeSettingsModel.swift in Sources */, 3ABF91A7283FFA6E0001C766 /* AboutView.swift in Sources */, 3A8005C828EAF1AB0022A38A /* RestoreWalletStatus.swift in Sources */, @@ -2870,11 +3114,14 @@ 3A0391E6290BA40E00352D73 /* BugReportingView.swift in Sources */, 546B032A2983F33600DBED8E /* OnboardingPagerView.swift in Sources */, 001F6CDC238011CA00FA7002 /* PublicKey.swift in Sources */, + 5453242029A68310009281A7 /* PageToolbarView.swift in Sources */, 3A860BD2287DA2B800FC4F76 /* HomeViewModel.swift in Sources */, 370E886324FE974100576F61 /* OnionSettings.swift in Sources */, 545A9D16294F6EEA008D24A6 /* ThemeSettingsView.swift in Sources */, 0087A1B223F4235400B89EE7 /* AddRecipientViewController.swift in Sources */, + 544692A429B6059C0081085D /* ContactsManager.swift in Sources */, 378EE9A3250126BE009615B5 /* CustomBridgesViewController.swift in Sources */, + 54B854B929BF993600A2367A /* ContactBookListPlaceholder.swift in Sources */, 0053873024065F6C00901A68 /* SlideView.swift in Sources */, 3AE138CB28044B1E00443D34 /* CustomDeeplinkPopUpContentView.swift in Sources */, 3A2F30B828F5946B0095E25D /* CrashLogger.swift in Sources */, @@ -2884,14 +3131,15 @@ 3AC6F11428E9D8840068E6FF /* CustomBridgesHandable.swift in Sources */, 00E4919A2366E08B007B332D /* AppDelegate.swift in Sources */, 3A62674626D76E6B007F9895 /* SelectNetworkViewController.swift in Sources */, - 00B084C0249E471800F7B9BD /* EmojiSet.swift in Sources */, 3AA00E97271D5ECA00977C36 /* AddRecipientSectionHeaderView.swift in Sources */, 377C951E2522213800CC1696 /* TxsListViewController.swift in Sources */, 3AEDBE4F27CE46D6006B0166 /* DeeplinkDataDecoder.swift in Sources */, 3AD1306A27D62918003EC7FA /* SeedWordsListView.swift in Sources */, + 54D419B32995092F00D496B4 /* ContactBookModel.swift in Sources */, 3A66109627AD07F10038EB5B /* SendingTariView.swift in Sources */, 3A4376E9269C0C58006107B0 /* RestoreWalletFromSeedsModel.swift in Sources */, 3ADF96CE2858A93000A3C888 /* ContextualButton.swift in Sources */, + 546CE7F929AF523F00264699 /* ContactBookFormView.swift in Sources */, 3723A7A624AC9389003382EB /* SecureBackupViewController.swift in Sources */, 37875E4824D8787A00C0595B /* TxTableViewModel.swift in Sources */, 3AF97E6727CF769A00FF6A3F /* BaseNodesAddDeeplink.swift in Sources */, @@ -2904,6 +3152,7 @@ 37C8BA4F24813F98005BBC05 /* SettingsParentTableViewController.swift in Sources */, 3A8473C828EC55F50015E63A /* TariValidationService.swift in Sources */, 3AE8850C28378BF20070D1AC /* TariSegmentedControl.swift in Sources */, + 54D419B72995094100D496B4 /* ContactBookView.swift in Sources */, 37E4B62B24501FB200FA302B /* HomeViewController.swift in Sources */, 3A87C3B527A94086007A553F /* CoreError.swift in Sources */, 3723A7AB24ACD03E003382EB /* PasswordField.swift in Sources */, @@ -2916,13 +3165,18 @@ 3A7DAB9C28FDC9D8002CC013 /* LogView.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 */, + 54CB354429B905AA00551D5A /* PopPresenter+ContactBook.swift in Sources */, 3A4CE32C26A18D7300ECF460 /* UserDefault.swift in Sources */, 3A1D8E4B28400AB8004129D5 /* AboutViewHeader.swift in Sources */, 54A95B3C29747C19003CCE5C /* TariUnspentOutputsService.swift in Sources */, 3AD1306827D6290A003EC7FA /* SeedWordsListViewController.swift in Sources */, 3AF56BBB28B774C0004E8E43 /* PendingInboundTransactions.swift in Sources */, + 547A85D329ACF2D100D94985 /* MenuCell.swift in Sources */, A0779C612552C1AF00614EF3 /* DeleteWalletViewController.swift in Sources */, 3ADA05F127A41D44007F5677 /* PointerHandler.swift in Sources */, 3A890EC929262744000F5DA6 /* TariAddress.swift in Sources */, @@ -2937,19 +3191,24 @@ 370E886124FE7AE800576F61 /* BridgesConfigurationViewController.swift in Sources */, 3A983A5A27C62A3900F0AB61 /* VerifySeedWordsViewController.swift in Sources */, 54A6E9F2297F1FBA00A60853 /* PopUpStagedWalletSecurityHeaderView.swift in Sources */, + 54D419B52995093800D496B4 /* ContactBookViewController.swift in Sources */, 00B4D6F8241B77A900ED8318 /* ScheduleReminderNotificationsOperation.swift in Sources */, 3A6A03342802DBB5000432B4 /* PopUpHeaderView.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 */, + 540CB6F129C1DDCC003FACEF /* AddContactModel.swift in Sources */, 3AC877E628741680006F327B /* UTXOsWalletLoadingView.swift in Sources */, 37547D5624601BF600EB59CC /* UIView+GlobalFrame.swift in Sources */, BF8316FD23EF7EAA00235403 /* LAContext.swift in Sources */, 3A983A5E27C64B0500F0AB61 /* VerifySeedWordsModel.swift in Sources */, 3A6A033E2802DEEB000432B4 /* PopUpPresenter+CommonPopUps.swift in Sources */, 3ADEBF5D26A54FA500E87C84 /* SelectBaseNodeModel.swift in Sources */, + 540CB6F729C1DE83003FACEF /* AddContactConstructor.swift in Sources */, 3A8005CB28EAF3A90022A38A /* TransactionValidationData.swift in Sources */, 005387242405136700901A68 /* AddNoteViewController.swift in Sources */, 3AE9F4E42795A260006101D1 /* RequestTariAmountModel.swift in Sources */, @@ -2969,6 +3228,7 @@ 3A2B956A28648D860085BE21 /* CChar+Helpers.swift in Sources */, 3A0EA97A26AA8E1C002612D4 /* TokenCollectionView.swift in Sources */, 546B03282983F2F700DBED8E /* OnboardingPagerElementView.swift in Sources */, + 5453242629A68D7D009281A7 /* RoundedAvatarView.swift in Sources */, 37049274247EA9110034EE5D /* SystemMenuTableViewCell.swift in Sources */, 3A96B9452733E9A90040E59F /* TokensToolbar.swift in Sources */, 00D988692449B04B000FDC9C /* AnimatedRefreshingView.swift in Sources */, @@ -2984,9 +3244,13 @@ 37547D522460165500EB59CC /* UIApplication+KeyWindow.swift in Sources */, 3A87C3B727A9409E007A553F /* WalletError.swift in Sources */, 00A994332396434B007D9990 /* PulseButton.swift in Sources */, + 5460258629A74D8200CF5764 /* ContactDetailsModel.swift in Sources */, 545A9D21294F9CCE008D24A6 /* UserSettings.swift in Sources */, + 5453241E29A67FFF009281A7 /* ContactCapsuleMenuBackground.swift in Sources */, 3A237C7B27D8D0F7005FA6AB /* SeedWordsListModel.swift in Sources */, 3A84CDD42846088D005F2F8D /* UTXOsWalletView.swift in Sources */, + 5430B97D29B7402600C80AA2 /* LinkContactsConstructor.swift in Sources */, + 5453242229A68CA0009281A7 /* RoundedButton.swift in Sources */, 3A2C0B8727DA22970018C5A8 /* SeedWordListElementView.swift in Sources */, 3A8473D628EC56C90015E63A /* TariBalanceService.swift in Sources */, 3A6F3FF8283BF980005D1793 /* TariFeePerGramStats.swift in Sources */, @@ -2999,25 +3263,30 @@ 3ADEBF6626A5B5A500E87C84 /* AddBaseNodeView.swift in Sources */, 3AF985FA286B233100290387 /* PopUpSelectionView.swift in Sources */, 375DB1E0246E90D100B2BEF4 /* NavigationBar.swift in Sources */, + 5455969D29B0A73300D6719E /* ExternalContactsManager.swift in Sources */, 3A0391E4290BA37C00352D73 /* BugReportingViewController.swift in Sources */, 3A530BD8290AFBA000C423C7 /* ICloudBackupMetadataQuery.swift in Sources */, 3A0391EA290BA47300352D73 /* BugReportingConstructor.swift in Sources */, 3A70433D28E21CFB00207D6F /* CompletedTransaction.swift in Sources */, 3A72DC4526DFA26000E0BC43 /* TariNetwork.swift in Sources */, 004277F723E0628A00AE7BD9 /* UIViewController+Content.swift in Sources */, + 5430B97729B7400900C80AA2 /* LinkContactsModel.swift in Sources */, 54AA5D5C2981267C0031A396 /* OnboardingPageViewController.swift in Sources */, 3A2B95682864801D0085BE21 /* UTXOsWalletTileListLayout.swift in Sources */, 373CCDC52490F3B600D0A2C9 /* FileManager+SecureCopy.swift in Sources */, 3ACFA429278EC9BF00EBED98 /* ProfileView.swift in Sources */, 0087A1AF23F4235400B89EE7 /* ContactCell.swift in Sources */, + 540CB6F329C1DE26003FACEF /* AddContactViewController.swift in Sources */, 370E887224FEA32600576F61 /* Ipv6Tester.swift in Sources */, 543DF19A293F37E70031EA70 /* CustomBridgesHeaderView.swift in Sources */, + 5455969B29B0A6B500D6719E /* InternalContactsManager.swift in Sources */, 49996E1923F164BA002B6696 /* AnimatedBalanceLabel.swift in Sources */, 54A6E9F0297F1F9900A60853 /* StagedWalletSecurityManager.swift in Sources */, 3A7DAB9328FDB18A002CC013 /* LogsListModel.swift in Sources */, 3AEE7AA3286599E6000F84ED /* UTXOsWalletTopBar.swift in Sources */, 3AABFC9B28F5960100D87773 /* LogFormatter.swift in Sources */, 491AB8A023F3FF4400372189 /* AddAmountViewController.swift in Sources */, + 5460258829A74DA200CF5764 /* ContactDetailsViewController.swift in Sources */, 3A42059A279804B300A8D49C /* QRCodeFactory.swift in Sources */, 3ACFDD1D26E8C1A900C5E1EA /* AppInfo.swift in Sources */, 3A983A5C27C62A5700F0AB61 /* VerifySeedWordsView.swift in Sources */, @@ -3025,7 +3294,7 @@ 3AA2717A279028BF0076E51F /* RequestTariAmountViewController.swift in Sources */, 3A5A3D5C290178EB00B689C6 /* BackupWalletSettingsModel.swift in Sources */, 3AE5E5662874656200D3AF85 /* PopUpUTXOsBreakContentView.swift in Sources */, - 00230B0923770CEA00F23E34 /* CALayer.swift in Sources */, + 54B7F4452996583800BB484B /* ContactBookCell.swift in Sources */, 3A4BF7D027B57CE400CA499D /* SendingTariConstructor.swift in Sources */, 3A82455C27E1EEE3003B6B59 /* TransactionDetailsNoteView.swift in Sources */, 3A82455A27E1EE93003B6B59 /* TransactionDetailsContactView.swift in Sources */, @@ -3034,6 +3303,7 @@ 3A70434828E21F5600207D6F /* Transaction.swift in Sources */, 3A8473BF28EC51780015E63A /* TransactionValidationStatus.swift in Sources */, 37B444A9248949B800592D92 /* Checkbox.swift in Sources */, + 546CE7F629AF517900264699 /* FormOverlayView.swift in Sources */, BFF1ED9D2408111000CC9EF6 /* SendingTariViewController.swift in Sources */, 3AC877E4287415AD006F327B /* UTXOsWalletPlaceholderView.swift in Sources */, 3ABC7E9528FECFB200EAC852 /* UIViewController+DebugMenu.swift in Sources */, @@ -3052,9 +3322,12 @@ 3A4205922798001A00A8D49C /* AmountKeyboardView.swift in Sources */, 3A03A602288962FA00788A02 /* SplashViewModel.swift in Sources */, 3A312FEE28B6316D00A290D3 /* CompletedTransactions.swift in Sources */, + 54DDB18329A373C90021DFD4 /* ContactBookMenuButton.swift in Sources */, 00E2BCB2236B455900C2A105 /* HomeViewFloatingPanelDelegates.swift in Sources */, 3ADF96CC28585E2400A3C888 /* ContextualButtonsOverlay.swift in Sources */, 37ABB69024781CE800F08163 /* UILabel.swift in Sources */, + 54E1CB1E29C7292700E6777C /* EmojiTextField.swift in Sources */, + 54DD2E8629C37A7600C8C0D9 /* UINavigationController+Common.swift in Sources */, 3AA2DD962796F72100DC3CF7 /* QRCodePresentationView.swift in Sources */, 3AFAC360271C6B4D008AA842 /* ContactAvatarView.swift in Sources */, 3A8826C527F1B8320037F779 /* UICollectionViewDiffableDataSource+Animation.swift in Sources */, @@ -3064,9 +3337,11 @@ 3A6A03382802DC85000432B4 /* PopUpButtonsView.swift in Sources */, 376C62AE247574850091BB28 /* Animation+EnumInit.swift in Sources */, 3ABBBA7C2850771C00A3108D /* UTXOsWalletTextListView.swift in Sources */, + 5430B97B29B7401C00C80AA2 /* LinkContactsView.swift in Sources */, 3ACDA70526B031A400F138B8 /* SeedWordsRecoveryProgressViewController.swift in Sources */, 545A9D24294F9E77008D24A6 /* UserSettingsManager.swift in Sources */, - 3A3D70BB291A637100A8CD7A /* MenuTableView.swift in Sources */, + 546CE7F429AF514A00264699 /* FormOverlay.swift in Sources */, + 3A3D70BB291A637100A8CD7A /* BaseMenuTableView.swift in Sources */, 3A21FC5128FFD7B8004B09A0 /* PopUpTableView.swift in Sources */, 544D9D4C296D9B2000D8ECEF /* UnblindedOutputs.swift in Sources */, BFAB5D1123FDEA69009E8563 /* EmojiIdView.swift in Sources */, @@ -3077,6 +3352,7 @@ 37BB696B245705D20013AC4D /* RadialGradientView.swift in Sources */, 3A7E06DA287831BD00D15190 /* UTXOsEstimationLabel.swift in Sources */, 0042780323E8421200AE7BD9 /* String.swift in Sources */, + 540CB6F529C1DE42003FACEF /* AddContactView.swift in Sources */, 3A793BF3280354830094DF23 /* PopUpHeaderWithSubtitle.swift in Sources */, 3A3E7AD6284E03140065F3C0 /* UTXOTileView.swift in Sources */, 3A420594279802E500A8D49C /* BaseButton.swift in Sources */, @@ -3280,7 +3556,7 @@ "$(inherited)", "$(PROJECT_DIR)/MobileWallet/TariLib", ); - MARKETING_VERSION = 0.19.0; + MARKETING_VERSION = 0.20.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3315,7 +3591,7 @@ "$(inherited)", "$(PROJECT_DIR)/MobileWallet/TariLib", ); - MARKETING_VERSION = 0.19.0; + MARKETING_VERSION = 0.20.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/MobileWallet/Assets.xcassets/Assets-Icons/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Assets-Icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/icon-about.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-about.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-about.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-about.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-about.imageset/ico-about.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-about.imageset/ico-about.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-about.imageset/ico-about.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-about.imageset/ico-about.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-base-node.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-base-node.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-base-node.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-base-node.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-base-node.imageset/ico-base-node.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-base-node.imageset/ico-base-node.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-base-node.imageset/ico-base-node.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-base-node.imageset/ico-base-node.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-block-explorer.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-block-explorer.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-block-explorer.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-block-explorer.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-block-explorer.imageset/ico-block-explorer.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-block-explorer.imageset/ico-block-explorer.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-block-explorer.imageset/ico-block-explorer.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-block-explorer.imageset/ico-block-explorer.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-blockview.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-blockview.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-blockview.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-blockview.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-blockview.imageset/ico-blockview.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-blockview.imageset/ico-blockview.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-blockview.imageset/ico-blockview.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-blockview.imageset/ico-blockview.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-bridge-config.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-bridge-config.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-bridge-config.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-bridge-config.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-bridge-config.imageset/ico-bridge-config.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-bridge-config.imageset/ico-bridge-config.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-bridge-config.imageset/ico-bridge-config.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-bridge-config.imageset/ico-bridge-config.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-contribute.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-contribute.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-contribute.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-contribute.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-contribute.imageset/ico-contribute.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-contribute.imageset/ico-contribute.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-contribute.imageset/ico-contribute.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-contribute.imageset/ico-contribute.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-delete.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-delete.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-delete.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-delete.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-delete.imageset/ico-delete.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-delete.imageset/ico-delete.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-delete.imageset/ico-delete.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-delete.imageset/ico-delete.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-disclaimer.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-disclaimer.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-disclaimer.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-disclaimer.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-disclaimer.imageset/ico-disclaimer.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-disclaimer.imageset/ico-disclaimer.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-disclaimer.imageset/ico-disclaimer.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-disclaimer.imageset/ico-disclaimer.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-faucet.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-faucet.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-faucet.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-faucet.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-faucet.imageset/ico-filter.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-faucet.imageset/ico-filter.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-faucet.imageset/ico-filter.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-faucet.imageset/ico-filter.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-hourglass.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-hourglass.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-hourglass.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-hourglass.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-hourglass.imageset/ico-hourglass.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-hourglass.imageset/ico-hourglass.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-hourglass.imageset/ico-hourglass.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-hourglass.imageset/ico-hourglass.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-internet.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-internet.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-internet.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-internet.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-internet.imageset/icon-internet.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-internet.imageset/icon-internet.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-internet.imageset/icon-internet.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-internet.imageset/icon-internet.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-join-split.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-join-split.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-join-split.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-join-split.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-join-split.imageset/ico-join-split.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-join-split.imageset/ico-join-split.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-join-split.imageset/ico-join-split.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-join-split.imageset/ico-join-split.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-join.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-join.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-join.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-join.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-join.imageset/ico-join.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-join.imageset/ico-join.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-join.imageset/ico-join.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-join.imageset/ico-join.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-listview.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-listview.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-listview.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-listview.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-listview.imageset/ico-listview.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-listview.imageset/ico-listview.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-listview.imageset/ico-listview.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-listview.imageset/ico-listview.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-network.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-network.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-network.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-network.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-network.imageset/ico-network.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-network.imageset/ico-network.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-network.imageset/ico-network.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-network.imageset/ico-network.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-privacy-policy.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-privacy-policy.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-privacy-policy.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-privacy-policy.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-privacy-policy.imageset/ico-privacy-policy.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-privacy-policy.imageset/ico-privacy-policy.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-privacy-policy.imageset/ico-privacy-policy.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-privacy-policy.imageset/ico-privacy-policy.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-report-bug.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-report-bug.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-report-bug.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-report-bug.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-report-bug.imageset/ico-report-bug.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-report-bug.imageset/ico-report-bug.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-report-bug.imageset/ico-report-bug.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-report-bug.imageset/ico-report-bug.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-split.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-split.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-split.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-split.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-split.imageset/ico-split.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-split.imageset/ico-split.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-split.imageset/ico-split.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-split.imageset/ico-split.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-sync.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-sync.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-sync.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-sync.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-sync.imageset/icon-sync.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-sync.imageset/icon-sync.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-sync.imageset/icon-sync.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-sync.imageset/icon-sync.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-theme.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-theme.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-theme.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-theme.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-theme.imageset/icon-theme.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-theme.imageset/icon-theme.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-theme.imageset/icon-theme.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-theme.imageset/icon-theme.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-tor.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-tor.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-tor.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-tor.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-tor.imageset/icon-tor.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-tor.imageset/icon-tor.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-tor.imageset/icon-tor.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-tor.imageset/icon-tor.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-user-agreement.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-user-agreement.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-user-agreement.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-user-agreement.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-user-agreement.imageset/ico-user-agreement.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-user-agreement.imageset/ico-user-agreement.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-user-agreement.imageset/ico-user-agreement.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-user-agreement.imageset/ico-user-agreement.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-visit-tari.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-visit-tari.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-visit-tari.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-visit-tari.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-visit-tari.imageset/ico-visit-tari.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-visit-tari.imageset/ico-visit-tari.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-visit-tari.imageset/ico-visit-tari.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-visit-tari.imageset/ico-visit-tari.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-wallet-backups.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-wallet-backups.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-wallet-backups.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-wallet-backups.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-wallet-backups.imageset/ico-wallet-backups.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-wallet-backups.imageset/ico-wallet-backups.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-wallet-backups.imageset/ico-wallet-backups.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-wallet-backups.imageset/ico-wallet-backups.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-wallet.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-wallet.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-wallet.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-wallet.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-wallet.imageset/icon-wallet.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-wallet.imageset/icon-wallet.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-wallet.imageset/icon-wallet.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-wallet.imageset/icon-wallet.pdf diff --git a/MobileWallet/Assets.xcassets/Icons/icon-yat.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/Contents.json similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-yat.imageset/Contents.json rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/Contents.json diff --git a/MobileWallet/Assets.xcassets/Icons/icon-yat.imageset/ico-yat.pdf b/MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/ico-yat.pdf similarity index 100% rename from MobileWallet/Assets.xcassets/Icons/icon-yat.imageset/ico-yat.pdf rename to MobileWallet/Assets.xcassets/Assets-Icons/icon-yat.imageset/ico-yat.pdf diff --git a/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/Contents.json b/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/Contents.json deleted file mode 100644 index 8d4d6870..00000000 --- a/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "navProfile.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "navProfile@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "navProfile@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/navProfile.png b/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/navProfile.png deleted file mode 100644 index ff1fb4c0..00000000 Binary files a/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/navProfile.png and /dev/null differ diff --git a/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/navProfile@2x.png b/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/navProfile@2x.png deleted file mode 100644 index dbf91759..00000000 Binary files a/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/navProfile@2x.png and /dev/null differ diff --git a/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/navProfile@3x.png b/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/navProfile@3x.png deleted file mode 100644 index fe27418c..00000000 Binary files a/MobileWallet/Assets.xcassets/Assets/TabBar/navProfile.imageset/navProfile@3x.png and /dev/null differ diff --git a/MobileWallet/Assets.xcassets/Icons/Contact Types/Contents.json b/MobileWallet/Assets.xcassets/Icons/Contact Types/Contents.json new file mode 100644 index 00000000..6e965652 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Contact Types/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Contact Types/External.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Contact Types/External.imageset/Contents.json new file mode 100644 index 00000000..d74127e6 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Contact Types/External.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "ico-cb-nopadding.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Contact Types/External.imageset/ico-cb-nopadding.pdf b/MobileWallet/Assets.xcassets/Icons/Contact Types/External.imageset/ico-cb-nopadding.pdf new file mode 100644 index 00000000..24ce5c47 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Contact Types/External.imageset/ico-cb-nopadding.pdf differ diff --git a/MobileWallet/Assets.xcassets/Icons/Contact Types/Internal.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Contact Types/Internal.imageset/Contents.json new file mode 100644 index 00000000..404bcae1 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Contact Types/Internal.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "ico-tari-nopadding.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Contact Types/Internal.imageset/ico-tari-nopadding.pdf b/MobileWallet/Assets.xcassets/Icons/Contact Types/Internal.imageset/ico-tari-nopadding.pdf new file mode 100644 index 00000000..978bdfbd Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Contact Types/Internal.imageset/ico-tari-nopadding.pdf differ diff --git a/MobileWallet/Assets.xcassets/Icons/Contact Types/Linked.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Contact Types/Linked.imageset/Contents.json new file mode 100644 index 00000000..12193dd5 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Contact Types/Linked.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "ico-link-nopadding.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Contact Types/Linked.imageset/ico-link-nopadding.pdf b/MobileWallet/Assets.xcassets/Icons/Contact Types/Linked.imageset/ico-link-nopadding.pdf new file mode 100644 index 00000000..1d7a921d Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Contact Types/Linked.imageset/ico-link-nopadding.pdf differ diff --git a/MobileWallet/Assets.xcassets/Icons/Contents.json b/MobileWallet/Assets.xcassets/Icons/Contents.json index 73c00596..6e965652 100644 --- a/MobileWallet/Assets.xcassets/Icons/Contents.json +++ b/MobileWallet/Assets.xcassets/Icons/Contents.json @@ -2,5 +2,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "provides-namespace" : true } } diff --git a/MobileWallet/Assets.xcassets/Icons/Link.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Link.imageset/Contents.json new file mode 100644 index 00000000..d070df43 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Link.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "ico-link.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Link.imageset/ico-link.pdf b/MobileWallet/Assets.xcassets/Icons/Link.imageset/ico-link.pdf new file mode 100644 index 00000000..5608d851 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Link.imageset/ico-link.pdf differ diff --git a/MobileWallet/Assets.xcassets/Icons/Profile.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Profile.imageset/Contents.json new file mode 100644 index 00000000..4e2cdcb4 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Profile.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "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/Profile.imageset/ico-profile.pdf b/MobileWallet/Assets.xcassets/Icons/Profile.imageset/ico-profile.pdf new file mode 100644 index 00000000..fd047026 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Profile.imageset/ico-profile.pdf differ diff --git a/MobileWallet/Assets.xcassets/Icons/Send.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Send.imageset/Contents.json new file mode 100644 index 00000000..8f2ddc4e --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Send.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "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/Send.imageset/ico-send-receive.pdf b/MobileWallet/Assets.xcassets/Icons/Send.imageset/ico-send-receive.pdf new file mode 100644 index 00000000..7c2da928 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Send.imageset/ico-send-receive.pdf differ diff --git a/MobileWallet/Assets.xcassets/Icons/Star/Border.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Star/Border.imageset/Contents.json new file mode 100644 index 00000000..03bbe597 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Star/Border.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "ico-star.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Star/Border.imageset/ico-star.pdf b/MobileWallet/Assets.xcassets/Icons/Star/Border.imageset/ico-star.pdf new file mode 100644 index 00000000..9aea63cf Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Star/Border.imageset/ico-star.pdf differ diff --git a/MobileWallet/Assets.xcassets/Icons/Star/Contents.json b/MobileWallet/Assets.xcassets/Icons/Star/Contents.json new file mode 100644 index 00000000..6e965652 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Star/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Star/Filled.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Star/Filled.imageset/Contents.json new file mode 100644 index 00000000..433936b4 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Star/Filled.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "ico-star-filled.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Star/Filled.imageset/ico-star-filled.pdf b/MobileWallet/Assets.xcassets/Icons/Star/Filled.imageset/ico-star-filled.pdf new file mode 100644 index 00000000..68e53351 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Star/Filled.imageset/ico-star-filled.pdf differ diff --git a/MobileWallet/Assets.xcassets/Icons/TabBar/ContactBook.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/TabBar/ContactBook.imageset/Contents.json new file mode 100644 index 00000000..fe1e94eb --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/TabBar/ContactBook.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ico-contact-book.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/TabBar/ContactBook.imageset/ico-contact-book.pdf b/MobileWallet/Assets.xcassets/Icons/TabBar/ContactBook.imageset/ico-contact-book.pdf new file mode 100644 index 00000000..08e01262 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/TabBar/ContactBook.imageset/ico-contact-book.pdf differ diff --git a/MobileWallet/Assets.xcassets/Icons/TabBar/Contents.json b/MobileWallet/Assets.xcassets/Icons/TabBar/Contents.json new file mode 100644 index 00000000..6e965652 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/TabBar/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Unlink.imageset/Contents.json b/MobileWallet/Assets.xcassets/Icons/Unlink.imageset/Contents.json new file mode 100644 index 00000000..c5267e20 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Icons/Unlink.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "ico-unlink.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Icons/Unlink.imageset/ico-unlink.pdf b/MobileWallet/Assets.xcassets/Icons/Unlink.imageset/ico-unlink.pdf new file mode 100644 index 00000000..c2efbee8 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Icons/Unlink.imageset/ico-unlink.pdf differ diff --git a/MobileWallet/Assets.xcassets/Images/Contact Book/AddContact.imageset/Contents.json b/MobileWallet/Assets.xcassets/Images/Contact Book/AddContact.imageset/Contents.json new file mode 100644 index 00000000..9942eeb7 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Images/Contact Book/AddContact.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "add_contact.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Images/Contact Book/AddContact.imageset/add_contact.pdf b/MobileWallet/Assets.xcassets/Images/Contact Book/AddContact.imageset/add_contact.pdf new file mode 100644 index 00000000..5918e360 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Images/Contact Book/AddContact.imageset/add_contact.pdf differ diff --git a/MobileWallet/Assets.xcassets/Images/Contact Book/Contents.json b/MobileWallet/Assets.xcassets/Images/Contact Book/Contents.json new file mode 100644 index 00000000..6e965652 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Images/Contact Book/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListFavPlaceholder.imageset/Contents.json b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListFavPlaceholder.imageset/Contents.json new file mode 100644 index 00000000..08f9e98a --- /dev/null +++ b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListFavPlaceholder.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "ico-placeholder2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListFavPlaceholder.imageset/ico-placeholder2.pdf b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListFavPlaceholder.imageset/ico-placeholder2.pdf new file mode 100644 index 00000000..9ec263f5 Binary files /dev/null and b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListFavPlaceholder.imageset/ico-placeholder2.pdf differ diff --git a/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListPlaceholder.imageset/Contents.json b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListPlaceholder.imageset/Contents.json new file mode 100644 index 00000000..1d18c3cf --- /dev/null +++ b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListPlaceholder.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "ico-placeholder1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListPlaceholder.imageset/ico-placeholder1.pdf b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListPlaceholder.imageset/ico-placeholder1.pdf new file mode 100644 index 00000000..967629cf Binary files /dev/null and b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/ContactBookListPlaceholder.imageset/ico-placeholder1.pdf differ diff --git a/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/Contents.json b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/Contents.json new file mode 100644 index 00000000..6e965652 --- /dev/null +++ b/MobileWallet/Assets.xcassets/Images/Contact Book/Placeholders/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/MobileWallet/Assets.xcassets/background 1.imageset/Contents.json b/MobileWallet/Assets.xcassets/background 1.imageset/Contents.json deleted file mode 100644 index e95a74da..00000000 --- a/MobileWallet/Assets.xcassets/background 1.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "background.pdf", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/MobileWallet/Assets.xcassets/background 1.imageset/background.pdf b/MobileWallet/Assets.xcassets/background 1.imageset/background.pdf deleted file mode 100644 index d6f26046..00000000 Binary files a/MobileWallet/Assets.xcassets/background 1.imageset/background.pdf and /dev/null differ diff --git a/MobileWallet/Assets.xcassets/background.imageset/Contents.json b/MobileWallet/Assets.xcassets/background.imageset/Contents.json deleted file mode 100644 index e95a74da..00000000 --- a/MobileWallet/Assets.xcassets/background.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "background.pdf", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/MobileWallet/Assets.xcassets/background.imageset/background.pdf b/MobileWallet/Assets.xcassets/background.imageset/background.pdf deleted file mode 100644 index d6f26046..00000000 Binary files a/MobileWallet/Assets.xcassets/background.imageset/background.pdf and /dev/null differ diff --git a/MobileWallet/Backup/Manager/BackupFilesManager.swift b/MobileWallet/Backup/Manager/BackupFilesManager.swift index b0d16a0d..eea0e153 100644 --- a/MobileWallet/Backup/Manager/BackupFilesManager.swift +++ b/MobileWallet/Backup/Manager/BackupFilesManager.swift @@ -147,8 +147,6 @@ enum BackupFilesManager { try model.utxos .map { try UnblindedOutput(json: $0) } .forEach { _ = try Tari.shared.unspentOutputsService.store(unspentOutput: $0, sourceAddress: sourceAddress, message: localized("backup.cloud.partial.recovery_message")) } - - try MigrationManager.updateWalletVersion() } private static func recoverFullBackup(backupURL: URL, password: String) async throws { diff --git a/MobileWallet/Common/AppRouter.swift b/MobileWallet/Common/AppRouter.swift index ccf49d60..1308139d 100644 --- a/MobileWallet/Common/AppRouter.swift +++ b/MobileWallet/Common/AppRouter.swift @@ -154,4 +154,11 @@ enum AppRouter { navigationController.isModalInPresentation = true tabBar?.present(navigationController, animated: true) } + + static func presentSendTransaction(paymentInfo: PaymentInfo) { + let controller = AddAmountViewController(paymentInfo: paymentInfo, deeplink: nil) + let navigationController = AlwaysPoppableNavigationController(rootViewController: controller) + navigationController.isNavigationBarHidden = true + tabBar?.presentOnFullScreen(navigationController) + } } diff --git a/MobileWallet/Common/Extensions/LAContext.swift b/MobileWallet/Common/Extensions/LAContext.swift index ba374af0..909f88ee 100644 --- a/MobileWallet/Common/Extensions/LAContext.swift +++ b/MobileWallet/Common/Extensions/LAContext.swift @@ -86,7 +86,7 @@ extension LAContext { switch biometricType { case .faceID, .touchID, .pin: - let policy: LAPolicy = .deviceOwnerAuthentication // it is not clear why but it works like that. If you specify with biometrics for some reason (system error, can't handle that) the window for entering the password code is not called + let policy: LAPolicy = .deviceOwnerAuthentication let localizedReason = reason.rawValue evaluatePolicy(policy, localizedReason: localizedReason) { [weak self] success, error in diff --git a/MobileWallet/Common/Extensions/String.swift b/MobileWallet/Common/Extensions/String.swift index d6899420..048777a5 100644 --- a/MobileWallet/Common/Extensions/String.swift +++ b/MobileWallet/Common/Extensions/String.swift @@ -41,6 +41,17 @@ import UIKit extension String { + + 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() @@ -115,3 +126,11 @@ extension String.Index { string.distance(to: self) } } + +extension Array where Element == String.SubSequence { + + var firstString: String? { + guard let first else { return nil } + return String(first) + } +} diff --git a/MobileWallet/Common/Extensions/CALayer.swift b/MobileWallet/Common/Extensions/UINavigationController+Common.swift similarity index 65% rename from MobileWallet/Common/Extensions/CALayer.swift rename to MobileWallet/Common/Extensions/UINavigationController+Common.swift index df7a3198..9b98e5bf 100644 --- a/MobileWallet/Common/Extensions/CALayer.swift +++ b/MobileWallet/Common/Extensions/UINavigationController+Common.swift @@ -1,10 +1,10 @@ -// CALayer.swift +// UINavigationController+Common.swift /* Package MobileWallet - Created by Jason van den Berg on 2019/11/09 + Created by Adrian Truszczyński on 16/03/2023 Using Swift 5.0 - Running on macOS 10.15 + Running on macOS 13.0 Copyright 2019 The Tari Project @@ -40,23 +40,10 @@ import UIKit -extension CALayer { - func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) { - let border = CALayer() - switch edge { - case .top: - border.frame = CGRect(x: 0, y: 0, width: frame.width, height: thickness) - case .bottom: - border.frame = CGRect(x: 0, y: frame.height - thickness, width: frame.width, height: thickness) - case .left: - border.frame = CGRect(x: 0, y: 0, width: thickness, height: frame.height) - case .right: - border.frame = CGRect(x: frame.width - thickness, y: 0, width: thickness, height: frame.height) - default: - break - } - - border.backgroundColor = color.cgColor - addSublayer(border) +extension UINavigationController { + + func remove(controller: UIViewController) { + guard let index = viewControllers.firstIndex(of: controller) else { return } + viewControllers.remove(at: index) } } diff --git a/MobileWallet/Common/Extensions/UIScreen+Tools.swift b/MobileWallet/Common/Extensions/UIScreen+Tools.swift index 704b7f27..3d13d5cd 100644 --- a/MobileWallet/Common/Extensions/UIScreen+Tools.swift +++ b/MobileWallet/Common/Extensions/UIScreen+Tools.swift @@ -41,5 +41,5 @@ import UIKit extension UIScreen { - static var isSmallScreen: Bool { UIScreen.main.nativeBounds.height <= 1136.0 } + static var isSmallScreen: Bool { UIScreen.main.nativeBounds.height <= 1334.0 } } diff --git a/MobileWallet/UIElements/FadedOverlayView.swift b/MobileWallet/Common/Extensions/UITableView+Common.swift similarity index 68% rename from MobileWallet/UIElements/FadedOverlayView.swift rename to MobileWallet/Common/Extensions/UITableView+Common.swift index 22d75731..395124ca 100644 --- a/MobileWallet/UIElements/FadedOverlayView.swift +++ b/MobileWallet/Common/Extensions/UITableView+Common.swift @@ -1,10 +1,10 @@ -// FadedOverlayView.swift +// UITableView+Common.swift /* Package MobileWallet - Created by Jason van den Berg on 2019/12/09 + Created by Adrian Truszczyński on 13/03/2023 Using Swift 5.0 - Running on macOS 10.15 + Running on macOS 13.0 Copyright 2019 The Tari Project @@ -40,23 +40,18 @@ import UIKit -class FadedOverlayView: UIView { - func applyFade(_ color: UIColor, locations: [NSNumber] = [0, 1]) { - backgroundColor = .clear - let gradient: CAGradientLayer = CAGradientLayer() - gradient.frame = bounds - gradient.colors = [ - color.withAlphaComponent(0).cgColor, - color.cgColor - ] - gradient.locations = locations - gradient.startPoint = CGPoint(x: 0.5, y: 0) - gradient.endPoint = CGPoint(x: 0.5, y: 1) - - layer.insertSublayer(gradient, at: 0) - } +extension UITableView { + + func updateFooterFrame() { + + guard let tableFooterView else { return } + + let width = bounds.width + let size = tableFooterView.systemLayoutSizeFitting(CGSize(width: width, height: UIView.layoutFittingCompressedSize.height)) + + guard tableFooterView.bounds.height != size.height else { return } - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - return false + tableFooterView.bounds.size.height = size.height + self.tableFooterView = tableFooterView } } diff --git a/MobileWallet/Common/Extensions/UIViewController+Content.swift b/MobileWallet/Common/Extensions/UIViewController+Content.swift index bb079343..6b4b7907 100644 --- a/MobileWallet/Common/Extensions/UIViewController+Content.swift +++ b/MobileWallet/Common/Extensions/UIViewController+Content.swift @@ -83,4 +83,9 @@ extension UIViewController { controller.willMove(toParent: self) controller.didMove(toParent: self) } + + func presentOnFullScreen(_ viewController: UIViewController) { + viewController.modalPresentationStyle = .fullScreen + present(viewController, animated: true) + } } diff --git a/MobileWallet/Common/Factories/QRCodeFactory.swift b/MobileWallet/Common/Factories/QRCodeFactory.swift index 87ba2db0..5ec728f6 100644 --- a/MobileWallet/Common/Factories/QRCodeFactory.swift +++ b/MobileWallet/Common/Factories/QRCodeFactory.swift @@ -42,22 +42,34 @@ import UIKit final class QRCodeFactory { - static func makeQrCode(data: Data) -> UIImage? { + static func makeQrCode(data: Data) async -> UIImage? { - guard let filter = CIFilter(name: "CIQRCodeGenerator") else { return nil } + let screenWidth = await UIScreen.main.bounds.width - filter.setValuesForKeys([ - "inputMessage": data, - "inputCorrectionLevel": "L" - ]) + return await withCheckedContinuation { continuation in - guard let outputImage = filter.outputImage else { return nil } + guard let filter = CIFilter(name: "CIQRCodeGenerator") else { + continuation.resume(returning: nil) + return + } - let scaleX = UIScreen.main.bounds.width / outputImage.extent.size.width - let scaleY = UIScreen.main.bounds.width / outputImage.extent.size.height - let transform = CGAffineTransform(scaleX: scaleX, y: scaleY) - let scaledOutputImage = outputImage.transformed(by: transform) + filter.setValuesForKeys([ + "inputMessage": data, + "inputCorrectionLevel": "L" + ]) + + guard let outputImage = filter.outputImage else { + continuation.resume(returning: nil) + return + } + + let scaleX = screenWidth / outputImage.extent.size.width + let scaleY = screenWidth / outputImage.extent.size.height + let transform = CGAffineTransform(scaleX: scaleX, y: scaleY) + let scaledOutputImage = outputImage.transformed(by: transform) + let image = UIImage(ciImage: scaledOutputImage) + continuation.resume(returning: image) + } - return UIImage(ciImage: scaledOutputImage) } } diff --git a/MobileWallet/Common/Managers/MigrationManager.swift b/MobileWallet/Common/Managers/MigrationManager.swift index 18a76619..1d1a0e02 100644 --- a/MobileWallet/Common/Managers/MigrationManager.swift +++ b/MobileWallet/Common/Managers/MigrationManager.swift @@ -46,22 +46,7 @@ enum MigrationManager { // MARK: - Properties - private static let minValidVersion = "0.43.2" - - private static var currentWalletVersion: String { - - get throws { - guard - let path = Bundle.main.path(forResource: "Constants", ofType: "plist"), - let data = FileManager.default.contents(atPath: path), - let dictionary = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: String], - let value = dictionary["FFI Version"] - else { - throw MigrationError.noCurrentWalletVersion - } - return value - } - } + private static let minValidVersion = "0.49.0-pre.4" // MARK: - Actions @@ -77,21 +62,16 @@ enum MigrationManager { } } - static func updateWalletVersion() throws { - try Tari.shared.keyValues.set(key: .version, value: currentWalletVersion) - } - private static func isWalletHasValidVersion() -> Bool { - let walletVersion: String - - do { - walletVersion = try Tari.shared.keyValues.value(key: .version) - } catch { + if let version = try? Tari.shared.walletVersion { + let isValid = VersionValidator.compare(version, isHigherOrEqualTo: minValidVersion) + Logger.log(message: "Min. Valid Wallet Version: \(minValidVersion), Local Wallet Version: \(version), isValid: \(isValid)", domain: .general, level: .info) + return isValid + } else { + Logger.log(message: "Unable to get wallet version", domain: .general, level: .info) return false } - - return VersionValidator.compare(walletVersion, isHigherOrEqualTo: minValidVersion) } private static func showPopUp(completion: @escaping (Bool) -> Void) { diff --git a/MobileWallet/Common/Onboarding/OnboardingViewController.swift b/MobileWallet/Common/Onboarding/OnboardingViewController.swift index aa809765..99cdcf55 100644 --- a/MobileWallet/Common/Onboarding/OnboardingViewController.swift +++ b/MobileWallet/Common/Onboarding/OnboardingViewController.swift @@ -116,13 +116,13 @@ final class OnboardingViewController: UIViewController { private func pageViewModel(index: Int) -> OnboardingPageView.ViewModel { switch index { case 0: - return OnboardingPageView.ViewModel.page1 + return .page1 case 1: - return OnboardingPageView.ViewModel.page2 + return .page2 case 2: - return OnboardingPageView.ViewModel.page3 + return .page3 case 3: - return OnboardingPageView.ViewModel.page4 + return .page4 default: return OnboardingPageView.ViewModel(image: nil, titleComponents: [], messageComponents: [], footerComponents: [], actionButtonTitle: nil, actionButtonCallback: nil) } diff --git a/MobileWallet/Common/Pop-up/Components/PopUpDescriptionContentView.swift b/MobileWallet/Common/Pop-up/Components/PopUpDescriptionContentView.swift index 679fda74..2ac456cd 100644 --- a/MobileWallet/Common/Pop-up/Components/PopUpDescriptionContentView.swift +++ b/MobileWallet/Common/Pop-up/Components/PopUpDescriptionContentView.swift @@ -45,9 +45,12 @@ final class PopUpDescriptionContentView: DynamicThemeView { // MARK: - Subviews - @View private(set) var label: UILabel = { - let view = UILabel() + @View private(set) var label: StylizedLabel = { + let view = StylizedLabel() view.font = .Avenir.medium.withSize(14.0) + view.normalFont = .Avenir.medium.withSize(14.0) + view.boldFont = .Avenir.heavy.withSize(14.0) + view.separator = " " view.textAlignment = .center view.numberOfLines = 0 return view diff --git a/MobileWallet/Common/Pop-up/Components/PopUpHeaderView.swift b/MobileWallet/Common/Pop-up/Components/PopUpHeaderView.swift index fa65bb7c..84c37a62 100644 --- a/MobileWallet/Common/Pop-up/Components/PopUpHeaderView.swift +++ b/MobileWallet/Common/Pop-up/Components/PopUpHeaderView.swift @@ -45,9 +45,11 @@ final class PopUpHeaderView: DynamicThemeView { // MARK: - Subviews - @View private(set) var label: UILabel = { - let view = UILabel() + @View private(set) var label: StylizedLabel = { + let view = StylizedLabel() view.font = .Avenir.light.withSize(18.0) + view.normalFont = .Avenir.light.withSize(18.0) + view.separator = " " view.textAlignment = .center view.numberOfLines = 0 return view diff --git a/MobileWallet/Common/Pop-up/PopUpComponentsFactory.swift b/MobileWallet/Common/Pop-up/PopUpComponentsFactory.swift index cc7c49f5..c0acfabc 100644 --- a/MobileWallet/Common/Pop-up/PopUpComponentsFactory.swift +++ b/MobileWallet/Common/Pop-up/PopUpComponentsFactory.swift @@ -42,13 +42,13 @@ enum PopUpComponentsFactory { static func makeHeaderView(title: String) -> PopUpHeaderView { let view = PopUpHeaderView() - view.label.text = title + view.label.textComponents = [StylizedLabel.StylizedText(text: title, style: .normal)] return view } static func makeContentView(message: String) -> PopUpDescriptionContentView { let view = PopUpDescriptionContentView() - view.label.text = message + view.label.textComponents = [StylizedLabel.StylizedText(text: message, style: .normal)] return view } diff --git a/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift b/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift index c4c7d82a..a7e8af10 100644 --- a/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift +++ b/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift @@ -41,10 +41,35 @@ import UIKit struct PopUpDialogModel { - let title: String? - let message: String? + let titleComponents: [StylizedLabel.StylizedText] + let messageComponents: [StylizedLabel.StylizedText] let buttons: [PopUpDialogButtonModel] - let hapticType: PopUpPresenter.Configuration.HapticType + let hapticType: PopUpPresenter.HapticType + + init(titleComponents: [StylizedLabel.StylizedText], messageComponents: [StylizedLabel.StylizedText], buttons: [PopUpDialogButtonModel], hapticType: PopUpPresenter.HapticType) { + self.titleComponents = titleComponents + self.messageComponents = messageComponents + self.buttons = buttons + self.hapticType = hapticType + } + + init(title: String?, message: String?, buttons: [PopUpDialogButtonModel], hapticType: PopUpPresenter.HapticType) { + + if let title { + titleComponents = [StylizedLabel.StylizedText(text: title, style: .normal)] + } else { + titleComponents = [] + } + + if let message { + messageComponents = [StylizedLabel.StylizedText(text: message, style: .normal)] + } else { + messageComponents = [] + } + + self.buttons = buttons + self.hapticType = hapticType + } } struct PopUpDialogButtonModel { @@ -108,12 +133,16 @@ extension PopUpPresenter { var contentView: UIView? var buttonsView: UIView? - if let title = model.title { - headerView = PopUpComponentsFactory.makeHeaderView(title: title) + if !model.titleComponents.isEmpty { + let view = PopUpHeaderView() + view.label.textComponents = model.titleComponents + headerView = view } - if let message = model.message { - contentView = PopUpComponentsFactory.makeContentView(message: message) + if !model.messageComponents.isEmpty { + let view = PopUpDescriptionContentView() + view.label.textComponents = model.messageComponents + contentView = view } if !model.buttons.isEmpty { @@ -144,7 +173,7 @@ extension PopUpPresenter { // MARK: - Helpers - private static func makeHapticType(model: MessageModel) -> Configuration.HapticType { + private static func makeHapticType(model: MessageModel) -> HapticType { switch model.type { case .error: return .error @@ -160,11 +189,11 @@ extension PopUpPresenter { extension PopUpPresenter.Configuration { - static func message(hapticType: Self.HapticType) -> Self { + static func message(hapticType: PopUpPresenter.HapticType) -> Self { Self(displayDuration: 12.0, dismissOnTapOutsideOrSwipe: true, hapticType: hapticType) } - static func dialog(hapticType: Self.HapticType) -> Self { + static func dialog(hapticType: PopUpPresenter.HapticType) -> Self { Self(displayDuration: nil, dismissOnTapOutsideOrSwipe: false, hapticType: hapticType) } } diff --git a/MobileWallet/Common/Pop-up/PopUpPresenter.swift b/MobileWallet/Common/Pop-up/PopUpPresenter.swift index a821e80f..268c7221 100644 --- a/MobileWallet/Common/Pop-up/PopUpPresenter.swift +++ b/MobileWallet/Common/Pop-up/PopUpPresenter.swift @@ -43,14 +43,13 @@ import SwiftEntryKit enum PopUpPresenter { - struct Configuration { - - enum HapticType { - case success - case error - case none - } + enum HapticType { + case success + case error + case none + } + struct Configuration { let displayDuration: TimeInterval? let dismissOnTapOutsideOrSwipe: Bool let hapticType: HapticType diff --git a/MobileWallet/Common/Theme.swift b/MobileWallet/Common/Theme.swift index f6ff2f17..5b16c803 100644 --- a/MobileWallet/Common/Theme.swift +++ b/MobileWallet/Common/Theme.swift @@ -58,7 +58,6 @@ struct Images { let homeItem = UIImage(named: "navHome") let ttlItem = UIImage(named: "navTtl") let sendItem = UIImage(named: "navSend")?.withRenderingMode(.alwaysOriginal) - let profileItem = UIImage(named: "navProfile") let settingsItem = UIImage(named: "navSettings") // General icons @@ -300,15 +299,65 @@ extension Shadow { static var none: Self { Self(color: nil, opacity: 0.0, radius: 0.0, offset: .zero) } } +// MARK: - Images + extension UIImage { + static var security: SecurityImages.Type { SecurityImages.self } + static var contactBook: ContactBookImages.Type { ContactBookImages.self } + static var icons: IconsImages.Type { IconsImages.self } +} + +// MARK: - Security + +enum SecurityImages { + static var onboarding: SecurityOnboardingImages.Type { SecurityOnboardingImages.self } +} + +enum SecurityOnboardingImages { + static var background: UIImage? { UIImage(named: "Images/Security/Onboarding/Background") } + static var page1: UIImage? { UIImage(named: "Images/Security/Onboarding/Page1") } + static var page2: UIImage? { UIImage(named: "Images/Security/Onboarding/Page2") } + static var page3: UIImage? { UIImage(named: "Images/Security/Onboarding/Page3") } + static var page4: UIImage? { UIImage(named: "Images/Security/Onboarding/Page4") } +} + +// MARK: - Contact Book + +enum ContactBookImages { + static var placeholders: ContactBookPlaceholderImages.Type { ContactBookPlaceholderImages.self } + static var addContact: UIImage? { UIImage(named: "Images/Contact Book/AddContact") } +} + +enum ContactBookPlaceholderImages { + static var contactsList: UIImage? { UIImage(named: "Images/Contact Book/Placeholders/ContactBookListPlaceholder") } + static var favoritesContactsList: UIImage? { UIImage(named: "Images/Contact Book/Placeholders/ContactBookListFavPlaceholder") } +} + +// MARK: - Icons + +enum IconsImages { + + static var contactTypes: IconsContactTypesImages.Type { IconsContactTypesImages.self } + static var star: IconsStarImages.Type { IconsStarImages.self } + static var tabBar: IconsTabBarImages.Type { IconsTabBarImages.self } + + static var link: UIImage? { UIImage(named: "Icons/Link") } + static var profile: UIImage? { UIImage(named: "Icons/Profile") } + static var send: UIImage? { UIImage(named: "Icons/Send") } + static var unlink: UIImage? { UIImage(named: "Icons/Unlink") } +} + +enum IconsContactTypesImages { + static var `internal`: UIImage? { UIImage(named: "Icons/Contact Types/Internal") } + static var external: UIImage? { UIImage(named: "Icons/Contact Types/External") } + static var linked: UIImage? { UIImage(named: "Icons/Contact Types/Linked") } +} + +enum IconsStarImages { + static var border: UIImage? { UIImage(named: "Icons/Star/Border") } + static var filled: UIImage? { UIImage(named: "Icons/Star/Filled") } +} - enum security { - enum onboarding { - static var background: UIImage? { UIImage(named: "Images/Security/Onboarding/Background") } - static var page1: UIImage? { UIImage(named: "Images/Security/Onboarding/Page1") } - static var page2: UIImage? { UIImage(named: "Images/Security/Onboarding/Page2") } - static var page3: UIImage? { UIImage(named: "Images/Security/Onboarding/Page3") } - static var page4: UIImage? { UIImage(named: "Images/Security/Onboarding/Page4") } - } - } +enum IconsTabBarImages { + static var contactBook: UIImage? { UIImage(named: "Icons/TabBar/ContactBook") } } diff --git a/MobileWallet/Common/Tools/VersionValidator.swift b/MobileWallet/Common/Tools/VersionValidator.swift index b151b860..5cdbcbe2 100644 --- a/MobileWallet/Common/Tools/VersionValidator.swift +++ b/MobileWallet/Common/Tools/VersionValidator.swift @@ -39,24 +39,75 @@ */ enum VersionValidator { - static func compare(_ firstVersion: String, isHigherOrEqualTo secondVersion: String) -> Bool { + Version(rawVersion: firstVersion) >= Version(rawVersion: secondVersion) + } +} + +private struct Version: Comparable { - var firstVersionComponents = firstVersion.split(separator: ".") - var secondVersionComponents = secondVersion.split(separator: ".") + let components: [String] + let suffix: String - let componentsCount = max(firstVersionComponents.count, secondVersionComponents.count) + private var suffixValue: Int { + switch suffix { + case "pre": + return 1 + case "rc": + return 2 + case "": + return 3 + default: + return 0 + } + } + + init(rawVersion: String) { - firstVersionComponents += Array(repeating: "", count: componentsCount - firstVersionComponents.count) - secondVersionComponents += Array(repeating: "", count: componentsCount - secondVersionComponents.count) + let rawComponents = rawVersion + .split(separator: "-", maxSplits: 1) + .map { String($0) } - let result: Bool? = zip(firstVersionComponents, secondVersionComponents) + if rawComponents.count >= 1 { + components = rawComponents[0] + .split(separator: ".") + .map { String($0) } + } else { + components = [] + } + + if rawComponents.count == 2 { + suffix = rawComponents[1] + } else { + suffix = "" + } + } + + static func < (lhs: Self, rhs: Self) -> Bool { + + var firstComponents = lhs.components + var secondComponents = rhs.components + + let componentsCount = max(firstComponents.count, secondComponents.count) + + firstComponents += Array(repeating: "", count: componentsCount - firstComponents.count) + secondComponents += Array(repeating: "", count: componentsCount - secondComponents.count) + + let result: Bool? = zip(firstComponents, secondComponents) .compactMap { guard $0 != $1 else { return nil } - return $0 > $1 + return $0 < $1 } .first - return result ?? true + if let result { + return result + } + + if lhs.suffixValue == rhs.suffixValue { + return lhs.suffix < rhs.suffix + } + + return lhs.suffixValue < rhs.suffixValue } } diff --git a/MobileWallet/Common/Views/FormTextField.swift b/MobileWallet/Common/Views/FormTextField.swift new file mode 100644 index 00000000..c3d8592f --- /dev/null +++ b/MobileWallet/Common/Views/FormTextField.swift @@ -0,0 +1,146 @@ +// FormTextField.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 02/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import Combine +import TariCommon + +final class FormTextField: DynamicThemeView { + + // MARK: - Subviews + + @View private var textField: EmojiTextField = { + let view = EmojiTextField() + view.font = .Avenir.medium.withSize(14.0) + return view + }() + + @View private var separator = UIView() + + // MARK: - Properties + + var placeholder: String? { + get { textField.placeholder } + set { textField.placeholder = newValue } + } + + var text: String? { + get { textField.text } + set { textField.text = newValue } + } + + var returnKeyType: UIReturnKeyType { + get { textField.returnKeyType } + set { textField.returnKeyType = newValue } + } + + var publisher: AnyPublisher { + textField.textPublisher() + } + + var isEmojiKeyboardVisible: Bool { + get { textField.isEmojiKeyboardVisible } + set { textField.isEmojiKeyboardVisible = newValue } + } + + var onReturnPressed: (() -> Void)? + + // MARK: - Initialisers + + override init() { + super.init() + setupConstraints() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [textField, separator].forEach(addSubview) + + let constraints = [ + textField.topAnchor.constraint(equalTo: topAnchor), + textField.leadingAnchor.constraint(equalTo: leadingAnchor), + textField.trailingAnchor.constraint(equalTo: trailingAnchor), + separator.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 26.0), + separator.leadingAnchor.constraint(equalTo: leadingAnchor), + separator.trailingAnchor.constraint(equalTo: trailingAnchor), + separator.bottomAnchor.constraint(equalTo: bottomAnchor), + separator.heightAnchor.constraint(equalToConstant: 1.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + textField.delegate = self + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + textField.textColor = theme.text.heading + separator.backgroundColor = theme.neutral.secondary + } + + // MARK: - First Responder + + override func becomeFirstResponder() -> Bool { + textField.becomeFirstResponder() + } + + override func resignFirstResponder() -> Bool { + textField.resignFirstResponder() + } +} + +extension FormTextField: UITextFieldDelegate { + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + onReturnPressed?() + return false + } +} diff --git a/MobileWallet/Common/Views/LoadingImageView.swift b/MobileWallet/Common/Views/LoadingImageView.swift new file mode 100644 index 00000000..623cdee9 --- /dev/null +++ b/MobileWallet/Common/Views/LoadingImageView.swift @@ -0,0 +1,125 @@ +// LoadingImageView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 21/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import TariCommon +import Lottie + +final class LoadingImageView: UIView { + + enum State { + case loading + case image(_ image: UIImage?) + } + + // MARK: - Subviews + + @View private var loadingView: AnimationView = { + let view = AnimationView() + view.backgroundBehavior = .pauseAndRestore + view.animation = .named(.pendingCircleAnimation) + view.loopMode = .loop + view.play() + return view + }() + + @View private var imageView: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFit + view.alpha = 0.0 + return view + }() + + // MARK: - Properties + + var state: State = .loading { + didSet { update(state: state) } + } + + // MARK: - Initialisers + + init() { + super.init(frame: .zero) + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [loadingView, imageView].forEach(addSubview) + + let constraints = [ + loadingView.centerXAnchor.constraint(equalTo: centerXAnchor), + loadingView.centerYAnchor.constraint(equalTo: centerYAnchor), + loadingView.widthAnchor.constraint(equalToConstant: 44.0), + loadingView.heightAnchor.constraint(equalToConstant: 44.0), + loadingView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + loadingView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor), + loadingView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor), + loadingView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor), + imageView.topAnchor.constraint(equalTo: topAnchor), + imageView.leadingAnchor.constraint(equalTo: leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: trailingAnchor), + imageView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + private func update(state: State) { + UIView.animate(withDuration: 0.3) { + switch state { + case .loading: + self.loadingView.alpha = 1.0 + self.imageView.alpha = 0.0 + case let .image(image): + self.loadingView.alpha = 0.0 + self.imageView.alpha = 1.0 + self.imageView.image = image + } + } + } +} diff --git a/MobileWallet/Common/Views/SearchTextField.swift b/MobileWallet/Common/Views/SearchTextField.swift new file mode 100644 index 00000000..cab7c938 --- /dev/null +++ b/MobileWallet/Common/Views/SearchTextField.swift @@ -0,0 +1,95 @@ +// SearchTextField.swift + +/* + Package MobileWallet + Created by Browncoat on 21/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +final class SearchTextField: DynamicThemeTextField { + + // MARK: - Subviews + + private let rightImageView: UIImageView = { + let view = UIImageView(image: Theme.shared.images.searchIcon) + view.frame = CGRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) + view.contentMode = .scaleAspectFill + return view + }() + + // MARK: - Initialisers + + override init() { + super.init() + setupView() + setupSideViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupView() { + font = .Avenir.medium.withSize(14.0) + layer.cornerRadius = 6.0 + layer.borderWidth = 1.0 + heightAnchor.constraint(equalToConstant: 46.0).isActive = true + } + + private func setupSideViews() { + + leftView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 20.0, height: 0.0)) + leftViewMode = .always + + let rightPaddingView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 44.0, height: 0.0)) + rightPaddingView.addSubview(rightImageView) + rightImageView.center = rightPaddingView.center + + rightView = rightPaddingView + rightViewMode = .always + } + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.primary + textColor = theme.text.heading + rightImageView.tintColor = theme.text.links + layer.borderColor = theme.neutral.tertiary?.cgColor + } +} diff --git a/MobileWallet/Info.plist b/MobileWallet/Info.plist index 706335af..8931ca03 100644 --- a/MobileWallet/Info.plist +++ b/MobileWallet/Info.plist @@ -51,11 +51,13 @@ LSRequiresIPhoneOS NSCameraUsageDescription - Tari needs access to the camera to scan the QR + + NSContactsUsageDescription + NSFaceIDUsageDescription - Enable Face ID to protect your wallet + NSPhotoLibraryAddUsageDescription - Tari needs permission to write media to photo library + NSUbiquitousContainers iCloud.com.tari.wallet diff --git a/MobileWallet/InfoPlist.strings b/MobileWallet/InfoPlist.strings new file mode 100644 index 00000000..12d12a4a --- /dev/null +++ b/MobileWallet/InfoPlist.strings @@ -0,0 +1,4 @@ +NSCameraUsageDescription = "Tari needs access to the camera to scan the QR"; +NSContactsUsageDescription = "The App need access to your contact book to present contats on the list"; +NSFaceIDUsageDescription = "Enable Face ID to protect your wallet"; +NSPhotoLibraryAddUsageDescription = "Tari needs permission to write media to photo library"; diff --git a/MobileWallet/MobileWallet-bridging-header.h b/MobileWallet/MobileWallet-bridging-header.h index 93045b14..a531d61a 100644 --- a/MobileWallet/MobileWallet-bridging-header.h +++ b/MobileWallet/MobileWallet-bridging-header.h @@ -3,5 +3,5 @@ // MobileWallet // -#import "./TariLib/wallet.h" +#import "./TariLib/libtari_wallet_ffi_ios.xcframework/ios-arm64/HEADERS" #import "./TariLib/Core/Tor/Helpers/NetworkTools.h" diff --git a/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesViewController.swift b/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesViewController.swift index b7b0d4b9..a1a53db7 100644 --- a/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesViewController.swift +++ b/MobileWallet/Screens/AdvancedSettings/Bridges/CustomBridgesViewController.swift @@ -298,7 +298,10 @@ extension CustomBridgesViewController: UITextViewDelegate { } func textViewDidChange(_ textView: UITextView) { - navigationBar.rightButton.isEnabled = textView.text.count > 0 && textView.text.trimmingCharacters(in: .whitespacesAndNewlines) != bridgesConfiguration?.customBridges?.joined(separator: "\n") && textView.text != examplePlaceHolderString + let isTextFieldNotEmpty = !textView.text.isEmpty + let isNewBridge = textView.text.trimmingCharacters(in: .whitespacesAndNewlines) != bridgesConfiguration?.customBridges?.joined(separator: "\n") + let isNotPlaceholder = textView.text != examplePlaceHolderString + navigationBar.rightButton.isEnabled = isTextFieldNotEmpty && isNewBridge && isNotPlaceholder } func textViewDidBeginEditing(_ textView: UITextView) { diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift index fec5481d..b7fbb4b7 100644 --- a/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift +++ b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift @@ -133,7 +133,6 @@ final class SplashViewModel { do { status = StatusModel(status: .working, statusRepresentation: .content) try await connectToWallet(isWalletConnected: false) - try MigrationManager.updateWalletVersion() status = StatusModel(status: .success, statusRepresentation: .content) } catch { handle(error: error) @@ -149,14 +148,14 @@ final class SplashViewModel { let statusRepresentation = status?.statusRepresentation ?? .content status = StatusModel(status: .working, statusRepresentation: statusRepresentation) - try await connectToWallet(isWalletConnected: isWalletConnected) - isWalletConnected = false - guard await validateWallet() else { self.deleteWallet() return } + try await connectToWallet(isWalletConnected: isWalletConnected) + isWalletConnected = false + status = StatusModel(status: .success, statusRepresentation: statusRepresentation) } catch { self.handle(error: error) diff --git a/MobileWallet/Screens/Contact Book/Add Contact/AddContactConstructor.swift b/MobileWallet/Screens/Contact Book/Add Contact/AddContactConstructor.swift new file mode 100644 index 00000000..1910573e --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Add Contact/AddContactConstructor.swift @@ -0,0 +1,47 @@ +// AddContactConstructor.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 15/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum AddContactConstructor { + + static func bulidScene() -> AddContactViewController { + let model = AddContactModel() + return AddContactViewController(model: model) + } +} diff --git a/MobileWallet/Screens/Contact Book/Add Contact/AddContactModel.swift b/MobileWallet/Screens/Contact Book/Add Contact/AddContactModel.swift new file mode 100644 index 00000000..a914aebd --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Add Contact/AddContactModel.swift @@ -0,0 +1,204 @@ +// AddContactModel.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 15/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +final class AddContactModel { + + private enum DataValidationError: Int, Error, Comparable { + + case noEmojiID + case invalidEmojiID + case noName + + static func < (lhs: AddContactModel.DataValidationError, rhs: AddContactModel.DataValidationError) -> Bool { + lhs.rawValue < rhs.rawValue + } + } + + enum Action { + case moveToContactDetails(model: ContactsManager.Model) + } + + // MARK: - View Model + + let searchTextSubject: CurrentValueSubject = CurrentValueSubject("") + + @Published var contactName: String = "" + @Published var isSearchTextFormatted: Bool = true + + @Published private(set) var isDataValid: Bool = false + @Published private(set) var action: Action? + @Published private(set) var errorText: String? + @Published private(set) var errorMessage: MessageModel? + + // MARK: - Properties + + private var rawSearchText: String = "" + private var address: TariAddress? + @Published private var errors = Set([.noEmojiID]) + + private let contactsManager = ContactsManager() + private var cancellables = Set() + + // MARK: - Initialisers + + init() { + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + + Publishers.CombineLatest(searchTextSubject.removeDuplicates(), $isSearchTextFormatted.removeDuplicates()) + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.handle(searchText: $0, isSearchTextFormatted: $1) } + .store(in: &cancellables) + + $contactName + .sink { [weak self] in self?.handle(contactName: $0) } + .store(in: &cancellables) + + $errors + .map { $0.sorted().first } + .map { [weak self] in self?.makeErrorText(error: $0) } + .sink { [weak self] in self?.errorText = $0 } + .store(in: &cancellables) + + $errors + .map { $0.isEmpty } + .assignPublisher(to: \.isDataValid, on: self) + .store(in: &cancellables) + } + + // MARK: - Actions + + func createContact() { + do { + guard let address else { return } + let model = try contactsManager.createInternalModel(name: contactName, isFavorite: false, address: address) + action = .moveToContactDetails(model: model) + } catch { + errorMessage = ErrorMessageManager.errorModel(forError: error) + } + } + + func handle(deeplink: TransactionsSendDeeplink) { + let address = TariAddressFactory.address(text: deeplink.receiverAddress) + guard let emojis = try? address?.emojis else { return } + rawSearchText = emojis + searchTextSubject.send(emojis) + } + + // MARK: - Handlers + + private func handle(searchText: String, isSearchTextFormatted: Bool) { + + if !isSearchTextFormatted { + rawSearchText = searchText + } + + handle(searchText: rawSearchText) + + if isSearchTextFormatted, address != nil { + searchTextSubject.send(rawSearchText.insertSeparator(" | ", atEvery: 3)) + } else { + searchTextSubject.send(rawSearchText) + } + } + + private func handle(searchText: String) { + + guard !searchText.isEmpty else { + address = nil + errors.remove(.invalidEmojiID) + errors.insert(.noEmojiID) + return + } + + errors.remove(.noEmojiID) + + guard let address = TariAddressFactory.address(text: searchText) else { + address = nil + errors.insert(.invalidEmojiID) + return + } + + self.address = address + errors.remove(.invalidEmojiID) + } + + private func handle(contactName: String) { + + guard !contactName.isEmpty else { + errors.insert(.noName) + return + } + + errors.remove(.noName) + } + + private func makeErrorText(error: DataValidationError?) -> String? { + + guard let error else { return nil } + + switch error { + case .noEmojiID: + return nil + case .invalidEmojiID: + return localized("contact_book.add_contact.validation_error.invalid_emoji_id") + case .noName: + return localized("contact_book.add_contact.validation_error.no_name") + } + } +} + +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 new file mode 100644 index 00000000..19778609 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Add Contact/AddContactView.swift @@ -0,0 +1,174 @@ +// AddContactView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 15/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine +import TariCommon + +final class AddContactView: BaseNavigationContentView { + + // MARK: - Subviews + + @View private var searchViewBackgroundView = UIView() + + @View private(set) var searchView: AddRecipientSearchView = { + let view = AddRecipientSearchView() + view.textField.placeholder = localized("contact_book.add_contact.text_field.search.placeholder") + view.previewText = nil + return view + }() + + @View private var nameTextField: UITextField = { + let view = UITextField() + view.font = .Avenir.medium.withSize(14.0) + return view + }() + + @View private var nameTextFieldSeparator = UIView() + + @View private var errorLabel: UILabel = { + let view = UILabel() + view.textAlignment = .center + view.font = .Avenir.medium.withSize(14.0) + view.numberOfLines = 0 + return view + }() + + // MARK: - Properties + + var contactName: AnyPublisher { nameTextField.textPublisher() } + + var errorText: String? { + didSet { errorLabel.text = errorText } + } + + var isDoneButtonEnabled: Bool = false { + didSet { navigationBar.rightButton.isEnabled = isDoneButtonEnabled } + } + + var onDoneButtonTap: (() -> Void)? + var onQRCodeButtonTap: (() -> Void)? + var onSearchTextFieldFocusState: ((_ isFocused: Bool) -> Void)? + + // MARK: - Initialisers + + override init() { + super.init() + setupSuviews() + setupConstraints() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupSuviews() { + navigationBar.title = localized("contact_book.add_contact.title") + navigationBar.rightButton.setTitle(localized("common.done"), for: .normal) + } + + private func setupConstraints() { + + [searchViewBackgroundView, searchView, nameTextField, nameTextFieldSeparator, errorLabel].forEach(addSubview) + + let constraints = [ + searchViewBackgroundView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + searchViewBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor), + searchViewBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor), + searchView.topAnchor.constraint(equalTo: searchViewBackgroundView.topAnchor, constant: 22.0), + searchView.leadingAnchor.constraint(equalTo: searchViewBackgroundView.leadingAnchor, constant: 25.0), + searchView.trailingAnchor.constraint(equalTo: searchViewBackgroundView.trailingAnchor, constant: -25.0), + searchView.bottomAnchor.constraint(equalTo: searchViewBackgroundView.bottomAnchor, constant: -22.0), + nameTextField.topAnchor.constraint(equalTo: searchViewBackgroundView.bottomAnchor, constant: 8.0), + nameTextField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + nameTextField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), + nameTextField.heightAnchor.constraint(equalToConstant: 50.0), + nameTextFieldSeparator.topAnchor.constraint(equalTo: nameTextField.bottomAnchor), + nameTextFieldSeparator.leadingAnchor.constraint(equalTo: nameTextField.leadingAnchor), + nameTextFieldSeparator.trailingAnchor.constraint(equalTo: nameTextField.trailingAnchor), + nameTextFieldSeparator.heightAnchor.constraint(equalToConstant: 1.0), + errorLabel.topAnchor.constraint(equalTo: nameTextFieldSeparator.bottomAnchor, constant: 8.0), + errorLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + errorLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + + navigationBar.onRightButtonAction = { [weak self] in + self?.onDoneButtonTap?() + } + + searchView.qrButton.onTap = { [weak self] in + self?.onQRCodeButtonTap?() + } + + searchView.textField.delegate = self + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.primary + searchViewBackgroundView.backgroundColor = theme.backgrounds.secondary + nameTextField.textColor = theme.text.heading + nameTextFieldSeparator.backgroundColor = theme.neutral.tertiary + errorLabel.textColor = theme.system.red + + guard let placeholderColor = theme.text.lightText else { return } + 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 new file mode 100644 index 00000000..8dbdde18 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Add Contact/AddContactViewController.swift @@ -0,0 +1,140 @@ +// AddContactViewController.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 15/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import Combine + +final class AddContactViewController: UIViewController { + + // MARK: - Properties + + private let model: AddContactModel + private let mainView = AddContactView() + + private var cancellables = Set() + + // MARK: - Initialisers + + init(model: AddContactModel) { + self.model = model + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View Lifecycle + + override func loadView() { + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + + model.$errorText + .sink { [weak self] in self?.mainView.errorText = $0 } + .store(in: &cancellables) + + model.$isDataValid + .sink { [weak self] in self?.mainView.isDoneButtonEnabled = $0 } + .store(in: &cancellables) + + model.$action + .compactMap { $0 } + .sink { [weak self] in self?.handle(action: $0) } + .store(in: &cancellables) + + model.$errorMessage + .compactMap { $0 } + .sink { PopUpPresenter.show(message: $0) } + .store(in: &cancellables) + + mainView.searchView.textField.bind(withSubject: model.searchTextSubject, storeIn: &cancellables) + + mainView.contactName + .assignPublisher(to: \.contactName, on: model) + .store(in: &cancellables) + + mainView.onSearchTextFieldFocusState = { [weak self] in + self?.model.isSearchTextFormatted = !$0 + } + + mainView.onQRCodeButtonTap = { [weak self] in + self?.showQRScanner() + } + + mainView.onDoneButtonTap = { [weak self] in + self?.model.createContact() + } + } + + // MARK: - Handlers + + private func handle(action: AddContactModel.Action) { + switch action { + case let .moveToContactDetails(model): + let controller = ContactDetailsConstructor.buildScene(model: model) + navigationController?.pushViewController(controller, animated: true) + navigationController?.remove(controller: self) + } + } + + private func showQRScanner() { + let scanViewController = ScanViewController(scanResourceType: .publicKey) + scanViewController.actionDelegate = self + scanViewController.modalPresentationStyle = UIDevice.current.userInterfaceIdiom == .pad ? .automatic :.popover + present(scanViewController, animated: true, completion: nil) + } +} + +extension AddContactViewController: ScanViewControllerDelegate { + + func onScan(deeplink: TransactionsSendDeeplink) { + model.handle(deeplink: deeplink) + } +} diff --git a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsConstructor.swift b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsConstructor.swift new file mode 100644 index 00000000..d0a35d95 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsConstructor.swift @@ -0,0 +1,47 @@ +// ContactDetailsConstructor.swift + +/* + Package MobileWallet + Created by Browncoat on 23/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum ContactDetailsConstructor { + + static func buildScene(model: ContactsManager.Model) -> ContactDetailsViewController { + let model = ContactDetailsModel(model: model) + return ContactDetailsViewController(model: model) + } +} diff --git a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsModel.swift b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsModel.swift new file mode 100644 index 00000000..2749be6c --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsModel.swift @@ -0,0 +1,387 @@ +// ContactDetailsModel.swift + +/* + Package MobileWallet + Created by Browncoat on 23/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine +import YatLib + +final class ContactDetailsModel { + + struct MenuSection { + let title: String? + let items: [MenuItem] + } + + enum MenuItem: UInt { + case send + case addToFavorites + case removeFromFavorites + case linkContact + case unlinkContact + case removeContact + case btcWallet + case ethWallet + case xmrWallet + } + + enum Action { + case sendTokens(paymentInfo: PaymentInfo) + case moveToLinkContactScreen(model: ContactsManager.Model) + case showUnlinkConfirmationDialog(emojiID: String, name: String) + case showUnlinkSuccessDialog(emojiID: String, name: String) + case removeContactConfirmation + case endFlow + } + + struct ViewModel { + let avatarText: String? + let avatarImage: UIImage? + let emojiID: String + let hex: String? + let contactType: ContactsManager.ContactType + } + + // MARK: - View Model + + @Published private(set) var isContactExist: Bool = false + @Published private(set) var name: String? + @Published private(set) var viewModel: ViewModel? + @Published private(set) var yat: String? + @Published private(set) var menuSections: [MenuSection] = [] + @Published private(set) var action: Action? + @Published private(set) var errorModel: MessageModel? + + var hasSplittedName: Bool { model.hasExternalModel } + var nameComponents: [String] { model.nameComponents } + + // MARK: - Properties + + @Published private var model: ContactsManager.Model + @Published private var connectedWallets: [YatRecordTag: String] = [:] + @Published private var mainMenuItems: [MenuItem] = [] + @Published private var yatMenuItems: [MenuItem] = [] + + private let contactsManager = ContactsManager() + private var cancellables = Set() + + init(model: ContactsManager.Model) { + self.model = model + setupCallbacks() + } + + // MARK: - View Model + + func perform(actionID: UInt) { + + guard let menuItem = MenuItem(rawValue: actionID) else { return } + + switch menuItem { + case .send: + performSendAction() + case .linkContact: + action = .moveToLinkContactScreen(model: model) + case .unlinkContact: + prepareForUnkinkAction() + case .addToFavorites: + update(isFavorite: true) + case .removeFromFavorites: + update(isFavorite: false) + case .removeContact: + action = .removeContactConfirmation + case .btcWallet: + openAddress(type: .BTCAddress) + case .ethWallet: + openAddress(type: .ETHAddress) + case .xmrWallet: + openAddress(type: .XMRStandardAddress) + } + } + + // MARK: - Setups + + private func setupCallbacks() { + + $model + .sink { [weak self] in self?.handle(model: $0) } + .store(in: &cancellables) + + $yat + .sink { [weak self] in self?.fetchYatData(yat: $0) } + .store(in: &cancellables) + + $connectedWallets + .compactMap { [weak self] in self?.makeYatMenuItems(connectedWallets: $0) } + .assignPublisher(to: \.yatMenuItems, on: self) + .store(in: &cancellables) + + Publishers.CombineLatest($mainMenuItems, $yatMenuItems) + .compactMap { [weak self] in self?.makeMenuSections(mainMenuItems: $0, yatMenuItems: $1) } + .assignPublisher(to: \.menuSections, on: self) + .store(in: &cancellables) + } + + // MARK: - Actions + + func updateData() { + Task { + do { + try await contactsManager.fetchModels() + model = contactsManager.updatedModel(model: model) + } catch { + errorModel = ErrorMessageManager.errorModel(forError: error) + } + } + } + + func update(nameComponents: [String], yat: String) { + update(nameComponents: nameComponents, isFavorite: model.isFavorite, yat: yat) + } + + func unlinkContact() { + + guard let emojiID = model.internalModel?.emojiID.obfuscatedText, let name = model.externalModel?.fullname else { return } + + do { + try contactsManager.unlink(contact: model) + action = .showUnlinkSuccessDialog(emojiID: emojiID, name: name) + updateData() + } catch { + errorModel = ErrorMessageManager.errorModel(forError: error) + } + } + + func removeContact() { + do { + try contactsManager.remove(contact: model) + action = .endFlow + } catch { + errorModel = ErrorMessageManager.errorModel(forError: error) + } + } + + private func update(isFavorite: Bool) { + update(nameComponents: model.nameComponents, isFavorite: isFavorite, yat: yat ?? "") + } + + private func update(nameComponents: [String], isFavorite: Bool, yat: String) { + do { + try contactsManager.update(nameComponents: nameComponents, isFavorite: isFavorite, yat: yat, contact: model) + updateData() + } catch { + errorModel = ErrorMessageManager.errorModel(forError: error) + } + } + + 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 + } + } + + if model.isFFIContact || model.hasExternalModel { + mainMenuItems.append(.removeContact) + } + + self.mainMenuItems = mainMenuItems + } + + private func performSendAction() { + do { + guard let paymentInfo = try model.paymentInfo else { return } + action = .sendTokens(paymentInfo: paymentInfo) + } catch { + errorModel = ErrorMessageManager.errorModel(forError: error) + } + } + + private func prepareForUnkinkAction() { + guard let emojiID = model.internalModel?.emojiID.obfuscatedText, let name = model.externalModel?.fullname else { return } + action = .showUnlinkConfirmationDialog(emojiID: emojiID, name: name) + } + + private func openAddress(type: YatRecordTag) { + + guard var address = connectedWallets[type] else { return } + + let prefix: String + + switch type { + case .BTCAddress: + prefix = "bitcoin:" + case .ETHAddress: + prefix = "ethereum:pay-" + if !address.hasPrefix("0x") { + address = "0x" + address + } + case .XMRStandardAddress: + prefix = "monero:" + default: + return + } + + guard let url = URL(string: [prefix, address].joined()) else { return } + + UIApplication.shared.open(url) { [weak self] isSuccess in + guard !isSuccess else { return } + self?.errorModel = MessageModel(title: localized("common.error"), message: localized("contact_book.details.popup.error.unable_to_open_url", arguments: type.walletName), type: .error) + } + } + + // MARK: - Yat + + private func fetchYatData(yat: String?) { + + guard let yat, !yat.isEmpty else { + connectedWallets.removeAll() + return + } + + Yat.api.emojiID.lookupEmojiIDPublisher(emojiId: yat, tags: nil) + .sink { [weak self] in + self?.handle(yatCompletion: $0) + } receiveValue: { [weak self] in + self?.handle(yatResponse: $0) + } + .store(in: &cancellables) + } + + private func handle(yatResponse: LookupResponse) { + + guard let result = yatResponse.result else { return } + + var connectedWallets = [YatRecordTag: String]() + + connectedWallets[.BTCAddress] = result.first { $0.tag == YatRecordTag.BTCAddress.rawValue }?.data.split(separator: "|").firstString + connectedWallets[.ETHAddress] = result.first { $0.tag == YatRecordTag.ETHAddress.rawValue }?.data.split(separator: "|").firstString + connectedWallets[.XMRStandardAddress] = result.first { $0.tag == YatRecordTag.XMRStandardAddress.rawValue }?.data.split(separator: "|").firstString + + self.connectedWallets = connectedWallets + } + + private func handle(yatCompletion: Subscribers.Completion) { + switch yatCompletion { + case .failure: + connectedWallets.removeAll() + errorModel = MessageModel(title: localized("common.error"), message: localized("contact_book.details.popup.error.invalid_yat_response"), type: .error) + case .finished: + break + } + } + + // MARK: - Handlers + + private func handle(model: ContactsManager.Model) { + + if model.type == .internalOrEmojiID, !model.isFFIContact { + isContactExist = false + name = nil + } else { + name = model.name + isContactExist = true + } + + yat = model.externalModel?.yat + updateData(model: model) + } + + private func makeMenuSections(mainMenuItems: [MenuItem], yatMenuItems: [MenuItem]) -> [MenuSection] { + + var menuSections = [MenuSection(title: nil, items: mainMenuItems)] + + if !yatMenuItems.isEmpty { + menuSections.append(MenuSection(title: localized("contact_book.details.menu.section.connected_wallets"), items: yatMenuItems)) + } + + return menuSections + } + + private func makeYatMenuItems(connectedWallets: [YatRecordTag: String]) -> [MenuItem] { + + var menuItems = [MenuItem]() + + if connectedWallets[.BTCAddress] != nil { + menuItems.append(.btcWallet) + } + + if connectedWallets[.ETHAddress] != nil { + menuItems.append(.ethWallet) + } + + if connectedWallets[.XMRStandardAddress] != nil { + menuItems.append(.xmrWallet) + } + + return menuItems + } +} + +private extension YatRecordTag { + + var walletName: String { + switch self { + case .BTCAddress: + return localized("contact_book.details.wallet.bitcoin") + case .ETHAddress: + return localized("contact_book.details.wallet.ethereum") + case .XMRStandardAddress: + return localized("contact_book.details.wallet.monero") + default: + return "" + } + } +} diff --git a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift new file mode 100644 index 00000000..385a211b --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsView.swift @@ -0,0 +1,359 @@ +// ContactDetailsView.swift + +/* + Package MobileWallet + Created by Browncoat on 23/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class ContactDetailsView: BaseNavigationContentView { + + private enum IDElementsState { + case allHidden + case emojiOnly + case yatOnly + case yatHidden + case yatVisible + } + + // MARK: - Subviews + + @View private var avatarView = RoundedAvatarView() + + @View private var nameLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.medium.withSize(17.0) + view.textAlignment = .center + return view + }() + + @View private var emojiIdView: EmojiIdView = { + let view = EmojiIdView() + view.isHidden = true + return view + }() + + @View private var yatLabel: UILabel = { + let view = UILabel() + view.textAlignment = .center + view.font = .Avenir.medium.withSize(20.0) + view.isHidden = true + return view + }() + + @View private var yatButton: BaseButton = { + let view = BaseButton() + view.setImage(Theme.shared.images.yatButtonOn, for: .disabled) + return view + }() + + @View private var tableView = MenuTableView() + private var footer = ContactDetailsViewBottomView() + + // MARK: - Properties + + var editButtonName: String? { + get { navigationBar.rightButton.title(for: .normal) } + set { navigationBar.rightButton.setTitle(newValue, for: .normal) } + } + + 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 yat: String? { + didSet { updateYatView() } + } + + var tableViewSections: [MenuTableView.Section] = [] { + didSet { update(viewModel: tableViewSections) } + } + + var onSelectRow: ((UInt) -> Void)? { + get { tableView.onSelectRow } + set { tableView.onSelectRow = newValue } + } + + var onEditButtonTap: (() -> Void)? + + private var idElementsState: IDElementsState = .allHidden { + didSet { handle(idElementsState: idElementsState) } + } + + // MARK: - Initalisers + + override init() { + super.init() + setupViews() + setupConstraints() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupViews() { + navigationBar.title = localized("contact_book.details.title") + tableView.tableFooterView = footer + } + + private func setupConstraints() { + + [avatarView, nameLabel, emojiIdView, 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.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), + yatButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), + yatButton.centerYAnchor.constraint(equalTo: emojiIdView.centerYAnchor), + yatButton.widthAnchor.constraint(equalToConstant: 24.0), + yatButton.heightAnchor.constraint(equalToConstant: 24.0), + tableView.topAnchor.constraint(equalTo: emojiIdView.bottomAnchor, constant: 20.0), + tableView.leadingAnchor.constraint(equalTo: leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + + navigationBar.onRightButtonAction = { [weak self] in + self?.onEditButtonTap?() + } + + yatButton.onTap = { [weak self] in + self?.toggleYatButton() + } + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.secondary + nameLabel.textColor = theme.text.heading + yatButton.enabledTintColor = theme.icons.active + yatButton.diabledTintColor = theme.icons.inactive + } + + func updateFooter(image: UIImage?, text: String?) { + footer.update(image: image, text: text) + } + + private func update(viewModel: [MenuTableView.Section]) { + tableView.viewModel = viewModel + } + + private func updateEmojiView() { + updateIDElementsState() + guard let emojiModel else { return } + emojiIdView.update(viewModel: emojiModel) + } + + private func updateYatView() { + updateIDElementsState() + yatLabel.text = yat + } + + private func updateIDElementsState() { + let isEmojiIdAAvailable = emojiModel?.emojiID != nil && emojiModel?.emojiID.isEmpty == false + let isYatAvailable = yat != nil && yat?.isEmpty == false + + switch (isEmojiIdAAvailable, isYatAvailable) { + case (true, true): + idElementsState = .yatHidden + case (true, false): + idElementsState = .emojiOnly + case (false, true): + idElementsState = .yatOnly + case (false, false): + idElementsState = .allHidden + } + } + + // MARK: - Handlers + + private func handle(idElementsState: IDElementsState) { + + switch idElementsState { + case .allHidden: + emojiIdView.isHidden = true + yatLabel.isHidden = true + yatButton.isHidden = true + case .emojiOnly: + emojiIdView.isHidden = false + yatLabel.isHidden = true + yatButton.isHidden = true + case .yatOnly: + emojiIdView.isHidden = true + yatLabel.isHidden = false + yatButton.isHidden = false + yatButton.isEnabled = false + case .yatHidden: + emojiIdView.isHidden = false + yatLabel.isHidden = true + yatButton.isHidden = false + yatButton.isEnabled = true + yatButton.setImage(Theme.shared.images.yatButtonOn, for: .normal) + case .yatVisible: + emojiIdView.isHidden = true + yatLabel.isHidden = false + yatButton.isHidden = false + yatButton.isEnabled = true + yatButton.setImage(Theme.shared.images.yatButtonOff, for: .normal) + } + } + + // MARK: - Actions + + private func toggleYatButton() { + + switch idElementsState { + case .yatHidden: + idElementsState = .yatVisible + case .yatVisible: + idElementsState = .yatHidden + case .allHidden, .emojiOnly, .yatOnly: + return + } + } + + // MARK: - Layout + + override func layoutSubviews() { + super.layoutSubviews() + tableView.updateFooterFrame() + } +} + +private final class ContactDetailsViewBottomView: DynamicThemeView { + + // MARK: - Subviews + + @View private var contentView = UIView() + + @View private var imageView: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFit + return view + }() + + @View private var label: UILabel = { + let view = UILabel() + view.font = .Avenir.medium.withSize(15.0) + 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(contentView) + [imageView, label].forEach(contentView.addSubview) + + let constraints = [ + contentView.topAnchor.constraint(equalTo: topAnchor), + contentView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor), + contentView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: bottomAnchor), + contentView.centerXAnchor.constraint(equalTo: centerXAnchor), + imageView.topAnchor.constraint(equalTo: contentView.topAnchor), + imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + imageView.widthAnchor.constraint(equalToConstant: 12.0), + imageView.heightAnchor.constraint(equalToConstant: 12.0), + label.topAnchor.constraint(equalTo: contentView.topAnchor), + label.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0), + label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + imageView.tintColor = theme.text.body + label.textColor = theme.text.body + } + + func update(image: UIImage?, text: String?) { + imageView.image = image + label.text = text + } +} diff --git a/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift new file mode 100644 index 00000000..bea68e6a --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Contact Details/ContactDetailsViewController.swift @@ -0,0 +1,285 @@ +// ContactDetailsViewController.swift + +/* + Package MobileWallet + Created by Browncoat on 23/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import Combine + +final class ContactDetailsViewController: UIViewController { + + // MARK: - Properties + + private let mainView = ContactDetailsView() + private let model: ContactDetailsModel + + private var needUpdate = false + private var cancellables = Set() + + // MARK: - Initialisers + + init(model: ContactDetailsModel) { + self.model = model + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View lifecycle + + override func loadView() { + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupCallbacks() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + updateData() + } + + // MARK: - Setups + + private func setupCallbacks() { + + model.$isContactExist + .receive(on: DispatchQueue.main) + .map { $0 ? localized("common.edit") : localized("common.add") } + .sink { [weak self] in self?.mainView.editButtonName = $0 } + .store(in: &cancellables) + + model.$name + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.mainView.name = $0 } + .store(in: &cancellables) + + model.$viewModel + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { [weak self] in + + if let avatarImage = $0.avatarImage { + self?.mainView.avatar = .image(avatarImage) + } else { + self?.mainView.avatar = .text($0.avatarText) + } + + self?.mainView.emojiModel = EmojiIdView.ViewModel(emojiID: $0.emojiID, hex: $0.hex) + self?.mainView.updateFooter(image: $0.contactType.image, text: $0.contactType.text) + } + .store(in: &cancellables) + + model.$yat + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.mainView.yat = $0 } + .store(in: &cancellables) + + model.$menuSections + .receive(on: DispatchQueue.main) + .sink { [weak self] in + self?.mainView.tableViewSections = $0.map { MenuTableView.Section(title: $0.title, items: $0.items.map { $0.viewModel }) } + } + .store(in: &cancellables) + + model.$action + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.handle(action: $0) } + .store(in: &cancellables) + + model.$errorModel + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { PopUpPresenter.show(message: $0) } + .store(in: &cancellables) + + mainView.onSelectRow = { [weak self] in + self?.model.perform(actionID: $0) + } + + mainView.onEditButtonTap = { [weak self] in + self?.showEditForm() + } + } + + // MARK: - Handlers + + private func handle(action: ContactDetailsModel.Action) { + switch action { + case let .sendTokens(paymentInfo): + moveToSendTokensScreen(paymentInfo: paymentInfo) + case let .moveToLinkContactScreen(model): + moveToLinkContactScreen(model: model) + case let .showUnlinkConfirmationDialog(emojiID, name): + showUnlinkConfirmationDialog(emojiID: emojiID, name: name) + case let .showUnlinkSuccessDialog(emojiID, name): + showUnlinkSuccessDialog(emojiID: emojiID, name: name) + case .removeContactConfirmation: + showRemoveContactConfirmationDialog() + case .endFlow: + endFlow() + } + } + + // MARK: - Actions + + private func updateData() { + guard needUpdate else { + needUpdate = true + return + } + model.updateData() + } + + private func showEditForm() { + + var nameComponents: [String] = model.isContactExist ? model.nameComponents : model.nameComponents.map { _ in "" } + var yat: String = model.yat ?? "" + let models: [ContactBookFormView.TextFieldViewModel] + + if model.hasSplittedName { + models = [ + ContactBookFormView.TextFieldViewModel( + placeholder: localized("contact_book.details.edit_form.text_field.first_name"), + text: nameComponents[0], + isEmojiKeyboardVisible: false, + callback: { nameComponents[0] = $0 } + ), + ContactBookFormView.TextFieldViewModel( + placeholder: localized("contact_book.details.edit_form.text_field.last_name"), + text: nameComponents[1], + isEmojiKeyboardVisible: false, + callback: { nameComponents[1] = $0 } + ), + ContactBookFormView.TextFieldViewModel( + placeholder: localized("contact_book.details.edit_form.text_field.yat"), + text: yat, + isEmojiKeyboardVisible: true, + callback: { yat = $0 } + ) + ] + } else { + models = [ + ContactBookFormView.TextFieldViewModel( + placeholder: localized("contact_book.details.edit_form.text_field.name"), + text: nameComponents[0], + isEmojiKeyboardVisible: false, + callback: { nameComponents[0] = $0 } + ) + ] + } + + let title = model.isContactExist ? localized("contact_book.details.edit_form.title.edit") : localized("contact_book.details.edit_form.title.add") + let formView = ContactBookFormView(title: title, textFieldsModels: models) + let overlay = FormOverlay(formView: formView) + + overlay.onClose = { [weak self] in + self?.model.update(nameComponents: nameComponents, yat: yat) + } + + present(overlay, animated: true) + } + + private func moveToSendTokensScreen(paymentInfo: PaymentInfo) { + AppRouter.presentSendTransaction(paymentInfo: paymentInfo) + } + + private func moveToLinkContactScreen(model: ContactsManager.Model) { + let controller = LinkContactsConstructor.buildScene(contactModel: model) + navigationController?.pushViewController(controller, animated: true) + } + + private func showRemoveContactConfirmationDialog() { + + let model = PopUpDialogModel( + title: localized("contact_book.details.popup.delete_contact.title"), + message: localized("contact_book.details.popup.delete_contact.message"), + buttons: [ + PopUpDialogButtonModel(title: localized("contact_book.details.popup.delete_contact.button.ok"), type: .destructive, callback: { [weak self] in self?.model.removeContact() }), + PopUpDialogButtonModel(title: localized("common.cancel"), type: .text) + ], + hapticType: .none) + + PopUpPresenter.showPopUp(model: model) + } + + private func showUnlinkConfirmationDialog(emojiID: String, name: String) { + PopUpPresenter.showUnlinkConfirmationDialog(emojiID: emojiID, name: name) { [weak self] in + self?.model.unlinkContact() + } + } + + private func showUnlinkSuccessDialog(emojiID: String, name: String) { + PopUpPresenter.showUnlinkSuccessDialog(emojiID: emojiID, name: name) + } + + private func endFlow() { + navigationController?.popViewController(animated: true) + } +} + +private extension ContactDetailsModel.MenuItem { + + var viewModel: MenuCell.ViewModel { + switch self { + case .send: + return MenuCell.ViewModel(id: rawValue, title: localized("contact_book.details.menu.option.send"), isArrowVisible: true, isDestructive: false) + case .addToFavorites: + return MenuCell.ViewModel(id: rawValue, title: localized("contact_book.details.menu.option.add_to_favorites"), isArrowVisible: false, isDestructive: false) + case .removeFromFavorites: + return MenuCell.ViewModel(id: rawValue, title: localized("contact_book.details.menu.option.remove_from_favorites"), isArrowVisible: false, isDestructive: false) + case .linkContact: + return MenuCell.ViewModel(id: rawValue, title: localized("contact_book.details.menu.option.link"), isArrowVisible: true, isDestructive: false) + case .unlinkContact: + return MenuCell.ViewModel(id: rawValue, title: localized("contact_book.details.menu.option.unlink"), isArrowVisible: true, isDestructive: false) + case .removeContact: + return MenuCell.ViewModel(id: rawValue, title: localized("contact_book.details.menu.option.delete"), isArrowVisible: false, isDestructive: true) + case .btcWallet: + return MenuCell.ViewModel(id: rawValue, title: localized("contact_book.details.menu.option.bitcoin"), isArrowVisible: true, isDestructive: false) + case .ethWallet: + return MenuCell.ViewModel(id: rawValue, title: localized("contact_book.details.menu.option.ethereum"), isArrowVisible: true, isDestructive: false) + case .xmrWallet: + return MenuCell.ViewModel(id: rawValue, title: localized("contact_book.details.menu.option.monero"), isArrowVisible: true, isDestructive: false) + } + } +} diff --git a/MobileWallet/Screens/Contact Book/Contact Details/Edit Form/ContactBookFormView.swift b/MobileWallet/Screens/Contact Book/Contact Details/Edit Form/ContactBookFormView.swift new file mode 100644 index 00000000..63b83d26 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Contact Details/Edit Form/ContactBookFormView.swift @@ -0,0 +1,181 @@ +// ContactBookFormView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 01/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon +import Combine + +final class ContactBookFormView: DynamicThemeView, FormShowable { + + struct TextFieldViewModel { + let placeholder: String? + let text: String? + let isEmojiKeyboardVisible: Bool + let callback: ((String) -> Void)? + } + + // MARK: - Subviews + + @View private var titleBar: NavigationBar = { + let view = NavigationBar() + view.backButtonType = .none + return view + }() + + @View private var stackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + view.spacing = 20.0 + return view + }() + + // MARK: - Properties + + private var cancellables = Set() + + // MARK: - FormShowable + + var focusedView: UIView? { stackView.arrangedSubviews.first } + var initalReturkKeyType: UIReturnKeyType = .default + var onCloseAction: (() -> Void)? + + // MARK: - Initialisers + + init(title: String?, textFieldsModels: [TextFieldViewModel]) { + super.init() + setupTitleBar(title: title) + setupConstraints() + setupCallbacks() + update(textFieldsModels: textFieldsModels) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupTitleBar(title: String?) { + titleBar.title = title + titleBar.rightButton.setTitle(localized("common.done"), for: .normal) + } + + private func setupConstraints() { + + [titleBar, stackView].forEach(addSubview) + + let constraints = [ + titleBar.topAnchor.constraint(equalTo: topAnchor), + titleBar.leadingAnchor.constraint(equalTo: leadingAnchor), + titleBar.trailingAnchor.constraint(equalTo: trailingAnchor), + stackView.topAnchor.constraint(equalTo: titleBar.bottomAnchor, constant: 26.0), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 26.0), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -26.0), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -26.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + titleBar.onRightButtonAction = { [weak self] in + self?.onCloseAction?() + } + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.primary + } + + private func update(textFieldsModels: [TextFieldViewModel]) { + + let textFields = textFieldsModels + .enumerated() + .map { index, model in + let textField = FormTextField() + + textField.publisher + .sink { model.callback?($0) } + .store(in: &cancellables) + + textField.placeholder = model.placeholder + textField.text = model.text + textField.isEmojiKeyboardVisible = model.isEmojiKeyboardVisible + textField.onReturnPressed = { [weak self] in + self?.handleOnReturnPressedAction(index: index) + } + + return textField + } + + textFields + .forEach { [weak self] in + $0.returnKeyType = .next + self?.stackView.addArrangedSubview($0) + } + + textFields.last?.returnKeyType = .done + + initalReturkKeyType = textFields.count > 1 ? .next : .done + } + + // MARK: - Actions + + private func handleOnReturnPressedAction(index: Int) { + let nextIndex = index + 1 + guard stackView.arrangedSubviews.count > nextIndex else { + onCloseAction?() + return + } + stackView.arrangedSubviews[nextIndex].becomeFirstResponder() + } + + override var intrinsicContentSize: CGSize { + + guard #available(iOS 16.0, *) else { + return sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) + } + + return super.intrinsicContentSize + } +} diff --git a/MobileWallet/Screens/Contact Book/Extensions/ContactType+Data.swift b/MobileWallet/Screens/Contact Book/Extensions/ContactType+Data.swift new file mode 100644 index 00000000..0cf7ff76 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Extensions/ContactType+Data.swift @@ -0,0 +1,70 @@ +// ContactType+Data.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 14/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +extension ContactsManager.ContactType { + + var image: UIImage? { + switch self { + case .internalOrEmojiID: + return .icons.contactTypes.internal + case .external: + return .icons.contactTypes.external + case .linked: + return .icons.contactTypes.linked + case .empty: + return nil + } + } + + var text: String? { + switch self { + case .internalOrEmojiID: + return localized("contact_book.contact_type.internal") + case .external: + return localized("contact_book.contact_type.external") + case .linked: + return localized("contact_book.contact_type.linked") + case .empty: + return nil + } + } +} diff --git a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsConstructor.swift b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsConstructor.swift new file mode 100644 index 00000000..8984b5fc --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsConstructor.swift @@ -0,0 +1,47 @@ +// LinkContactsConstructor.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 07/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum LinkContactsConstructor { + + static func buildScene(contactModel: ContactsManager.Model) -> LinkContactsViewController { + let model = LinkContactsModel(contactModel: contactModel) + return LinkContactsViewController(model: model) + } +} diff --git a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsModel.swift b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsModel.swift new file mode 100644 index 00000000..5453998e --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsModel.swift @@ -0,0 +1,167 @@ +// LinkContactsModel.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 07/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +final class LinkContactsModel { + + enum Action { + case showConfirmation(emojiID: String, name: String) + case showSuccess(emojiID: String, name: String) + } + + // MARK: - View Model + + @Published var searchText: String = "" + + @Published private(set) var name: String? + @Published private(set) var models: [ContactsManager.Model] = [] + @Published private(set) var action: Action? + @Published private(set) var errorModel: MessageModel? + + // MARK: - Properties + + @Published private var allModels: [ContactsManager.Model] = [] + + private let contactsManager = ContactsManager() + private let contactModel: ContactsManager.Model + + private var unconfirmedInternalModel: InternalContactsManager.ContactModel? + private var unconfirmedExternalModel: ExternalContactsManager.ContactModel? + private var cancellables = Set() + + init(contactModel: ContactsManager.Model) { + self.contactModel = contactModel + setupData() + setupCallbacks() + } + + // MARK: - Setups + + private func setupData() { + + if contactModel.type == .linked { + errorModel = ErrorMessageManager.errorModel(forError: nil) + return + } + + Task { + do { + try await contactsManager.fetchModels() + updateModels() + } catch { + errorModel = ErrorMessageManager.errorModel(forError: error) + } + } + + name = contactModel.internalModel?.emojiID.obfuscatedText ?? contactModel.externalModel?.fullname + } + + private func updateModels() { + switch contactModel.type { + case .internalOrEmojiID: + allModels = contactsManager.externalModels + case .external: + allModels = contactsManager.tariContactModels.filter { $0.type == .internalOrEmojiID } + case .linked, .empty: + allModels = [] + } + } + + private func setupCallbacks() { + + Publishers.CombineLatest($allModels, $searchText) + .map { models, searchText in + guard !searchText.isEmpty else { return models } + return models.filter { $0.name.range(of: searchText, options: .caseInsensitive) != nil } + } + .sink { [weak self] in self?.models = $0 } + .store(in: &cancellables) + } + + // MARK: - Actions + + func selectModel(index: IndexPath) { + + guard models.count > index.row else { return } + + let model = models[index.row] + + let internalContact: InternalContactsManager.ContactModel + let externalContact: ExternalContactsManager.ContactModel + + if let internalModel = contactModel.internalModel, let externalModel = model.externalModel { + internalContact = internalModel + externalContact = externalModel + } else if let internalModel = model.internalModel, let externalModel = contactModel.externalModel { + internalContact = internalModel + externalContact = externalModel + } else { + errorModel = ErrorMessageManager.errorModel(forError: nil) + return + } + + unconfirmedInternalModel = internalContact + unconfirmedExternalModel = externalContact + + action = .showConfirmation(emojiID: internalContact.emojiID.obfuscatedText, name: externalContact.fullname) + } + + func linkContacts() { + + guard let unconfirmedInternalModel, let unconfirmedExternalModel else { + errorModel = ErrorMessageManager.errorModel(forError: nil) + return + } + + do { + try contactsManager.link(internalContact: unconfirmedInternalModel, externalContact: unconfirmedExternalModel) + action = .showSuccess(emojiID: unconfirmedInternalModel.emojiID.obfuscatedText, name: unconfirmedExternalModel.fullname) + cancelLinkContacts() + } catch { + errorModel = ErrorMessageManager.errorModel(forError: error) + } + } + + func cancelLinkContacts() { + unconfirmedInternalModel = nil + unconfirmedExternalModel = nil + } +} diff --git a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsView.swift b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsView.swift new file mode 100644 index 00000000..e6b6348b --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsView.swift @@ -0,0 +1,181 @@ +// LinkContactsView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 07/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon +import Combine + +final class LinkContactsView: BaseNavigationContentView { + + // MARK: - Subviews + + @View private(set) var infoLabel: StylizedLabel = { + let view = StylizedLabel() + view.textAlignment = .center + view.normalFont = .Avenir.medium.withSize(14.0) + view.boldFont = .Avenir.heavy.withSize(14.0) + view.numberOfLines = 0 + return view + }() + + @View private(set) var searchTextField: SearchTextField = { + let view = SearchTextField() + view.placeholder = localized("contact_book.link_contacts.text_field.search") + return view + }() + + @View private var tableView: UITableView = { + let view = UITableView() + view.estimatedRowHeight = 44.0 + view.rowHeight = UITableView.automaticDimension + view.keyboardDismissMode = .interactive + view.register(type: ContactBookCell.self) + view.separatorInset = UIEdgeInsets(top: 0.0, left: 22.0, bottom: 0.0, right: 22.0) + return view + }() + + // MARK: - Properties + + var name: String = "" { + didSet { updateInfoLabel(name: name) } + } + + var viewModels: [ContactBookCell.ViewModel] = [] { + didSet { update(viewModels: viewModels) } + } + + var searchText: AnyPublisher { searchTextSubject.eraseToAnyPublisher() } + var onSelectRow: ((IndexPath) -> Void)? + + private var dataSource: UITableViewDiffableDataSource? + private let searchTextSubject = CurrentValueSubject("") + private var cancellables = Set() + + // MARK: - Initialisers + + override init() { + super.init() + setupSubviews() + setupConstraints() + setupTableView() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupSubviews() { + navigationBar.title = localized("contact_book.link_contacts.title") + } + + private func setupConstraints() { + + [infoLabel, searchTextField, tableView].forEach(addSubview) + + let constraints = [ + infoLabel.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 22.0), + infoLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 22.0), + infoLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -22.0), + searchTextField.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20.0), + searchTextField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + searchTextField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0), + tableView.topAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 20.0), + tableView.leadingAnchor.constraint(equalTo: leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupTableView() { + + dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, model in + let cell = tableView.dequeueReusableCell(type: ContactBookCell.self, indexPath: indexPath) + cell.update(viewModel: model) + return cell + } + + tableView.dataSource = dataSource + tableView.delegate = self + } + + private func setupCallbacks() { + searchTextField.bind(withSubject: searchTextSubject, storeIn: &cancellables) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.secondary + tableView.backgroundColor = theme.backgrounds.primary + tableView.separatorColor = theme.neutral.secondary + infoLabel.textColor = theme.text.body + } + + func updateInfoLabel(name: String) { + infoLabel.textComponents = [ + StylizedLabel.StylizedText(text: localized("contact_book.link_contacts.lables.info.part1") + " ", style: .normal), + StylizedLabel.StylizedText(text: name, style: .bold), + StylizedLabel.StylizedText(text: ".", style: .normal) + ] + } + + func update(viewModels: [ContactBookCell.ViewModel]) { + + var snapshot = NSDiffableDataSourceSnapshot() + + snapshot.appendSections([0]) + snapshot.appendItems(viewModels) + + dataSource?.apply(snapshot: snapshot) + } +} + +extension LinkContactsView: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + onSelectRow?(indexPath) + } +} diff --git a/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift new file mode 100644 index 00000000..2b984bab --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Link Contacts/LinkContactsViewController.swift @@ -0,0 +1,163 @@ +// LinkContactsViewController.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 07/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import Combine + +final class LinkContactsViewController: UIViewController { + + // MARK: - Properties + + let model: LinkContactsModel + let mainView = LinkContactsView() + + private var cancellables = Set() + + // MARK: - Initialisers + + init(model: LinkContactsModel) { + self.model = model + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View Lifecycle + + override func loadView() { + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + + model.$name + .compactMap { $0 } + .sink { [weak self] in self?.mainView.name = $0 } + .store(in: &cancellables) + + model.$models + .map { $0.map { ContactBookCell.ViewModel(id: $0.id, name: $0.name, avatarText: $0.avatar, avatarImage: $0.avatarImage, isFavorite: false, menuItems: [], contactTypeImage: nil) }} + .sink { [weak self] in self?.mainView.viewModels = $0 } + .store(in: &cancellables) + + model.$action + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.handle(action: $0) } + .store(in: &cancellables) + + model.$errorModel + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { PopUpPresenter.show(message: $0) } + .store(in: &cancellables) + + mainView.searchText + .assign(to: \.searchText, on: model) + .store(in: &cancellables) + + mainView.onSelectRow = { [weak self] in + self?.model.selectModel(index: $0) + } + } + + // MARK: - Handlers + + 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) + } + } + + // MARK: - Actions + + private func showConfirmationDialog(emojiID: 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: name, style: .bold) + ], + buttons: [ + PopUpDialogButtonModel(title: localized("common.confirm"), type: .normal, callback: { [weak self] in self?.model.linkContacts() }), + PopUpDialogButtonModel(title: localized("common.cancel"), type: .text, callback: { [weak self] in self?.model.cancelLinkContacts() }) + ], + hapticType: .none + ) + + PopUpPresenter.showPopUp(model: model) + } + + private func showSuccessDialog(emojiID: 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: name, style: .bold) + ], + buttons: [ + PopUpDialogButtonModel(title: localized("common.close"), type: .text) + ], + hapticType: .none + ) + + PopUpPresenter.showPopUp(model: model) + navigationController?.popViewController(animated: true) + } +} diff --git a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift new file mode 100644 index 00000000..73d4a2d2 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListView.swift @@ -0,0 +1,270 @@ +// ContactBookContactListView.swift + +/* + Package MobileWallet + Created by Browncoat on 21/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class ContactBookContactListView: DynamicThemeView { + + struct Section { + let title: String? + let items: [ContactBookCell.ViewModel] + } + + // MARK: - Subviews + + @View private var tableView: UITableView = { + let view = UITableView() + view.estimatedRowHeight = 44.0 + view.rowHeight = UITableView.automaticDimension + view.keyboardDismissMode = .interactive + view.register(type: ContactBookCell.self) + view.register(headerFooterType: MenuTableHeaderView.self) + view.separatorInset = UIEdgeInsets(top: 0.0, left: 22.0, bottom: 0.0, right: 22.0) + return view + }() + + @View private var placeholderView: ContactBookListPlaceholder = { + let view = ContactBookListPlaceholder() + view.isHidden = true + return view + }() + + private let tableFooterView = ContactBookContactListFooter() + + // MARK: - Properties + + var viewModels: [Section] = [] { + didSet { update(sections: viewModels) } + } + + var placeholderViewModel: ContactBookListPlaceholder.ViewModel? { + didSet { update(placeholderViewModel: placeholderViewModel) } + } + + var isPlaceholderVisible: Bool = false { + didSet { placeholderView.isHidden = !isPlaceholderVisible } + } + + var isFooterVisible: Bool = false { + didSet { tableView.tableFooterView = isFooterVisible ? tableFooterView : nil } + } + + var onButtonTap: ((UUID, UInt) -> Void)? + + var onTap: (() -> Void)? { + get { tableFooterView.onTap } + set { tableFooterView.onTap = newValue } + } + + private var expandedIndex: IndexPath? { + didSet { updateCellsState() } + } + + private var dataSource: UITableViewDiffableDataSource? + + // MARK: - Initialisers + + override init() { + super.init() + setupConstraints() + setupTableView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [tableView, placeholderView].forEach(addSubview) + + let constraints = [ + tableView.topAnchor.constraint(equalTo: topAnchor), + tableView.leadingAnchor.constraint(equalTo: leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: bottomAnchor), + placeholderView.topAnchor.constraint(equalTo: topAnchor), + placeholderView.leadingAnchor.constraint(equalTo: leadingAnchor), + placeholderView.trailingAnchor.constraint(equalTo: trailingAnchor), + placeholderView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupTableView() { + + let dataSource = UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, model in + let cell = tableView.dequeueReusableCell(type: ContactBookCell.self, indexPath: indexPath) + cell.update(viewModel: model) + cell.updateCell(isExpanded: indexPath == self?.expandedIndex, withAnmiation: false) + cell.onButtonTap = { + self?.onButtonTap?(model.id, $0) + self?.expandedIndex = nil + } + cell.onExpand = { [weak self, tableView] in + guard let index = tableView.indexPath(for: cell) else { return } + self?.expandedIndex = index + } + + return cell + } + + tableView.delegate = self + tableView.dataSource = dataSource + + self.dataSource = dataSource + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + tableView.backgroundColor = theme.backgrounds.primary + tableView.separatorColor = theme.neutral.secondary + } + + private func update(sections: [Section]) { + + var snapshot = NSDiffableDataSourceSnapshot() + + sections + .enumerated() + .forEach { + snapshot.appendSections([$0]) + snapshot.appendItems($1.items) + } + + dataSource?.apply(snapshot: snapshot) + } + + private func update(placeholderViewModel: ContactBookListPlaceholder.ViewModel?) { + guard let placeholderViewModel else { return } + placeholderView.update(viewModel: placeholderViewModel) + } + + private func updateCellsState() { + tableView.visibleCells + .compactMap { $0 as? ContactBookCell } + .forEach { [weak self] in + guard let index = self?.tableView.indexPath(for: $0), index != self?.expandedIndex else { return } + $0.updateCell(isExpanded: false, withAnmiation: false) + } + } + + // MARK: - Layout + + override func layoutSubviews() { + super.layoutSubviews() + tableView.updateFooterFrame() + } +} + +extension ContactBookContactListView: UITableViewDelegate { + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + + guard let title = viewModels[section].title else { return nil } + + let headerView = tableView.dequeueReusableHeaderFooterView(type: MenuTableHeaderView.self) + headerView.label.text = title + return headerView + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + viewModels[section].title == nil ? 0.0 : UITableView.automaticDimension + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let cell = tableView.cellForRow(at: indexPath) as? ContactBookCell else { return } + cell.updateCell(isExpanded: !cell.isExpanded, withAnmiation: true) + } +} + +private class ContactBookContactListFooter: UIView { + + // MARK: - Subiews + + @View private var button: TextButton = { + let view = TextButton() + view.setVariation(.secondary) + view.setTitle(localized("contact_book.section.list.placeholder.button"), for: .normal) + return view + }() + + // MARK: - Initialisers + + init() { + super.init(frame: .zero) + setupConstraints() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Properties + + var onTap: (() -> Void)? + + // MARK: - Setups + + private func setupConstraints() { + + addSubview(button) + + let constraints = [ + button.topAnchor.constraint(equalTo: topAnchor), + button.leadingAnchor.constraint(equalTo: leadingAnchor), + button.trailingAnchor.constraint(equalTo: trailingAnchor), + button.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + button.onTap = { [weak self] in self?.onTap?() } + } +} diff --git a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListViewController.swift b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListViewController.swift new file mode 100644 index 00000000..9d8f04d8 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookContactListViewController.swift @@ -0,0 +1,92 @@ +// ContactBookContactListViewController.swift + +/* + Package MobileWallet + Created by Browncoat on 21/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +final class ContactBookContactListViewController: UIViewController { + + // MARK: - Properties + + var models: [ContactBookContactListView.Section] { + get { mainView.viewModels } + set { mainView.viewModels = newValue } + } + + var placeholderViewModel: ContactBookListPlaceholder.ViewModel? { + get { mainView.placeholderViewModel } + set { mainView.placeholderViewModel = newValue } + } + + var isPlaceholderVisible: Bool { + get { mainView.isPlaceholderVisible } + set { mainView.isPlaceholderVisible = newValue } + } + + var isFooterVisible: Bool { + get { mainView.isFooterVisible } + set { mainView.isFooterVisible = newValue } + } + + var onFooterTap: (() -> Void)? { + get { mainView.onTap } + set { mainView.onTap = newValue } + } + + var onButtonTap: ((UUID, UInt) -> Void)? + + private let mainView = ContactBookContactListView() + + // MARK: - View Lifecycle + + override func loadView() { + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupCallbacks() + } + + // MARK: - Sutups + + private func setupCallbacks() { + mainView.onButtonTap = { [weak self] in self?.onButtonTap?($0, $1) } + } +} diff --git a/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift new file mode 100644 index 00000000..3722fa54 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/Contact List/ContactBookListPlaceholder.swift @@ -0,0 +1,153 @@ +// ContactBookListPlaceholder.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 13/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class ContactBookListPlaceholder: DynamicThemeView { + + struct ViewModel { + let image: UIImage? + let titleComponents: [StylizedLabel.StylizedText] + let messageComponents: [StylizedLabel.StylizedText] + let actionButtonTitle: String? + let actionButtonCallback: (() -> Void)? + } + + // MARK: - Subviews + + @View private var backgroundImageView: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFit + view.image = .security.onboarding.background + return view + }() + + @View private var imageView: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFit + return view + }() + + @View private var titleLabel: StylizedLabel = { + let view = StylizedLabel() + view.textAlignment = .center + view.normalFont = .Avenir.medium.withSize(18.0) + view.boldFont = .Avenir.black.withSize(18.0) + view.separator = " " + view.numberOfLines = 0 + return view + }() + + @View private var messageLabel: StylizedLabel = { + let view = StylizedLabel() + view.textAlignment = .center + view.normalFont = .Avenir.medium.withSize(14.0) + view.boldFont = .Avenir.black.withSize(14.0) + view.separator = " " + view.numberOfLines = 0 + return view + }() + + @View private var actionButton: TextButton = { + let view = TextButton() + view.setVariation(.secondary) + 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() { + + [backgroundImageView, imageView, titleLabel, messageLabel, actionButton].forEach(addSubview) + + let constraints = [ + backgroundImageView.topAnchor.constraint(equalTo: topAnchor, constant: 44.0), + backgroundImageView.centerXAnchor.constraint(equalTo: centerXAnchor), + backgroundImageView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: UIScreen.isSmallScreen ? 0.2 : 0.33), + imageView.topAnchor.constraint(equalTo: backgroundImageView.topAnchor), + imageView.leadingAnchor.constraint(equalTo: backgroundImageView.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: backgroundImageView.trailingAnchor), + imageView.bottomAnchor.constraint(equalTo: backgroundImageView.bottomAnchor), + titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 20.0), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 35.0), + titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -35.0), + messageLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20.0), + messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 35.0), + messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -35.0), + actionButton.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 12.0), + actionButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 35.0), + actionButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -35.0), + actionButton.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -60.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.primary + backgroundImageView.tintColor = theme.brand.purple + imageView.tintColor = theme.icons.default + titleLabel.textColor = theme.text.heading + messageLabel.textColor = theme.text.body + } + + func update(viewModel: ViewModel) { + imageView.image = viewModel.image + titleLabel.textComponents = viewModel.titleComponents + messageLabel.textComponents = viewModel.messageComponents + actionButton.setTitle(viewModel.actionButtonTitle, for: .normal) + actionButton.onTap = viewModel.actionButtonCallback + } +} diff --git a/MobileWallet/Screens/Contact Book/List/ContactBookConstructor.swift b/MobileWallet/Screens/Contact Book/List/ContactBookConstructor.swift new file mode 100644 index 00000000..5878d9b3 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/ContactBookConstructor.swift @@ -0,0 +1,47 @@ +// ContactBookConstructor.swift + +/* + Package MobileWallet + Created by Browncoat on 09/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum ContactBookConstructor { + + static func buildScene() -> ContactBookViewController { + let model = ContactBookModel() + return ContactBookViewController(model: model) + } +} diff --git a/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift b/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift new file mode 100644 index 00000000..24a83259 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/ContactBookModel.swift @@ -0,0 +1,239 @@ +// ContactBookModel.swift + +/* + Package MobileWallet + Created by Browncoat on 09/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import Combine + +final class ContactBookModel { + + struct ContactSection { + let title: String? + let viewModels: [ContactViewModel] + } + + struct ContactViewModel: Identifiable { + let id: UUID + let name: String + let avatar: String + let avatarImage: UIImage? + let isFavorite: Bool + let menuItems: [ContactBookModel.MenuItem] + let type: ContactsManager.ContactType + } + + enum MenuItem: UInt { + case send + case addToFavorites + case removeFromFavorites + case link + case unlink + case details + } + + 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) + } + + // MARK: - View Model + + @Published var searchText: String = "" + + @Published private(set) var contactsList: [ContactSection] = [] + @Published private(set) var favoriteContactsList: [ContactSection] = [] + @Published private(set) var areContactsAvailable: Bool = false + @Published private(set) var areFavoriteContactsAvailable: Bool = false + @Published private(set) var errorModel: MessageModel? + @Published private(set) var action: Action? + @Published private(set) var isPermissionGranted: Bool = false + + // MARK: - Properties + + @Published private var allContactList: [ContactSection] = [] + + private let contactsManager = ContactsManager() + + private var contacts: [ContactsManager.Model] = [] + private var cancellables = Set() + + // MARK: - Initialisers + + init() { + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + + $allContactList + .sink { [weak self] in + let models = $0.flatMap { $0.viewModels } + self?.areContactsAvailable = !models.isEmpty + self?.areFavoriteContactsAvailable = models.first { $0.isFavorite } != nil + } + .store(in: &cancellables) + + let contactsPublisher = Publishers.CombineLatest($allContactList, $searchText) + .map { sections, searchText in + guard !searchText.isEmpty else { return sections } + return sections.map { ContactSection(title: $0.title, viewModels: $0.viewModels.filter { $0.name.range(of: searchText, options: .caseInsensitive) != nil }) } + } + .share() + + contactsPublisher + .map { $0.filter { !$0.viewModels.isEmpty } } + .sink { [weak self] in self?.contactsList = $0 } + .store(in: &cancellables) + + contactsPublisher + .map { $0.map { ContactSection(title: $0.title, viewModels: $0.viewModels.filter { $0.isFavorite }) }} + .map { $0.filter { !$0.viewModels.isEmpty } } + .sink { [weak self] in self?.favoriteContactsList = $0 } + .store(in: &cancellables) + } + + // MARK: - View Model + + func fetchContacts() { + + Task { + do { + try await contactsManager.fetchModels() + + var sections: [ContactSection] = [] + + let internalContacts = contactsManager.tariContactModels + let externalContacts = contactsManager.externalModels + + let internalContactSection = internalContacts + .map { ContactViewModel(id: $0.id, name: $0.name, avatar: $0.avatar, avatarImage: $0.avatarImage, isFavorite: $0.isFavorite, menuItems: $0.menuItems, type: $0.type) } + + let externalContactSection = externalContacts + .map { ContactViewModel(id: $0.id, name: $0.name, avatar: $0.avatar, avatarImage: $0.avatarImage, isFavorite: false, menuItems: $0.menuItems, type: $0.type) } + + if !internalContactSection.isEmpty { + sections.append(ContactSection(title: nil, viewModels: internalContactSection)) + } + + if !externalContactSection.isEmpty { + sections.append(ContactSection(title: localized("contact_book.section.phone_contacts"), viewModels: externalContactSection)) + } + + contacts = internalContacts + externalContacts + allContactList = sections + + } catch { + errorModel = ErrorMessageManager.errorModel(forError: error) + } + + isPermissionGranted = contactsManager.isPermissionGranted + } + } + + func performAction(contactID: UUID, menuItemID: UInt) { + + guard let model = contacts.first(where: { $0.id == 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) + return + } + } + + 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) + } + } + + 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) + } + } + + // MARK: - Handlers + + 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) + } +} diff --git a/MobileWallet/Screens/Contact Book/List/ContactBookView.swift b/MobileWallet/Screens/Contact Book/List/ContactBookView.swift new file mode 100644 index 00000000..a51cd3f0 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/ContactBookView.swift @@ -0,0 +1,126 @@ +// ContactBookView.swift + +/* + Package MobileWallet + Created by Browncoat on 09/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon +import Combine + +final class ContactBookView: BaseNavigationContentView { + + // MARK: - Subviews + + @View private var searchTextField: SearchTextField = { + let view = SearchTextField() + view.placeholder = localized("contact_book.search_bar.placeholder") + return view + }() + + // MARK: - Properties + + var searchText: AnyPublisher { searchTextSubject.eraseToAnyPublisher() } + var onAddContactButtonTap: (() -> Void)? + + private let searchTextSubject = CurrentValueSubject("") + private var cancellables = Set() + + // MARK: - Initialisers + + override init() { + super.init() + setupSuviews() + setupConstraints() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + func setup(pagerView: UIView) { + + addSubview(pagerView) + + let constraints = [ + pagerView.topAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 20.0), + pagerView.leadingAnchor.constraint(equalTo: leadingAnchor), + pagerView.trailingAnchor.constraint(equalTo: trailingAnchor), + pagerView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupSuviews() { + navigationBar.title = localized("contact_book.title") + navigationBar.backButtonType = .none + navigationBar.rightButton.setImage(.contactBook.addContact, for: .normal) + } + + private func setupConstraints() { + + addSubview(searchTextField) + + let constraints = [ + searchTextField.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 20.0), + searchTextField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + searchTextField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + + searchTextField.bind(withSubject: searchTextSubject, storeIn: &cancellables) + + navigationBar.onRightButtonAction = { [weak self] in + self?.onAddContactButtonTap?() + } + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.secondary + } +} diff --git a/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift b/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift new file mode 100644 index 00000000..fcaf12e8 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/ContactBookViewController.swift @@ -0,0 +1,302 @@ +// ContactBookViewController.swift + +/* + Package MobileWallet + Created by Browncoat on 09/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import Combine + +final class ContactBookViewController: UIViewController { + + // MARK: - Properties + + private let model: ContactBookModel + private let mainView = ContactBookView() + private let pagerViewController = TariPagerViewController() + + private let contactsPageViewController = ContactBookContactListViewController() + private let favoritesPageViewController = ContactBookContactListViewController() + + private var cancellables = Set() + + // MARK: - Initialisers + + init(model: ContactBookModel) { + self.model = model + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View Lifecycle + + override func loadView() { + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupPages() + setupCallbacks() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + model.fetchContacts() + } + + // MARK: - Setups + + private func setupPages() { + + guard let pagerView = pagerViewController.view else { return } + + mainView.setup(pagerView: pagerView) + + pagerViewController.pages = [ + TariPagerViewController.Page(title: localized("contact_book.pager.tab.contacts"), controller: contactsPageViewController), + TariPagerViewController.Page(title: localized("contact_book.pager.tab.favorites"), controller: favoritesPageViewController) + ] + + favoritesPageViewController.placeholderViewModel = ContactBookListPlaceholder.ViewModel( + image: .contactBook.placeholders.favoritesContactsList, + titleComponents: [ + StylizedLabel.StylizedText(text: localized("contact_book.section.favorites.placeholder.title.part1"), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.section.favorites.placeholder.title.part2.bold"), style: .bold) + ], + messageComponents: [ + StylizedLabel.StylizedText(text: localized("contact_book.section.favorites.placeholder.message.part1"), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.section.favorites.placeholder.message.part2.bold"), style: .bold), + StylizedLabel.StylizedText(text: localized("contact_book.section.favorites.placeholder.message.part3"), style: .normal) + ], + actionButtonTitle: nil, + actionButtonCallback: nil + ) + } + + private func setupCallbacks() { + + model.$contactsList + .compactMap { [weak self] in self?.map(sections: $0) } + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.contactsPageViewController.models = $0 } + .store(in: &cancellables) + + model.$favoriteContactsList + .compactMap { [weak self] in self?.map(sections: $0) } + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.favoritesPageViewController.models = $0 } + .store(in: &cancellables) + + model.$areContactsAvailable + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.contactsPageViewController.isPlaceholderVisible = !$0 } + .store(in: &cancellables) + + model.$areFavoriteContactsAvailable + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.favoritesPageViewController.isPlaceholderVisible = !$0 } + .store(in: &cancellables) + + model.$isPermissionGranted + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.updatePlaceholders(isPermissionGranted: $0) } + .store(in: &cancellables) + + model.$action + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.handle(action: $0) } + .store(in: &cancellables) + + model.$errorModel + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { PopUpPresenter.show(message: $0) } + .store(in: &cancellables) + + mainView.searchText + .receive(on: DispatchQueue.main) + .assign(to: \.searchText, on: model) + .store(in: &cancellables) + + mainView.onAddContactButtonTap = { [weak self] in + self?.moveToAddContactScreen() + } + + contactsPageViewController.onButtonTap = { [weak self] in + self?.model.performAction(contactID: $0, menuItemID: $1) + } + + favoritesPageViewController.onButtonTap = { [weak self] in + self?.model.performAction(contactID: $0, menuItemID: $1) + } + + contactsPageViewController.onFooterTap = { [weak self] in + self?.openAppSettings() + } + } + + // MARK: - Updates + + private func updatePlaceholders(isPermissionGranted: Bool) { + + contactsPageViewController.isFooterVisible = !isPermissionGranted + + guard isPermissionGranted else { + + contactsPageViewController.placeholderViewModel = ContactBookListPlaceholder.ViewModel( + image: .contactBook.placeholders.contactsList, + titleComponents: [ + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.title.part1"), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.title.part2.bold"), style: .bold) + ], + messageComponents: [ + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.without_permission.message.part1"), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.without_permission.message.part2.bold"), style: .bold), + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.without_permission.message.part3"), style: .normal) + ], + actionButtonTitle: localized("contact_book.section.list.placeholder.button"), + actionButtonCallback: { [weak self] in self?.openAppSettings() } + ) + + return + } + + contactsPageViewController.placeholderViewModel = ContactBookListPlaceholder.ViewModel( + image: .contactBook.placeholders.contactsList, + titleComponents: [ + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.title.part1"), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.title.part2.bold"), style: .bold) + ], + messageComponents: [ + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.message.part1"), style: .normal), + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.message.part2.bold"), style: .bold), + StylizedLabel.StylizedText(text: localized("contact_book.section.list.placeholder.message.part3"), style: .normal) + ], + actionButtonTitle: nil, + actionButtonCallback: nil + ) + } + + // MARK: - Handlers + + 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) + } + } + + private func map(sections: [ContactBookModel.ContactSection]) -> [ContactBookContactListView.Section] { + sections.map { + let items = $0.viewModels.map { + let menuItems = $0.menuItems.map { $0.buttonViewModel } + return ContactBookCell.ViewModel(id: $0.id, name: $0.name, avatarText: $0.avatar, avatarImage: $0.avatarImage, isFavorite: $0.isFavorite, menuItems: menuItems, contactTypeImage: $0.type.image) + } + return ContactBookContactListView.Section(title: $0.title, items: items) + } + } + + // MARK: - Actions + + private func moveToAddContactScreen() { + let controller = AddContactConstructor.bulidScene() + navigationController?.pushViewController(controller, animated: true) + } + + private func moveToSendTokensScreen(paymentInfo: PaymentInfo) { + 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() { + guard let url = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(url) + } +} + +private extension ContactBookModel.MenuItem { + + var buttonViewModel: ContactCapsuleMenu.ButtonViewModel { ContactCapsuleMenu.ButtonViewModel(id: rawValue, icon: icon) } + + private var icon: UIImage? { + switch self { + case .send: + return .icons.send + case .addToFavorites: + return .icons.star.filled + case .removeFromFavorites: + return .icons.star.border + case .link: + return .icons.link + case .unlink: + return .icons.unlink + case .details: + return .icons.profile + } + } +} diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift b/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift new file mode 100644 index 00000000..865bd64b --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/Views/ContactBookCell.swift @@ -0,0 +1,178 @@ +// ContactBookCell.swift + +/* + Package MobileWallet + Created by Browncoat on 10/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class ContactBookCell: DynamicThemeCell { + + struct ViewModel: Identifiable, Hashable { + let id: UUID + let name: String + let avatarText: String + let avatarImage: UIImage? + let isFavorite: Bool + let menuItems: [ContactCapsuleMenu.ButtonViewModel] + let contactTypeImage: UIImage? + } + + // MARK: - Subviews + + @View private var avatarMenu = ContactCapsuleMenu() + + @View private var contactTypeBackgroundView: UIView = { + let view = UIView() + view.layer.cornerRadius = 8.0 + return view + }() + + @View private var contactTypeView: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFit + return view + }() + + @View private var nameLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.heavy.withSize(15.0) + return view + }() + + @View private var favoriteView: UIImageView = { + let view = UIImageView() + view.image = .icons.star.filled + view.contentMode = .scaleAspectFit + return view + }() + + // MARK: - Properties + + private(set) var isExpanded: Bool = false + + var onButtonTap: ((UInt) -> Void)? + var onExpand: (() -> Void)? + + // MARK: - Initialisers + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + setupConstraints() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupViews() { + selectionStyle = .none + backgroundColor = .clear + } + + private func setupConstraints() { + + [nameLabel, favoriteView, avatarMenu, contactTypeBackgroundView, contactTypeView].forEach(contentView.addSubview) + + let constraints = [ + avatarMenu.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10.0), + avatarMenu.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 22.0), + avatarMenu.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10.0), + contactTypeBackgroundView.trailingAnchor.constraint(equalTo: avatarMenu.avatarView.trailingAnchor), + contactTypeBackgroundView.bottomAnchor.constraint(equalTo: avatarMenu.avatarView.bottomAnchor), + 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: avatarMenu.avatarView.trailingAnchor, constant: 10.0), + nameLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + favoriteView.leadingAnchor.constraint(equalTo: nameLabel.trailingAnchor, constant: 10.0), + favoriteView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -22.0), + favoriteView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + avatarMenu.onButtonTap = { [weak self] in self?.onButtonTap?($0) } + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + nameLabel.textColor = theme.text.heading + favoriteView.tintColor = theme.brand.purple + contactTypeBackgroundView.backgroundColor = theme.brand.purple + contactTypeView.tintColor = theme.backgrounds.primary + } + + func update(viewModel: ViewModel) { + nameLabel.text = viewModel.name + + if let avatarImage = viewModel.avatarImage { + avatarMenu.avatarView.avatar = .image(avatarImage) + } else { + avatarMenu.avatarView.avatar = .text(viewModel.avatarText) + } + + avatarMenu.update(buttons: viewModel.menuItems) + favoriteView.isHidden = !viewModel.isFavorite + contactTypeView.image = viewModel.contactTypeImage + contactTypeBackgroundView.isHidden = viewModel.contactTypeImage == nil + } + + func updateCell(isExpanded: Bool, withAnmiation: Bool) { + + self.isExpanded = isExpanded + + if isExpanded { + avatarMenu.show(withAnmiation: withAnmiation) + onExpand?() + } else { + avatarMenu.hide(withAnmiation: withAnmiation) + } + } +} diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactBookMenuButton.swift b/MobileWallet/Screens/Contact Book/List/Views/ContactBookMenuButton.swift new file mode 100644 index 00000000..943b8d83 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/Views/ContactBookMenuButton.swift @@ -0,0 +1,121 @@ +// ContactBookMenuButton.swift + +/* + Package MobileWallet + Created by Browncoat on 20/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class ContactBookMenuButton: DynamicThemeView { + + // MARK: - Subviews + + @View private var button = RoundedButton() + + // MARK: - Properties + + var image: UIImage? { + get { button.image(for: .normal) } + set { button.setImage(newValue, for: .normal) } + } + + var onTap: (() -> Void)? + + private var buttonCollapsedSizeConstraints: [NSLayoutConstraint] = [] + private var buttonExpandedSizeConstraints: [NSLayoutConstraint] = [] + + // MARK: - Initialisers + + override init() { + super.init() + setupConstraints() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + addSubview(button) + + buttonCollapsedSizeConstraints = [ + button.widthAnchor.constraint(equalToConstant: 0.0), + button.heightAnchor.constraint(equalToConstant: 0.0) + ] + + buttonExpandedSizeConstraints = [ + button.widthAnchor.constraint(equalTo: widthAnchor), + button.heightAnchor.constraint(equalTo: heightAnchor) + ] + + let constraints = [ + button.centerXAnchor.constraint(equalTo: centerXAnchor), + button.centerYAnchor.constraint(equalTo: centerYAnchor) + ] + + NSLayoutConstraint.activate(constraints + buttonCollapsedSizeConstraints) + } + + private func setupCallbacks() { + button.onTap = { [weak self] in self?.onTap?() } + } + + // MARK: - Actions + + func show() { + NSLayoutConstraint.deactivate(buttonCollapsedSizeConstraints) + NSLayoutConstraint.activate(buttonExpandedSizeConstraints) + } + + func hide() { + NSLayoutConstraint.deactivate(buttonExpandedSizeConstraints) + NSLayoutConstraint.activate(buttonCollapsedSizeConstraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + button.backgroundColor = theme.backgrounds.primary?.withAlphaComponent(0.15) + button.tintColor = theme.buttons.primaryText + } +} diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactCapsuleMenu.swift b/MobileWallet/Screens/Contact Book/List/Views/ContactCapsuleMenu.swift new file mode 100644 index 00000000..ccd19736 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/Views/ContactCapsuleMenu.swift @@ -0,0 +1,169 @@ +// ContactCapsuleMenu.swift + +/* + Package MobileWallet + Created by Browncoat on 20/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class ContactCapsuleMenu: UIView { + + struct ButtonViewModel: Identifiable, Hashable { + let id: UInt + let icon: UIImage? + } + + // MARK: - Subviews + + @View private var backgroundView = ContactCapsuleMenuBackground() + @View private(set) var avatarView = RoundedAvatarView() + + @View private var stackView: UIStackView = { + let view = UIStackView() + view.spacing = 15.0 + return view + }() + + // MARK: - Properties + + var onButtonTap: ((UInt) -> Void)? + private var avatarViewFrame: CGRect { CGRect(origin: stackView.frame.origin, size: avatarView.frame.size) } + + // MARK: - Initialisers + + init() { + super.init(frame: .zero) + setupConstraints() + setupGradientMask() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [backgroundView, stackView].forEach(addSubview) + update(buttons: []) + + let constraints = [ + backgroundView.topAnchor.constraint(equalTo: topAnchor), + backgroundView.leadingAnchor.constraint(equalTo: leadingAnchor), + backgroundView.trailingAnchor.constraint(equalTo: trailingAnchor), + backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor), + avatarView.widthAnchor.constraint(equalToConstant: 44.0), + avatarView.heightAnchor.constraint(equalToConstant: 44.0), + stackView.topAnchor.constraint(equalTo: topAnchor, constant: 8.0), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupGradientMask() { + layoutIfNeeded() + backgroundView.maskFrame = avatarViewFrame + } + + // MARK: - Updates + + func update(buttons: [ButtonViewModel]) { + + stackView.arrangedSubviews.forEach { + self.stackView.removeArrangedSubview($0) + guard $0 != avatarView else { return } + $0.removeFromSuperview() + } + + var views: [UIView] = [avatarView] + views += buttons.map { self.makeButton(model: $0) } + + views.forEach(stackView.addArrangedSubview) + } + + // MARK: - Actions + + func show(withAnmiation animated: Bool) { + + stackView.arrangedSubviews + .compactMap { $0 as? ContactBookMenuButton } + .forEach { $0.show() } + + let duration: TimeInterval = animated ? 0.3 : 0.0 + layoutIfNeeded() + + UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.0, options: [.beginFromCurrentState]) { + self.backgroundView.maskFrame = self.bounds + } + } + + func hide(withAnmiation animated: Bool) { + + stackView.arrangedSubviews + .compactMap { $0 as? ContactBookMenuButton } + .forEach { $0.hide() } + + let duration: TimeInterval = animated ? 0.3 : 0.0 + + UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.0, options: [.beginFromCurrentState]) { + self.backgroundView.maskFrame = self.avatarViewFrame + } + } + + // MARK: - Helpers + + private func makeButton(model: ButtonViewModel) -> ContactBookMenuButton { + let button = ContactBookMenuButton() + button.image = model.icon + button.onTap = { [weak self] in self?.onButtonTap?(model.id) } + button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true + button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true + return button + } + + // MARK: - Autolayout + + override func layoutSubviews() { + super.layoutSubviews() + backgroundView.layer.cornerRadius = frame.height * 0.5 + } +} diff --git a/MobileWallet/Screens/Contact Book/List/Views/ContactCapsuleMenuBackground.swift b/MobileWallet/Screens/Contact Book/List/Views/ContactCapsuleMenuBackground.swift new file mode 100644 index 00000000..29f0784f --- /dev/null +++ b/MobileWallet/Screens/Contact Book/List/Views/ContactCapsuleMenuBackground.swift @@ -0,0 +1,93 @@ +// ContactCapsuleMenuBackground.swift + +/* + Package MobileWallet + Created by Browncoat on 22/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class ContactCapsuleMenuBackground: UIView { + + // MARK: - Subviews + + @View private var gradientView = TariGradientView() + + private let menuMaskView: UIView = { + let view = UIView() + view.backgroundColor = .black + return view + }() + + // MARK: - Properties + + var maskFrame: CGRect { + get { menuMaskView.frame } + set { + menuMaskView.frame = newValue + menuMaskView.layer.cornerRadius = newValue.height * 0.5 + } + } + + // MARK: - Initialisers + + init() { + super.init(frame: .zero) + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupViews() { + + addSubview(gradientView) + mask = menuMaskView + + let constraints = [ + gradientView.centerXAnchor.constraint(equalTo: centerXAnchor), + gradientView.centerYAnchor.constraint(equalTo: centerYAnchor), + gradientView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.5), + gradientView.heightAnchor.constraint(equalTo: heightAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } +} diff --git a/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift b/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift new file mode 100644 index 00000000..863fc483 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Managers/ContactsManager.swift @@ -0,0 +1,233 @@ +// ContactsManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 06/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +final class ContactsManager { + + enum ContactType { + case internalOrEmojiID + case external + case linked + case empty + } + + struct Model: Identifiable { + + let id: UUID = UUID() + let internalModel: InternalContactsManager.ContactModel? + let externalModel: ExternalContactsManager.ContactModel? + + var name: String { nameComponents.joined(separator: " ") } + + var nameComponents: [String] { + guard let externalModel else { + let name = internalModel?.alias ?? internalModel?.emojiID.obfuscatedText ?? "" + return [name] + } + return [externalModel.firstName, externalModel.lastName] + } + + var avatar: String { + guard let externalModel else { return internalModel?.emojiID.firstOrEmpty ?? "" } + return [externalModel.firstName, externalModel.lastName] + .compactMap { $0.first } + .map { String($0) } + .joined() + } + + var avatarImage: UIImage? { + externalModel?.avatar + } + + var isFavorite: Bool { internalModel?.isFavorite ?? false } + var isFFIContact: Bool { internalModel?.alias != nil } + var hasIntrenalModel: Bool { internalModel != nil } + var hasExternalModel: Bool { externalModel != nil } + + var type: ContactType { + + if hasIntrenalModel && hasExternalModel { + return .linked + } + + if hasIntrenalModel { + return .internalOrEmojiID + } + + if hasExternalModel { + return .external + } + + return .empty + } + } + + var isPermissionGranted: Bool { + externalContactsManager.isPermissionGranted + } + + // MARK: - Properties + + @Published private(set) var tariContactModels: [Model] = [] + @Published private(set) var externalModels: [Model] = [] + + private let internalContactsManager = InternalContactsManager() + private let externalContactsManager = ExternalContactsManager() + + // MARK: - Actions + + func fetchModels() async throws { + + var internalContacts = try internalContactsManager.fetchAllModels() + let externalContacts = try await externalContactsManager.fetchAllModels() + var filteredExternalContacts = externalContacts + + var tariContactModels = [Model]() + + for index in 0.. Model { + + if let externalModel = model.externalModel, let updatedModel = tariContactModels.first(where: { $0.externalModel == externalModel }) { + return updatedModel + } + + if let externalModel = model.externalModel, let updatedModel = externalModels.first(where: { $0.externalModel == externalModel }) { + return updatedModel + } + + if let internalModel = model.internalModel, let updatedModel = tariContactModels.first(where: { $0.internalModel == internalModel }) { + return updatedModel + } + + return model + } + + func update(nameComponents: [String], isFavorite: Bool, yat: String, contact: Model) throws { + + let internalContact = contact.internalModel + let externalContact = contact.externalModel + + if let internalContact { + try internalContactsManager.update(name: nameComponents.joined(separator: " "), isFavorite: isFavorite, contact: internalContact) + } + + if let externalContact, nameComponents.count == 2 { + try externalContactsManager.update(firstName: nameComponents[0], lastName: nameComponents[1], yat: yat, contact: externalContact) + } + } + + func remove(contact: Model) throws { + + if let internalContact = contact.internalModel { + try internalContactsManager.remove(contact: internalContact) + } + + if let externalContact = contact.externalModel { + try externalContactsManager.remove(contact: externalContact) + } + } + + func link(internalContact: InternalContactsManager.ContactModel, externalContact: ExternalContactsManager.ContactModel) throws { + try externalContactsManager.link(hex: internalContact.hex, emojiID: internalContact.emojiID, contact: externalContact) + try internalContactsManager.update(name: externalContact.fullname, isFavorite: internalContact.isFavorite, contact: internalContact) + } + + func unlink(contact: Model) throws { + guard let externalContact = contact.externalModel else { return } + try externalContactsManager.unlink(contact: externalContact) + } + + func createInternalModel(name: String, isFavorite: Bool, address: TariAddress) throws -> Model { + let internalModel = try internalContactsManager.create(name: name, isFavorite: isFavorite, address: address) + return Model(internalModel: internalModel, externalModel: nil) + } +} + +extension ContactsManager.Model { + + var menuItems: [ContactBookModel.MenuItem] { + + var items: [ContactBookModel.MenuItem] = [] + + if hasIntrenalModel { + items.append(.send) + } + + if isFFIContact, let internalModel { + items.append(internalModel.isFavorite ? .removeFromFavorites : .addToFavorites) + } + + if hasIntrenalModel, hasExternalModel { + items.append(.unlink) + } else { + items.append(.link) + } + + items.append(.details) + + return items + } + + var paymentInfo: PaymentInfo? { + get throws { + guard let internalModel else { return nil } + let address = try TariAddress(hex: internalModel.hex) + return PaymentInfo(address: address, yatID: nil) + } + } +} diff --git a/MobileWallet/Screens/Contact Book/Managers/ExternalContactsManager.swift b/MobileWallet/Screens/Contact Book/Managers/ExternalContactsManager.swift new file mode 100644 index 00000000..47aa21e0 --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Managers/ExternalContactsManager.swift @@ -0,0 +1,166 @@ +// ExternalContactsManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 02/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Contacts +import UIKit + +final class ExternalContactsManager { + + private static let auroraServiceName = "Aurora" + private static let yatServiceName = "Yat" + + struct ContactModel: Equatable { + let firstName: String + let lastName: String + let contact: CNContact + let yat: String? + + var avatar: UIImage? { + guard let data = contact.thumbnailImageData, let image = UIImage(data: data) else { return nil } + return image + } + + var emojiID: String? { contact.socialProfiles.first { $0.value.service == auroraServiceName }?.value.username } + var fullname: String { [firstName, lastName].joined(separator: " ") } + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.contact.identifier == rhs.contact.identifier + } + } + + // MARK: - Properties + + var isPermissionGranted: Bool { + CNContactStore.authorizationStatus(for: .contacts) == .authorized + } + + private let store = CNContactStore() + + // MARK: - Actions + + func fetchAllModels() async throws -> [ContactModel] { + + do { + try await store.requestAccess(for: .contacts) + } catch { + return [] + } + + let keysToFetch: [CNKeyDescriptor] = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactInstantMessageAddressesKey, CNContactSocialProfilesKey, CNContactThumbnailImageDataKey] as [CNKeyDescriptor] + let request = CNContactFetchRequest(keysToFetch: keysToFetch) + + return try await withCheckedThrowingContinuation { continuation in + + var models: [ContactModel] = [] + + do { + try store.enumerateContacts(with: request) { contact, _ in + let yat = contact.socialProfiles.first { $0.value.service == Self.yatServiceName }?.value.username + models.append(ContactModel(firstName: contact.givenName, lastName: contact.familyName, contact: contact, yat: yat)) + } + continuation.resume(with: .success(models)) + } catch { + continuation.resume(throwing: error) + } + } + } + + func update(firstName: String, lastName: String, yat: String, contact: ContactModel) throws { + + guard let mutableContact = contact.contact.mutableCopy() as? CNMutableContact else { return } + + mutableContact.givenName = firstName + mutableContact.familyName = lastName + + let urlString = "https://www.y.at" + + var socialProfiles = mutableContact.socialProfiles.filter { $0.value.service != Self.yatServiceName } + + if !yat.isEmpty { + socialProfiles.append(CNLabeledValue(label: nil, value: CNSocialProfile(urlString: urlString, username: yat, userIdentifier: nil, service: Self.yatServiceName))) + } + + mutableContact.socialProfiles = socialProfiles + + let request = CNSaveRequest() + request.update(mutableContact) + + try store.execute(request) + } + + func remove(contact: ContactModel) throws { + + guard let mutableContact = contact.contact.mutableCopy() as? CNMutableContact else { return } + + let request = CNSaveRequest() + request.delete(mutableContact) + + try store.execute(request) + } + + func link(hex: String, emojiID: String, contact: ContactModel) throws { + + guard let mutableContact = contact.contact.mutableCopy() as? CNMutableContact else { return } + + let urlString = "tari://\(NetworkManager.shared.selectedNetwork.name)/transactions/send?publicKey=\(hex)" + + var socialProfiles = mutableContact.socialProfiles.filter { $0.value.service != Self.auroraServiceName } + socialProfiles.append(CNLabeledValue(label: nil, value: CNSocialProfile(urlString: urlString, username: emojiID, userIdentifier: nil, service: Self.auroraServiceName))) + + mutableContact.socialProfiles = socialProfiles + + let request = CNSaveRequest() + request.update(mutableContact) + + try store.execute(request) + } + + func unlink(contact: ContactModel) throws { + + guard let mutableContact = contact.contact.mutableCopy() as? CNMutableContact else { return } + + mutableContact.socialProfiles = mutableContact.socialProfiles.filter { $0.value.service != Self.auroraServiceName } + + let request = CNSaveRequest() + request.update(mutableContact) + + try store.execute(request) + } +} diff --git a/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift b/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift new file mode 100644 index 00000000..24f89f7f --- /dev/null +++ b/MobileWallet/Screens/Contact Book/Managers/InternalContactsManager.swift @@ -0,0 +1,134 @@ +// InternalContactsManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 02/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class InternalContactsManager { + + struct ContactModel: Hashable { + + let alias: String? + let emojiID: String + let hex: String + let isFavorite: Bool + private(set) var contact: Contact? + + static func == (lhs: InternalContactsManager.ContactModel, rhs: InternalContactsManager.ContactModel) -> Bool { + lhs.hex == rhs.hex + } + + func hash(into hasher: inout Hasher) { + hasher.combine(hex) + } + } + + func fetchAllModels() throws -> [ContactModel] { + + var models: [ContactModel] = [] + + models += try fetchWalletContacts().map { try ContactModel(alias: $0.alias, emojiID: $0.address.emojis, hex: $0.address.byteVector.hex, isFavorite: $0.isFavorite, contact: $0) } + models += try fetchTariAddresses().map { try ContactModel(alias: nil, emojiID: $0.emojis, hex: $0.byteVector.hex, isFavorite: false, contact: nil) } + + return models + .reduce(into: [ContactModel]()) { collection, model in + guard collection.first(where: {$0.emojiID == model.emojiID }) == nil else { return } + collection.append(model) + } + .sorted { + + let firstAlias = $0.alias?.lowercased() + let secondAlias = $1.alias?.lowercased() + + if let firstAlias, let secondAlias { + return firstAlias < secondAlias + } + + if firstAlias != nil { + return true + } + + if secondAlias != nil { + return false + } + + return $0.emojiID < $1.emojiID + } + } + + func create(name: String, isFavorite: Bool, address: TariAddress) throws -> ContactModel { + let contact = try Contact(alias: name, isFavorite: isFavorite, addressPointer: address.pointer) + try Tari.shared.contacts.upsert(contact: contact) + return try ContactModel(alias: name, emojiID: address.emojis, hex: address.byteVector.hex, isFavorite: isFavorite, contact: contact) + } + + func update(name: String, isFavorite: Bool, contact: ContactModel) throws { + + let address: TariAddress + + if let existingContact = contact.contact { + address = try TariAddress(emojiID: existingContact.address.emojis) + } else { + address = try TariAddress(emojiID: contact.emojiID) + } + + let contact = try Contact(alias: name, isFavorite: isFavorite, addressPointer: address.pointer) + try Tari.shared.contacts.upsert(contact: contact) + } + + func remove(contact: ContactModel) throws { + guard let contact = contact.contact else { return } + try Tari.shared.contacts.remove(contact: contact) + } + + private func fetchWalletContacts() throws -> [Contact] { + try Tari.shared.contacts.allContacts + } + + private func fetchTariAddresses() throws -> [TariAddress] { + + var transactions: [Transaction] = [] + + transactions += Tari.shared.transactions.pendingInbound + transactions += Tari.shared.transactions.pendingOutbound + transactions += Tari.shared.transactions.cancelled + transactions += Tari.shared.transactions.completed + + return try transactions + .map { try $0.address } + } +} diff --git a/MobileWallet/Screens/Contact Book/PopPresenter+ContactBook.swift b/MobileWallet/Screens/Contact Book/PopPresenter+ContactBook.swift new file mode 100644 index 00000000..a5b0b49d --- /dev/null +++ b/MobileWallet/Screens/Contact Book/PopPresenter+ContactBook.swift @@ -0,0 +1,81 @@ +// PopPresenter+ContactBook.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 08/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +extension PopUpPresenter { + + static func showUnlinkConfirmationDialog(emojiID: 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: name, style: .bold) + ], + buttons: [ + PopUpDialogButtonModel(title: localized("common.confirm"), type: .normal, callback: { confirmationCallback() }), + PopUpDialogButtonModel(title: localized("common.cancel"), type: .text) + ], + hapticType: .none + ) + + showPopUp(model: model) + } + + static func showUnlinkSuccessDialog(emojiID: 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: name, style: .bold) + ], + buttons: [ + PopUpDialogButtonModel(title: localized("common.close"), type: .text) + ], + hapticType: .none + ) + + showPopUp(model: model) + } +} diff --git a/MobileWallet/Screens/Debug/UIViewController+DebugMenu.swift b/MobileWallet/Screens/Debug/UIViewController+DebugMenu.swift index fc2c1bd8..bb59104c 100644 --- a/MobileWallet/Screens/Debug/UIViewController+DebugMenu.swift +++ b/MobileWallet/Screens/Debug/UIViewController+DebugMenu.swift @@ -44,8 +44,8 @@ extension UIViewController { // MARK: - Motion - open override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { - super.motionBegan(motion, with: event) + open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { + super.motionEnded(motion, with: event) guard motion == .motionShake else { return } handleShakeGesture() } diff --git a/MobileWallet/Screens/Home/Home/HomeViewController.swift b/MobileWallet/Screens/Home/Home/HomeViewController.swift index 0a14a97c..78bf734c 100644 --- a/MobileWallet/Screens/Home/Home/HomeViewController.swift +++ b/MobileWallet/Screens/Home/Home/HomeViewController.swift @@ -117,8 +117,6 @@ final class HomeViewController: DynamicThemeViewController { setupCallbacks() NotificationManager.shared.requestAuthorization() StagedWalletSecurityManager.shared.start() - - PopUpPresenter.showStorePopUp() // TODO: Remove } override func viewDidAppear(_ animated: Bool) { diff --git a/MobileWallet/Screens/Home/Home/HomeViewModel.swift b/MobileWallet/Screens/Home/Home/HomeViewModel.swift index ae7867ef..0a958c04 100644 --- a/MobileWallet/Screens/Home/Home/HomeViewModel.swift +++ b/MobileWallet/Screens/Home/Home/HomeViewModel.swift @@ -108,27 +108,19 @@ final class HomeViewModel { private func handle(networkConnection: NetworkMonitor.Status, torConnection: TorManager.ConnectionStatus, baseNodeConnection: BaseNodeConnectivityStatus, syncStatus: TariValidationService.SyncStatus) { switch (networkConnection, torConnection, baseNodeConnection, syncStatus) { - case (.disconnected, _, _, _): + case (.disconnected, _, _, _), + (.connected, .disconnected, _, _), + (.connected, .disconnecting, _, _): connectionStatusImage = offlineIcon - case (.connected, .disconnected, _, _): - connectionStatusImage = offlineIcon - case (.connected, .disconnecting, _, _): - connectionStatusImage = offlineIcon - case (.connected, .connecting, _, _): - connectionStatusImage = limitedConnectionIcon - case (.connected, .portsOpen, _, _): - connectionStatusImage = limitedConnectionIcon - case (.connected, .connected, .offline, _): + case (.connected, .connecting, _, _), + (.connected, .portsOpen, _, _), + (.connected, .connected, .offline, _), + (.connected, .connected, .connecting, _), + (.connected, .connected, .online, .idle), + (.connected, .connected, .online, .failed): connectionStatusImage = limitedConnectionIcon - case (.connected, .connected, .connecting, _): - connectionStatusImage = limitedConnectionIcon - case (.connected, .connected, .online, .idle): - connectionStatusImage = limitedConnectionIcon - case (.connected, .connected, .online, .failed): - connectionStatusImage = limitedConnectionIcon - case (.connected, .connected, .online, .syncing): - connectionStatusImage = onlineIcon - case (.connected, .connected, .online, .synced): + case (.connected, .connected, .online, .syncing), + (.connected, .connected, .online, .synced): connectionStatusImage = onlineIcon } } diff --git a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift index f401e445..f50bff92 100644 --- a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift +++ b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift @@ -150,7 +150,15 @@ final class TransactionDetailsModel { do { let address = try transaction.address - let contact = try Contact(alias: alias, addressPointer: address.pointer) + let isFavorite: Bool + + if let existingContact = try Tari.shared.contacts.findContact(hex: address.byteVector.hex) { + isFavorite = try existingContact.isFavorite + } else { + isFavorite = false + } + + let contact = try Contact(alias: alias, isFavorite: isFavorite, addressPointer: address.pointer) _ = try Tari.shared.contacts.upsert(contact: contact) userAliasUpdateSuccessCallback?() userAlias = alias diff --git a/MobileWallet/Screens/Home/TransactionHistory/TxTableViewCell.swift b/MobileWallet/Screens/Home/TransactionHistory/TxTableViewCell.swift index df04c23d..439acde0 100644 --- a/MobileWallet/Screens/Home/TransactionHistory/TxTableViewCell.swift +++ b/MobileWallet/Screens/Home/TransactionHistory/TxTableViewCell.swift @@ -40,11 +40,11 @@ import UIKit import GiphyUISDK +import TariCommon -class TxTableViewCell: DynamicThemeCell { - private let avatarContainer = UIView() +final class TxTableViewCell: DynamicThemeCell { private let labelsContainer = UIView() - private let avatarLabel = UILabel() + @View private var avatarView = RoundedAvatarView() private let titleLabel = UILabel() private let timeLabel = UILabel() private let statusLabel = UILabel() @@ -86,11 +86,15 @@ class TxTableViewCell: DynamicThemeCell { func configure(with model: TxTableViewModel) { - if model.id == self.model?.id { return } - setStatus(model.status) self.model = model - avatarLabel.text = model.avatar + + 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 @@ -219,8 +223,6 @@ class TxTableViewCell: DynamicThemeCell { super.update(theme: theme) backgroundColor = .clear - avatarContainer.backgroundColor = theme.backgrounds.primary - avatarContainer.apply(shadow: theme.shadows.box) titleLabel.textColor = theme.text.body timeLabel.textColor = theme.text.lightText statusLabel.textColor = theme.system.yellow @@ -245,21 +247,17 @@ extension TxTableViewCell { } private func setupAvatar() { - contentView.addSubview(avatarContainer) - - avatarContainer.translatesAutoresizingMaskIntoConstraints = false - - let size: CGFloat = 42 - avatarContainer.widthAnchor.constraint(equalToConstant: size).isActive = true - avatarContainer.heightAnchor.constraint(equalToConstant: size).isActive = true - avatarContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Theme.shared.sizes.appSidePadding).isActive = true - avatarContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: TxTableViewCell.topCellPadding).isActive = true - avatarContainer.layer.cornerRadius = size / 2 - avatarLabel.translatesAutoresizingMaskIntoConstraints = false - avatarContainer.addSubview(avatarLabel) - avatarLabel.font = UIFont.systemFont(ofSize: size * 0.55) - avatarLabel.centerXAnchor.constraint(equalTo: avatarContainer.centerXAnchor).isActive = true - avatarLabel.centerYAnchor.constraint(equalTo: avatarContainer.centerYAnchor).isActive = true + + 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() { @@ -281,7 +279,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: avatarContainer.trailingAnchor, constant: Theme.shared.sizes.appSidePadding).isActive = true + labelsContainer.leadingAnchor.constraint(equalTo: avatarView.trailingAnchor, 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/Home/TransactionHistory/TxTableViewModel.swift b/MobileWallet/Screens/Home/TransactionHistory/TxTableViewModel.swift index 618fdeff..54cdd9bc 100644 --- a/MobileWallet/Screens/Home/TransactionHistory/TxTableViewModel.swift +++ b/MobileWallet/Screens/Home/TransactionHistory/TxTableViewModel.swift @@ -52,7 +52,8 @@ class TxTableViewModel: NSObject { let id: UInt64 private(set) var transaction: Transaction private(set) var title = NSAttributedString() - private(set) var avatar: String = "" + private(set) var avatarText: String = "" + private(set) var avatarImage: UIImage? private(set) var message: String private(set) var value: Value @@ -67,8 +68,11 @@ class TxTableViewModel: NSObject { @objc dynamic private(set) var status: String = "" @objc dynamic private(set) var time: String - init(transaction: Transaction) throws { + private let contact: ContactsManager.Model? + + init(transaction: Transaction, contact: ContactsManager.Model?) throws { self.transaction = transaction + self.contact = contact self.id = try transaction.identifier value = Value(microTari: MicroTari(try transaction.amount), isOutboundTransaction: try transaction.isOutboundTransaction, isCancelled: transaction.isCancelled, isPending: transaction.isPending) @@ -123,30 +127,17 @@ class TxTableViewModel: NSObject { private func updateTitleAndAvatar() throws { guard try !transaction.isOneSidedPayment else { - avatar = localized("transaction.one_sided_payment.avatar") + 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, isAliasEmojiID: false) + title = attributed(title: localized("tx_list.inbound_pending_title", arguments: alias), withAlias: alias) return } - let address = try transaction.address - - let emojis = try address.emojis - avatar = String(emojis.prefix(1)) - - var alias = "" - var aliasIsEmojis = false - - if let contact = try Tari.shared.contacts.findContact(hex: try address.byteVector.hex) { - alias = try contact.alias - } - - if alias.isEmpty { - alias = "\(String(emojis.prefix(2)))•••\(String(emojis.suffix(2)))" - aliasIsEmojis = true - } + avatarText = contact?.avatar ?? "" + avatarImage = contact?.avatarImage var titleText = "" + let alias = contact?.name ?? "" if try transaction.isOutboundTransaction { titleText = localized("tx_list.outbound_title", arguments: alias) @@ -154,10 +145,10 @@ class TxTableViewModel: NSObject { titleText = try transaction.status == .pending ? localized("tx_list.inbound_pending_title", arguments: alias) : localized("tx_list.inbound_title", arguments: alias) } - title = attributed(title: titleText, withAlias: alias, isAliasEmojiID: aliasIsEmojis) + title = attributed(title: titleText, withAlias: alias) } - private func attributed(title: String, withAlias alias: String, isAliasEmojiID: Bool) -> NSAttributedString { + private func attributed(title: String, withAlias alias: String) -> NSAttributedString { let title = title .replacingOccurrences(of: alias, with: " \(alias) ") diff --git a/MobileWallet/Screens/Home/TransactionHistory/TxsListViewController.swift b/MobileWallet/Screens/Home/TransactionHistory/TxsListViewController.swift index 6ff6a043..251ca1c0 100644 --- a/MobileWallet/Screens/Home/TransactionHistory/TxsListViewController.swift +++ b/MobileWallet/Screens/Home/TransactionHistory/TxsListViewController.swift @@ -73,7 +73,10 @@ final class TxsListViewController: DynamicThemeViewController { weak var actionDelegate: TxsTableViewDelegate? let tableView = UITableView(frame: .zero, style: .grouped) + private let animatedRefresher = AnimatedRefreshingView() + private let contactsManager = ContactsManager() + private let refreshTimeoutPeriodSecs = 40.0 private var pendingTxModels = [TxTableViewModel]() @@ -121,7 +124,10 @@ final class TxsListViewController: DynamicThemeViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - setupTransactionsCallbacks() + Task { + try? await contactsManager.fetchModels() + setupTransactionsCallbacks() + } if backgroundType != .intro { safeRefreshTable() @@ -140,7 +146,7 @@ final class TxsListViewController: DynamicThemeViewController { cancelTransactionsCallbacks() } - func safeRefreshTable(_ completion: (() -> Void)? = nil) { + private func safeRefreshTable(_ completion: (() -> Void)? = nil) { txDataUpdateQueue.async(flags: .barrier) { DispatchQueue.main.async { [weak self] in self?.tableView.reloadData() @@ -154,22 +160,49 @@ final class TxsListViewController: DynamicThemeViewController { Publishers.CombineLatest(Tari.shared.transactions.$pendingInbound, Tari.shared.transactions.$pendingOutbound) .map { $0 as [Transaction] + $1 } .tryMap { try $0.sorted { try $0.timestamp > $1.timestamp }} - .tryMap { try $0.map { try TxTableViewModel(transaction: $0) }} - .replaceError(with: []) - .receive(on: DispatchQueue.main) - .sink { [weak self] in self?.pendingTxModels = $0 } + .replaceError(with: [Transaction]()) + .sink { [weak self] in self?.handle(transaction: $0, areCompletedTransactions: false) } .store(in: &transactionModelsCancellables) Publishers.CombineLatest(Tari.shared.transactions.$completed, Tari.shared.transactions.$cancelled) .map { $0 + $1 } .tryMap { try $0.sorted { try $0.timestamp > $1.timestamp }} - .tryMap { try $0.map { try TxTableViewModel(transaction: $0) }} - .replaceError(with: []) - .receive(on: DispatchQueue.main) - .sink { [weak self] in self?.completedTxModels = $0 } + .replaceError(with: [Transaction]()) + .sink { [weak self] in self?.handle(transaction: $0, areCompletedTransactions: true) } .store(in: &transactionModelsCancellables) } + private func handle(transaction: [Transaction], areCompletedTransactions: Bool) { + Task { + do { + try await contactsManager.fetchModels() + let models = try makeTxViewModels(transactions: transaction) + update(models: models, areCompletedTransactions: areCompletedTransactions) + safeRefreshTable() + } catch {} + } + } + + private func makeTxViewModels(transactions: [Transaction]) throws -> [TxTableViewModel] { + return try transactions.map { + let contact = try self.contact(transaction: $0) + return try TxTableViewModel(transaction: $0, contact: contact) + } + } + + private func update(models: [TxTableViewModel], areCompletedTransactions: Bool) { + if areCompletedTransactions { + completedTxModels = models + } else { + pendingTxModels = models + } + } + + private func contact(transaction: Transaction) throws -> ContactsManager.Model? { + let emojis = try transaction.address.emojis + return contactsManager.tariContactModels.first { $0.internalModel?.emojiID == emojis } + } + private func cancelTransactionsCallbacks() { transactionModelsCancellables.forEach { $0.cancel() } transactionModelsCancellables.removeAll() diff --git a/MobileWallet/Screens/Profile/ProfileModel.swift b/MobileWallet/Screens/Profile/ProfileModel.swift index 7839f946..ae4aea39 100644 --- a/MobileWallet/Screens/Profile/ProfileModel.swift +++ b/MobileWallet/Screens/Profile/ProfileModel.swift @@ -115,9 +115,10 @@ final class ProfileModel { private func updateData() { do { let walletAddress = try Tari.shared.walletAddress - let deeplinkData = try walletAddress.byteVector.hex.data(using: .utf8) ?? Data() + let deeplinkModel = try TransactionsSendDeeplink(receiverAddress: walletAddress.byteVector.hex, amount: nil, note: nil) + let deeplinkData = try DeepLinkFormatter.deeplink(model: deeplinkModel)?.absoluteString.data(using: .utf8) ?? Data() self.walletAddress = walletAddress - qrCodeImage = QRCodeFactory.makeQrCode(data: deeplinkData) + updateQR(data: deeplinkData) updateYatIdData() } catch { qrCodeImage = nil @@ -126,6 +127,12 @@ final class ProfileModel { } } + private func updateQR(data: Data) { + Task { + qrCodeImage = await QRCodeFactory.makeQrCode(data: data) + } + } + func updateYatIdData() { guard let connectedYat = TariSettings.shared.walletSettings.connectedYat else { return } diff --git a/MobileWallet/Screens/Profile/ProfileView.swift b/MobileWallet/Screens/Profile/ProfileView.swift index 095030c0..ef641db2 100644 --- a/MobileWallet/Screens/Profile/ProfileView.swift +++ b/MobileWallet/Screens/Profile/ProfileView.swift @@ -48,7 +48,7 @@ final class ProfileView: DynamicThemeView { @View private var navigationBar: NavigationBar = { let view = NavigationBar() - view.backButtonType = .none + view.backButtonType = .back view.title = localized("profile_view.title") return view }() @@ -87,10 +87,10 @@ final class ProfileView: DynamicThemeView { return view }() - @View private var qrImageView = UIImageView() + @View private var qrImageView = LoadingImageView() var qrCodeImage: UIImage? { - didSet { qrImageView.image = qrCodeImage } + didSet { qrImageView.state = .image(qrCodeImage) } } var isYatButtonOn: Bool = false { @@ -142,7 +142,6 @@ final class ProfileView: DynamicThemeView { reconnectYatButton.topAnchor.constraint(equalTo: middleLabel.bottomAnchor), reconnectYatButton.centerXAnchor.constraint(equalTo: centerXAnchor), qrContainer.heightAnchor.constraint(equalTo: qrContainer.widthAnchor), - qrContainer.centerXAnchor.constraint(equalTo: centerXAnchor), qrImageView.leadingAnchor.constraint(equalTo: qrContainer.leadingAnchor, constant: 30.0), qrImageView.trailingAnchor.constraint(equalTo: qrContainer.trailingAnchor, constant: -30.0), qrImageView.bottomAnchor.constraint(equalTo: qrContainer.bottomAnchor, constant: -30.0), @@ -153,14 +152,14 @@ final class ProfileView: DynamicThemeView { constraints += [ qrContainer.topAnchor.constraint(equalTo: reconnectYatButton.bottomAnchor, constant: 100.0), qrContainer.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, multiplier: 0.5), - qrContainer.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor, multiplier: 0.5) + qrContainer.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor, multiplier: 0.5), + qrContainer.centerXAnchor.constraint(equalTo: centerXAnchor) ] } else { constraints += [ - qrContainer.topAnchor.constraint(greaterThanOrEqualTo: reconnectYatButton.bottomAnchor, constant: 10.0), - qrContainer.topAnchor.constraint(lessThanOrEqualTo: reconnectYatButton.bottomAnchor, constant: 25.0), - qrContainer.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 22.0), - qrContainer.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -22.0) + qrContainer.topAnchor.constraint(equalTo: reconnectYatButton.bottomAnchor, constant: 25.0), + qrContainer.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + qrContainer.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25.0) ] } diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift index 3129ef2c..992966fa 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift @@ -156,7 +156,6 @@ final class RestoreWalletFromSeedsViewController: SettingsParentViewController, let overlay = SeedWordsRecoveryProgressViewController() overlay.onSuccess = { - try? MigrationManager.updateWalletVersion() AppRouter.transitionToSplashScreen(isWalletConnected: true) } diff --git a/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift b/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift index a0d5ee08..6f1e484e 100644 --- a/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift +++ b/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift @@ -320,10 +320,3 @@ private extension TariAddress { return ContactElementItem(id: uuid, title: title, initial: "", isEmojiID: true) } } - -private extension String { - var obfuscatedText: String { - guard count >= 9 else { return self } - return "\(prefix(3))•••\(suffix(3))" - } -} diff --git a/MobileWallet/Screens/Send/AddRecipient/Views/AddRecipientSearchView.swift b/MobileWallet/Screens/Send/AddRecipient/Views/AddRecipientSearchView.swift index b3dd6f89..941400a1 100644 --- a/MobileWallet/Screens/Send/AddRecipient/Views/AddRecipientSearchView.swift +++ b/MobileWallet/Screens/Send/AddRecipient/Views/AddRecipientSearchView.swift @@ -107,6 +107,7 @@ final class AddRecipientSearchView: DynamicThemeView { private func setupViews() { backgroundColor = .clear + layer.cornerRadius = 6.0 } override func update(theme: ColorTheme) { diff --git a/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountModel.swift b/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountModel.swift index 98d561b3..d5d8cb77 100644 --- a/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountModel.swift +++ b/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountModel.swift @@ -98,8 +98,10 @@ final class RequestTariAmountModel { } func generateQrRequest() { - guard let deeplink = makeDeeplink(), let deeplinkData = deeplink.absoluteString.data(using: .utf8), let qrCodeImage = QRCodeFactory.makeQrCode(data: deeplinkData) else { return } - qrCode = qrCodeImage + guard let deeplink = makeDeeplink(), let deeplinkData = deeplink.absoluteString.data(using: .utf8) else { return } + Task { + qrCode = await QRCodeFactory.makeQrCode(data: deeplinkData) + } } func shareActionRequest() { diff --git a/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsView.swift b/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsView.swift index 8abe7186..6cf85b2a 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsView.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/Settings/BackupWalletSettingsView.swift @@ -45,7 +45,7 @@ final class BackupWalletSettingsView: BaseNavigationContentView { // MARK: - Subviews - @View private var tableView = MenuTableView() + @View private var tableView = BaseMenuTableView() // MARK: - Properties diff --git a/MobileWallet/Screens/Settings/Bug Reporting/BugReportingView.swift b/MobileWallet/Screens/Settings/Bug Reporting/BugReportingView.swift index cf8c2186..1a8698f6 100644 --- a/MobileWallet/Screens/Settings/Bug Reporting/BugReportingView.swift +++ b/MobileWallet/Screens/Settings/Bug Reporting/BugReportingView.swift @@ -147,7 +147,8 @@ final class BugReportingView: BaseNavigationContentView { private func setupConstraints() { addSubview(mainContentView) - [headerLabel, nameTextField, nameTextFieldSeparator, emailTextField, emailTextFieldSeparator, messageHeaderLabel, messageTextView, footerLabel, sendButton, logsButton].forEach { mainContentView.contentView.addSubview($0) } + [headerLabel, nameTextField, nameTextFieldSeparator, emailTextField, emailTextFieldSeparator, messageHeaderLabel, messageTextView, footerLabel, sendButton, logsButton] + .forEach { mainContentView.contentView.addSubview($0) } let constraints = [ mainContentView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), diff --git a/MobileWallet/Screens/Settings/CustomBridgesHandable.swift b/MobileWallet/Screens/Settings/CustomBridgesHandable.swift index 474128d0..0de82b2f 100644 --- a/MobileWallet/Screens/Settings/CustomBridgesHandable.swift +++ b/MobileWallet/Screens/Settings/CustomBridgesHandable.swift @@ -43,7 +43,7 @@ import Combine protocol CustomBridgesHandable: UIViewController { var navigationBar: NavigationBar { get } - var tableView: MenuTableView { get } + var tableView: BaseMenuTableView { get } } extension CustomBridgesHandable { diff --git a/MobileWallet/Screens/Settings/MoreSettings/AboutModel.swift b/MobileWallet/Screens/Settings/MoreSettings/AboutModel.swift index 84aa1983..5ac98b0f 100644 --- a/MobileWallet/Screens/Settings/MoreSettings/AboutModel.swift +++ b/MobileWallet/Screens/Settings/MoreSettings/AboutModel.swift @@ -69,7 +69,11 @@ final class AboutModel { RowData(model: RowModel(icon: Theme.shared.images.settingsBridgeConfigIcon, title: "Repair Tools by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/repair-tools-4213156/")), RowData(model: RowModel(icon: Theme.shared.images.settingsNetworkIcon, title: "Internet Server by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/internet-server-4213144/")), RowData(model: RowModel(icon: Theme.shared.images.settingsBaseNodeIcon, title: "Networking by Design Circle from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/networking-4213263/")), - RowData(model: RowModel(icon: Theme.shared.images.settingsDeleteIcon, title: "Delete by Maya Nurhayati from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/delete-4727971/")) + RowData(model: RowModel(icon: Theme.shared.images.settingsDeleteIcon, title: "Delete by Maya Nurhayati from\nNounProject.com"), url: URL(string: "https://thenounproject.com/icon/delete-4727971/")), + RowData(model: RowModel(icon: .security.onboarding.page1, title: "Write by Mada Creative from NounProject.com"), url: URL(string: "https://thenounproject.com/icon/write-4207866/")), + RowData(model: RowModel(icon: .security.onboarding.page2, title: "Cloud by Tippawan Sookruay from NounProject.com"), url: URL(string: "https://thenounproject.com/icon/cloud-3384041/")), + RowData(model: RowModel(icon: .security.onboarding.page3, title: "Password by Tippawan Sookruay from NounProject.com"), url: URL(string: "https://thenounproject.com/icon/password-3384056/")), + RowData(model: RowModel(icon: .security.onboarding.page4, title: "Key by Tippawan Sookruay from NounProject.com"), url: URL(string: "https://thenounproject.com/icon/key-3384048/")) ] private let creativeCommonsURL = URL(string: "https://creativecommons.org/licenses/by/3.0/") diff --git a/MobileWallet/Screens/Settings/MoreSettings/AboutView.swift b/MobileWallet/Screens/Settings/MoreSettings/AboutView.swift index 1b02480b..03695e0e 100644 --- a/MobileWallet/Screens/Settings/MoreSettings/AboutView.swift +++ b/MobileWallet/Screens/Settings/MoreSettings/AboutView.swift @@ -60,8 +60,8 @@ final class AboutView: BaseNavigationContentView { // MARK: - Subviews - @View private var tableView: MenuTableView = { - let view = MenuTableView() + @View private var tableView: BaseMenuTableView = { + let view = BaseMenuTableView() view.register(type: AboutViewCell.self) view.separatorStyle = .none return view diff --git a/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift b/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift index 22ae3aad..ba3b1c4d 100644 --- a/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift +++ b/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift @@ -43,7 +43,7 @@ import TariCommon class SettingsParentTableViewController: SettingsParentViewController { - @View private(set) var tableView = MenuTableView() + @View private(set) var tableView = BaseMenuTableView() override func viewDidLoad() { super.viewDidLoad() diff --git a/MobileWallet/Screens/Settings/SettingsViewController.swift b/MobileWallet/Screens/Settings/SettingsViewController.swift index 7d1a8eeb..e44177b8 100644 --- a/MobileWallet/Screens/Settings/SettingsViewController.swift +++ b/MobileWallet/Screens/Settings/SettingsViewController.swift @@ -48,23 +48,23 @@ final class SettingsViewController: SettingsParentTableViewController { private let localAuth = LAContext() private enum Section: Int, CaseIterable { + case profile case security case more - case yat case advancedSettings } private enum SettingsHeaderTitle: CaseIterable { + case profileHeader case securityHeader case moreHeader - case yatHeader case advancedSettingsHeader var rawValue: String { switch self { + case .profileHeader: return "" case .securityHeader: return localized("settings.item.header.security") case .moreHeader: return localized("settings.item.header.more") - case .yatHeader: return localized("settings.item.header.yat") case .advancedSettingsHeader: return localized("settings.item.header.advanced_settings") } } @@ -150,6 +150,7 @@ final class SettingsViewController: SettingsParentTableViewController { .blockExplorer: URL(string: TariSettings.shared.blockExplorerUrl) ] + private let profileIndexPath = IndexPath(row: 0, section: 0) private var cancellables = Set() override func viewDidLoad() { @@ -157,6 +158,7 @@ final class SettingsViewController: SettingsParentTableViewController { tableView.delegate = self tableView.dataSource = self tableView.tableFooterView = SettingsViewFooter() + tableView.register(type: SettingsProfileCell.self) setupCallbacks() } @@ -168,16 +170,7 @@ final class SettingsViewController: SettingsParentTableViewController { override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - - guard let footerView = tableView.tableFooterView else { return } - - let width = tableView.bounds.width - let size = footerView.systemLayoutSizeFitting(CGSize(width: width, height: UIView.layoutFittingCompressedSize.height)) - - guard footerView.bounds.height != size.height else { return } - - footerView.bounds.size.height = size.height - tableView.tableFooterView = footerView + tableView.updateFooterFrame() } private func setupCallbacks() { @@ -234,6 +227,11 @@ final class SettingsViewController: SettingsParentTableViewController { WebBrowserPresenter.open(url: url) } + private func onProfileAction() { + let controller = ProfileViewController() + navigationController?.pushViewController(controller, animated: true) + } + private func onConnectYatAction() { let address: String @@ -284,24 +282,37 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let section = Section(rawValue: section) else { return 0 } switch section { + case .profile: return yatSectionItems.count + 1 case .security: return securitySectionItems.count case .more: return moreSectionItems.count - case .yat: return yatSectionItems.count case .advancedSettings: return advancedSettingsSectionItems.count } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + if indexPath == profileIndexPath { + let cell = tableView.dequeueReusableCell(type: SettingsProfileCell.self, indexPath: indexPath) + do { + let emojiID = try Tari.shared.walletAddress.emojis.obfuscatedText + cell.update(avatar: emojiID.firstOrEmpty, emojiID: emojiID) + } catch { + let message = ErrorMessageManager.errorModel(forError: error) + PopUpPresenter.show(message: message) + } + return cell + } + let cell = tableView.dequeueReusableCell(type: SystemMenuTableViewCell.self, indexPath: indexPath) guard let section = Section(rawValue: indexPath.section) else { return cell } switch section { + case .profile: + cell.configure(yatSectionItems[indexPath.row - 1]) case .security: cell.configure(securitySectionItems[indexPath.row]) case .more: cell.configure(moreSectionItems[indexPath.row]) - case .yat: - cell.configure(yatSectionItems[indexPath.row]) case .advancedSettings: cell.configure(advancedSettingsSectionItems[indexPath.row]) } @@ -317,13 +328,13 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { let sec = Section(rawValue: section) switch sec { - case .security, .advancedSettings, .yat, .more: + case .security, .advancedSettings, .more: break default: return nil } - let header = SettingsTableHeaderView() + let header = MenuTableHeaderView() header.label.text = SettingsHeaderTitle.allCases[section].rawValue return header @@ -338,14 +349,26 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 65.0 + guard indexPath == profileIndexPath else { return 65.0 } + return UITableView.automaticDimension } func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { guard let section = Section(rawValue: indexPath.section) else { return nil } switch section { + case .profile: + switch indexPath.row { + case 0: + onProfileAction() + case 1: + onConnectYatAction() + default: + break + } case .security: onBackupWalletAction() case .more: + var indexPath = indexPath + indexPath.row -= 1 switch SettingsItemTitle.allCases[indexPath.row + indexPath.section] { case .about: onAboutAction() @@ -354,13 +377,6 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { default: onLinkAction(indexPath: indexPath) } - case .yat: - switch indexPath.row { - case 0: - onConnectYatAction() - default: - break - } case .advancedSettings: switch indexPath.row { case 0: @@ -421,45 +437,3 @@ extension SettingsViewController { } } } - -private final class SettingsTableHeaderView: DynamicThemeView { - - // MARK: - Subviews - - @View private(set) var label: UILabel = { - let view = UILabel() - view.font = Theme.shared.fonts.settingsViewHeader - 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(label) - - let constraints = [ - label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), - label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -15.0), - heightAnchor.constraint(equalToConstant: 70.0) - ] - - NSLayoutConstraint.activate(constraints) - } - - override func update(theme: ColorTheme) { - super.update(theme: theme) - label.textColor = theme.text.heading - } -} diff --git a/MobileWallet/Screens/Settings/Theme Settings/ThemeSettingsViewController.swift b/MobileWallet/Screens/Settings/Theme Settings/ThemeSettingsViewController.swift index 732f4a95..cde6f744 100644 --- a/MobileWallet/Screens/Settings/Theme Settings/ThemeSettingsViewController.swift +++ b/MobileWallet/Screens/Settings/Theme Settings/ThemeSettingsViewController.swift @@ -78,6 +78,7 @@ final class ThemeSettingsViewController: UIViewController { private func setupCallbacks() { Publishers.CombineLatest(model.$elements, model.$selectedIndex) + .receive(on: DispatchQueue.main) .sink { [weak self] elements, selectedIndex in let viewModels = elements.map { ThemeSettingsView.ViewModel(id: $0.id, image: $0.element.image, title: $0.element.title) } self?.mainView.update(viewModels: viewModels, selectedIndex: selectedIndex) diff --git a/MobileWallet/Screens/Settings/Theme Settings/Views/ThemeSettingsCollectionCell.swift b/MobileWallet/Screens/Settings/Theme Settings/Views/ThemeSettingsCollectionCell.swift index 0254554b..0cbf2e5e 100644 --- a/MobileWallet/Screens/Settings/Theme Settings/Views/ThemeSettingsCollectionCell.swift +++ b/MobileWallet/Screens/Settings/Theme Settings/Views/ThemeSettingsCollectionCell.swift @@ -84,17 +84,17 @@ final class ThemeSettingsCollectionCell: DynamicThemeCollectionCell { [imageView, label, radioButton].forEach { contentView.addSubview($0) } let constaints = [ - imageView.topAnchor.constraint(equalTo: topAnchor, constant: 10.0), - imageView.centerXAnchor.constraint(equalTo: centerXAnchor), + imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10.0), + imageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), imageView.heightAnchor.constraint(equalToConstant: 200.0), label.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 5.0), - label.leadingAnchor.constraint(equalTo: leadingAnchor), - label.trailingAnchor.constraint(equalTo: trailingAnchor), + label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), radioButton.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 5.0), - radioButton.centerXAnchor.constraint(equalTo: centerXAnchor), + radioButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), radioButton.widthAnchor.constraint(equalToConstant: 16.0), radioButton.heightAnchor.constraint(equalToConstant: 16.0), - radioButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10.0) + radioButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10.0) ] NSLayoutConstraint.activate(constaints) diff --git a/MobileWallet/Screens/Settings/Views/MenuTableView.swift b/MobileWallet/Screens/Settings/Views/BaseMenuTableView.swift similarity index 96% rename from MobileWallet/Screens/Settings/Views/MenuTableView.swift rename to MobileWallet/Screens/Settings/Views/BaseMenuTableView.swift index 90f6c748..c0fd1623 100644 --- a/MobileWallet/Screens/Settings/Views/MenuTableView.swift +++ b/MobileWallet/Screens/Settings/Views/BaseMenuTableView.swift @@ -1,4 +1,4 @@ -// MenuTableView.swift +// BaseMenuTableView.swift /* Package MobileWallet @@ -40,7 +40,7 @@ import UIKit -final class MenuTableView: DynamicThemeTableView { +final class BaseMenuTableView: DynamicThemeTableView { init() { super.init(frame: .zero, style: .grouped) diff --git a/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift b/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift new file mode 100644 index 00000000..1fffc845 --- /dev/null +++ b/MobileWallet/Screens/Settings/Views/SettingsProfileCell.swift @@ -0,0 +1,110 @@ +// SettingsProfileCell.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 15/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class SettingsProfileCell: DynamicThemeCell { + + // MARK: - Subviews + + @View private var avatarView = RoundedAvatarView() + + @View private var label: UILabel = { + let view = UILabel() + view.font = .Avenir.medium.withSize(17.0) + return view + }() + + @View private var scanImage: UIImageView = { + let view = UIImageView() + view.image = Theme.shared.images.qrButton?.withRenderingMode(.alwaysTemplate) + view.contentMode = .scaleAspectFit + return view + }() + + // MARK: - Initialisers + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [avatarView, label, scanImage].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), + label.leadingAnchor.constraint(equalTo: avatarView.trailingAnchor, constant: 10.0), + label.centerYAnchor.constraint(equalTo: avatarView.centerYAnchor), + scanImage.leadingAnchor.constraint(equalTo: label.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) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: Update + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.primary + scanImage.tintColor = theme.icons.default + } + + func update(avatar: String?, emojiID: String?) { + avatarView.avatar = .text(avatar) + label.text = emojiID + } +} diff --git a/MobileWallet/TariLib/Core/FFI/Contacts/Contact.swift b/MobileWallet/TariLib/Core/FFI/Contacts/Contact.swift index 0e58d104..57a7695a 100644 --- a/MobileWallet/TariLib/Core/FFI/Contacts/Contact.swift +++ b/MobileWallet/TariLib/Core/FFI/Contacts/Contact.swift @@ -59,26 +59,36 @@ final class Contact { var errorCode: Int32 = -1 let errorCodePointer = PointerHandler.pointer(for: &errorCode) let result = contact_get_alias(pointer, errorCodePointer) - guard errorCode == 0, let result = result, let alias = String(validatingUTF8: result) else { throw WalletError(code: errorCode) } + guard errorCode == 0, let result, let alias = String(validatingUTF8: result) else { throw WalletError(code: errorCode) } return alias } } + var isFavorite: Bool { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = contact_get_favourite(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + // MARK: - Initialisers init(pointer: OpaquePointer) { self.pointer = pointer } - convenience init(alias: String, addressPointer: OpaquePointer) throws { + convenience init(alias: String, isFavorite: Bool, addressPointer: OpaquePointer) throws { var errorCode: Int32 = -1 let errorCodePointer = PointerHandler.pointer(for: &errorCode) - let result = contact_create(alias, addressPointer, errorCodePointer) + let result = contact_create(alias, addressPointer, isFavorite, errorCodePointer) - guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + guard errorCode == 0, let result else { throw WalletError(code: errorCode) } - self.init(pointer: pointer) + self.init(pointer: result) } // MARK: - Deinitialiser diff --git a/MobileWallet/TariLib/Core/FFI/EmojiSet.swift b/MobileWallet/TariLib/Core/FFI/EmojiSet.swift deleted file mode 100644 index d02196f8..00000000 --- a/MobileWallet/TariLib/Core/FFI/EmojiSet.swift +++ /dev/null @@ -1,85 +0,0 @@ -// EmojiSet.swift -/* - Package MobileWallet - Created by David Main on 6/11/20 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -final class EmojiSet { - - // MARK: - Properties - - var count: 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 - } - } - - private let pointer: OpaquePointer - - // MARK: - Initialisers - - init() { - pointer = get_emoji_set() - } - - // MARK: - Actions - - func emmojiSet(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 = result else { throw WalletError(code: errorCode) } - return ByteVector(pointer: result) - } - - // MARK: - Deinitialiser - - deinit { - emoji_set_destroy(pointer) - } -} - -extension EmojiSet { - - var all: [ByteVector] { - get throws { - let count = try count - return try (0.. Bool { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_remove_contact(wallet.pointer, contact.pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + func startTransactionOutputValidation() throws -> UInt64 { let wallet = try exisingWallet @@ -399,7 +411,15 @@ final class FFIWalletManager { guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } return String(cString: cString) + } + func walletVersion(commsConfig: CommsConfig) throws -> String? { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_last_version(commsConfig.pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + guard let result else { return nil } + return String(cString: result) } func seedWords() throws -> SeedWords { diff --git a/MobileWallet/TariLib/Core/Services/TariContactsService.swift b/MobileWallet/TariLib/Core/Services/TariContactsService.swift index 5e71c0e1..0a37ea5c 100644 --- a/MobileWallet/TariLib/Core/Services/TariContactsService.swift +++ b/MobileWallet/TariLib/Core/Services/TariContactsService.swift @@ -50,6 +50,10 @@ final class TariContactsService: CoreTariService { try walletManager.upsert(contact: contact) } + @discardableResult func remove(contact: Contact) throws -> Bool { + try walletManager.remove(contact: contact) + } + func findContact(hex: String) throws -> Contact? { try allContacts.first { try $0.address.byteVector.hex == hex } } diff --git a/MobileWallet/TariLib/Core/Services/TariKeyValueService.swift b/MobileWallet/TariLib/Core/Services/TariKeyValueService.swift index 991aa429..ab277fb4 100644 --- a/MobileWallet/TariLib/Core/Services/TariKeyValueService.swift +++ b/MobileWallet/TariLib/Core/Services/TariKeyValueService.swift @@ -42,7 +42,6 @@ final class TariKeyValueService: CoreTariService { enum Key: String { case network = "SU7FM2O6Q3BU4XVN7HDD" - case version } func value(key: Key) throws -> String { diff --git a/MobileWallet/TariLib/Core/Tari.swift b/MobileWallet/TariLib/Core/Tari.swift index e42fd866..42609ac0 100644 --- a/MobileWallet/TariLib/Core/Tari.swift +++ b/MobileWallet/TariLib/Core/Tari.swift @@ -89,6 +89,13 @@ final class Tari: MainServiceable { get throws { try walletManager.walletAddress() } } + var walletVersion: String? { + get throws { + let commsConfig = try makeCommsConfig() + return try walletManager.walletVersion(commsConfig: commsConfig) + } + } + var logsURLs: [URL] { get throws { try FileManager.default.contentsOfDirectory(at: TariSettings.storageDirectory, includingPropertiesForKeys: nil) diff --git a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift b/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift index 6b2901d2..382dda02 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift @@ -189,17 +189,6 @@ private extension TariValidationService.SyncStatus { extension ConnectionMonitor { - var formattedDisplayItems: [String] { - var entries: [String] = [] - entries.append("Reachability: \(networkConnection.statusName)") - entries.append("Base node (\(NetworkManager.shared.selectedNetwork.selectedBaseNode.name)): \(syncStatus.statusName)") - entries.append("Base node connection status: \(baseNodeConnection.statusName)") - entries.append("Tor status: \(torConnection.statusName)") - entries.append("Tor bootstrap progress: \(torBootstrapProgress)%") - - return entries - } - func showDetailsPopup() { let headerSection = PopUpHeaderView() diff --git a/MobileWallet/TariLib/wallet.h b/MobileWallet/TariLib/wallet.h deleted file mode 100644 index 573ad59b..00000000 --- a/MobileWallet/TariLib/wallet.h +++ /dev/null @@ -1,3837 +0,0 @@ -// Copyright 2022. The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -// This file was generated by cargo-bindgen. Please do not edit manually. - -#include -#include -#include -#include - -/** - * The number of unique fields available. This always matches the number of variants in `OutputField`. - */ -#define OutputFields_NUM_FIELDS 10 - -enum TariTypeTag { - Text = 0, - Utxo = 1, - Commitment = 2, - U64 = 3, - I64 = 4, -}; - -enum TariUtxoSort { - ValueAsc = 0, - ValueDesc = 1, - MinedHeightAsc = 2, - MinedHeightDesc = 3, -}; - -/** - * This struct holds the detailed balance of the Output Manager Service. - */ -struct Balance; - -struct ByteVector; - -/** - * # Commitment and public key (CAPK) signatures - * - * Given a commitment `commitment = a*H + x*G` and group element `pubkey = y*G`, a CAPK signature is based on - * a representation proof of both openings: `(a, x)` and `y`. It additionally binds to arbitrary message data `m` - * via the challenge to produce a signature construction. - * - * It is used in Tari protocols as part of transaction authorization. - * - * The construction works as follows: - * - Sample scalar nonces `r_a, r_x, r_y` uniformly at random. - * - Compute ephemeral values `ephemeral_commitment = r_a*H + r_x*G` and `ephemeral_pubkey = r_y*G`. - * - Use strong Fiat-Shamir to produce a challenge `e`. If `e == 0` (this is unlikely), abort and start over. - * - Compute the responses `u_a = r_a + e*a` and `u_x = r_x + e*x` and `u_y = r_y + e*y`. - * - * The signature is the tuple `(ephemeral_commitment, ephemeral_pubkey, u_a, u_x, u_y)`. - * - * To verify: - * - The verifier computes the challenge `e` and rejects the signature if `e == 0` (this is unlikely). - * - Verification succeeds if and only if the following equations hold: `u_a*H + u*x*G == ephemeral_commitment + - * e*commitment` `u_y*G == ephemeral_pubkey + e*pubkey` - * - * We note that it is possible to make verification slightly more efficient. To do so, the verifier selects a nonzero - * scalar weight `w` uniformly at random (not through Fiat-Shamir!) and accepts the signature if and only if the - * following equation holds: - * `u_a*H + (u_x + w*u_y)*G - ephemeral_commitment - w*ephemeral_pubkey - e*commitment - (w*e)*pubkey == 0` - * The use of efficient multiscalar multiplication algorithms may also be useful for efficiency. - * The use of precomputation tables for `G` and `H` may also be useful for efficiency. - */ -struct CommitmentAndPublicKeySignature_RistrettoPublicKey__RistrettoSecretKey; - -struct CompletedTransaction; - -struct Contact; - -struct ContactsLivenessData; - -struct Covenant; - -struct EmojiSet; - -/** - * value: u64 + tag: [u8; 16] - */ -struct EncryptedValue; - -struct FeePerGramStat; - -struct FeePerGramStatsResponse; - -struct InboundTransaction; - -struct OutboundTransaction; - -/** - * Options for UTXO's - */ -struct OutputFeatures; - -/** - * Configuration for a comms node - */ -struct P2pConfig; - -/** - * The [PublicKey](trait.PublicKey.html) implementation for `ristretto255` is a thin wrapper around the dalek - * library's [RistrettoPoint](struct.RistrettoPoint.html). - * - * ## Creating public keys - * Both [PublicKey](trait.PublicKey.html) and [ByteArray](trait.ByteArray.html) are implemented on - * `RistrettoPublicKey` so all of the following will work: - * ```edition2018 - * use rand; - * use tari_crypto::{ - * keys::{PublicKey, SecretKey}, - * ristretto::{RistrettoPublicKey, RistrettoSecretKey}, - * }; - * use tari_utilities::{hex::Hex, ByteArray}; - * - * let mut rng = rand::thread_rng(); - * let _p1 = RistrettoPublicKey::from_bytes(&[ - * 224, 196, 24, 247, 200, 217, 196, 205, 215, 57, 91, 147, 234, 18, 79, 58, 217, 144, 33, - * 187, 104, 29, 252, 51, 2, 169, 217, 154, 46, 83, 230, 78, - * ]); - * let _p2 = RistrettoPublicKey::from_hex( - * &"e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e", - * ); - * let sk = RistrettoSecretKey::random(&mut rng); - * let _p3 = RistrettoPublicKey::from_secret_key(&sk); - * ``` - */ -struct RistrettoPublicKey; - -/** - * The [SecretKey](trait.SecretKey.html) implementation for [Ristretto](https://ristretto.group) is a thin wrapper - * around the Dalek [Scalar](struct.Scalar.html) type, representing a 256-bit integer (mod the group order). - * - * ## Creating secret keys - * [ByteArray](trait.ByteArray.html) and [SecretKeyFactory](trait.SecretKeyFactory.html) are implemented for - * [SecretKey](struct .SecretKey.html), so any of the following work (note that hex strings and byte array are - * little-endian): - * - * ```edition2018 - * use rand; - * use tari_crypto::{keys::SecretKey, ristretto::RistrettoSecretKey}; - * use tari_utilities::{hex::Hex, ByteArray}; - * - * let mut rng = rand::thread_rng(); - * let _k1 = RistrettoSecretKey::from_bytes(&[ - * 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - * 0, 0, - * ]); - * let _k2 = RistrettoSecretKey::from_hex(&"100000002000000030000000040000000"); - * let _k3 = RistrettoSecretKey::random(&mut rng); - * ``` - */ -struct RistrettoSecretKey; - -struct TariAddress; - -struct TariCompletedTransactions; - -struct TariContacts; - -struct TariPendingInboundTransactions; - -struct TariPendingOutboundTransactions; - -struct TariPublicKeys; - -struct TariSeedWords; - -struct TariUnblindedOutputs; - -struct TariWallet; - -/** - * The transaction kernel tracks the excess for a given transaction. For an explanation of what the excess is, and - * why it is necessary, refer to the - * [Mimblewimble TLU post](https://tlu.tarilabs.com/protocols/mimblewimble-1/sources/PITCHME.link.html?highlight=mimblewimble#mimblewimble). - * The kernel also tracks other transaction metadata, such as the lock height for the transaction (i.e. the earliest - * this transaction can be mined) and the transaction fee, in cleartext. - */ -struct TransactionKernel; - -struct TransactionSendStatus; - -struct TransportConfig; - -/** - * An unblinded output is one where the value and spending key (blinding factor) are known. This can be used to - * build both inputs and outputs (every input comes from an output) - */ -struct UnblindedOutput; - -/** - * -------------------------------- Vector ------------------------------------------------ /// - */ -struct TariVector { - enum TariTypeTag tag; - uintptr_t len; - uintptr_t cap; - void *ptr; -}; - -struct TariCoinPreview { - struct TariVector *expected_outputs; - uint64_t fee; -}; - -typedef struct TransactionKernel TariTransactionKernel; - -/** - * Define the explicit Public key implementation for the Tari base layer - */ -typedef struct RistrettoPublicKey PublicKey; - -typedef PublicKey TariPublicKey; - -/** - * Define the explicit Secret key implementation for the Tari base layer. - */ -typedef struct RistrettoSecretKey PrivateKey; - -typedef PrivateKey TariPrivateKey; - -typedef struct TariAddress TariWalletAddress; - -/** - * # A commitment and public key (CAPK) signature implementation on Ristretto - * - * `RistrettoComAndPubSig` utilises the [curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek1) - * implementation of `ristretto255` to provide CAPK signature functionality. - * - * ## Examples - * - * You can create a `RistrettoComAndPubSig` from its component parts: - * - * ```edition2018 - * # use tari_crypto::ristretto::*; - * # use tari_crypto::keys::*; - * # use tari_crypto::commitment::HomomorphicCommitment; - * # use tari_utilities::ByteArray; - * # use tari_utilities::hex::Hex; - * - * let ephemeral_commitment = HomomorphicCommitment::from_hex( - * "8063d85e151abee630e643e2b3dc47bfaeb8aa859c9d10d60847985f286aad19", - * ) - * .unwrap(); - * let ephemeral_pubkey = RistrettoPublicKey::from_hex( - * "8063d85e151abee630e643e2b3dc47bfaeb8aa859c9d10d60847985f286aad19", - * ) - * .unwrap(); - * let u_a = RistrettoSecretKey::from_bytes(b"10000000000000000000000010000000").unwrap(); - * let u_x = RistrettoSecretKey::from_bytes(b"a00000000000000000000000a0000000").unwrap(); - * let u_y = RistrettoSecretKey::from_bytes(b"a00000000000000000000000a0000000").unwrap(); - * let sig = RistrettoComAndPubSig::new(ephemeral_commitment, ephemeral_pubkey, u_a, u_x, u_y); - * ``` - * - * or you can create a signature for a commitment by signing a message with knowledge of the commitment and then - * verify it by calling the `verify_challenge` method: - * - * ```rust - * # use tari_crypto::ristretto::*; - * # use tari_crypto::keys::*; - * # use tari_crypto::hash::blake2::Blake256; - * # use digest::Digest; - * # use tari_crypto::commitment::HomomorphicCommitmentFactory; - * # use tari_crypto::ristretto::pedersen::*; - * use tari_crypto::ristretto::pedersen::commitment_factory::PedersenCommitmentFactory; - * use tari_utilities::hex::Hex; - * - * let mut rng = rand::thread_rng(); - * let a_val = RistrettoSecretKey::random(&mut rng); - * let x_val = RistrettoSecretKey::random(&mut rng); - * let y_val = RistrettoSecretKey::random(&mut rng); - * let a_nonce = RistrettoSecretKey::random(&mut rng); - * let x_nonce = RistrettoSecretKey::random(&mut rng); - * let y_nonce = RistrettoSecretKey::random(&mut rng); - * let e = Blake256::digest(b"Maskerade"); // In real life, this should be strong Fiat-Shamir! - * let factory = PedersenCommitmentFactory::default(); - * let commitment = factory.commit(&x_val, &a_val); - * let pubkey = RistrettoPublicKey::from_secret_key(&y_val); - * let sig = RistrettoComAndPubSig::sign( - * &a_val, &x_val, &y_val, &a_nonce, &x_nonce, &y_nonce, &e, &factory, - * ) - * .unwrap(); - * assert!(sig.verify_challenge(&commitment, &pubkey, &e, &factory, &mut rng)); - * ``` - */ -typedef struct CommitmentAndPublicKeySignature_RistrettoPublicKey__RistrettoSecretKey RistrettoComAndPubSig; - -/** - * Define the explicit Commitment Signature implementation for the Tari base layer. - */ -typedef RistrettoComAndPubSig ComAndPubSignature; - -typedef ComAndPubSignature TariComAndPubSignature; - -typedef struct UnblindedOutput TariUnblindedOutput; - -typedef struct OutputFeatures TariOutputFeatures; - -typedef struct Covenant TariCovenant; - -typedef struct EncryptedValue TariEncryptedValue; - -typedef struct Contact TariContact; - -typedef struct ContactsLivenessData TariContactsLivenessData; - -typedef struct CompletedTransaction TariCompletedTransaction; - -typedef struct OutboundTransaction TariPendingOutboundTransaction; - -typedef struct InboundTransaction TariPendingInboundTransaction; - -typedef struct TransactionSendStatus TariTransactionSendStatus; - -typedef struct TransportConfig TariTransportConfig; - -typedef struct P2pConfig TariCommsConfig; - -typedef struct Balance TariBalance; - -typedef struct FeePerGramStatsResponse TariFeePerGramStats; - -typedef struct FeePerGramStat TariFeePerGramStat; - -struct TariUtxo { - const char *commitment; - uint64_t value; - uint64_t mined_height; - uint64_t mined_timestamp; - uint8_t status; -}; - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * Initialize a new `TariVector` - * - * ## Arguments - * `tag` - A predefined type-tag of the vector's payload. - * - * ## Returns - * `*mut TariVector` - Returns a pointer to a `TariVector`. - * - * # Safety - * `destroy_tari_vector()` must be called to free the allocated memory. - */ -struct TariVector *create_tari_vector(enum TariTypeTag tag); - -/** - * Appending a given value to the back of the vector. - * - * ## Arguments - * `s` - An item to push. - * - * ## Returns - * - * - * # Safety - * `destroy_tari_vector()` must be called to free the allocated memory. - */ -void tari_vector_push_string(struct TariVector *tv, const char *s, int32_t *error_ptr); - -/** - * Frees memory allocated for `TariVector`. - * - * ## Arguments - * `v` - The pointer to `TariVector` - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void destroy_tari_vector(struct TariVector *v); - -/** - * Frees memory allocated for `TariCoinPreview`. - * - * ## Arguments - * `v` - The pointer to `TariCoinPreview` - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void destroy_tari_coin_preview(struct TariCoinPreview *p); - -/** - * -------------------------------- Strings ------------------------------------------------ /// - * Frees memory for a char array - * - * ## Arguments - * `ptr` - The pointer to be freed - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C. - * - * # Safety - * None - */ -void string_destroy(char *ptr); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- Transaction Kernel ------------------------------------- /// - * Gets the excess for a TariTransactionKernel - * - * ## Arguments - * `x` - The pointer to a TariTransactionKernel - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array. Note that it returns empty if there - * was an error - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak - */ -char *transaction_kernel_get_excess_hex(TariTransactionKernel *kernel, - int *error_out); - -/** - * Gets the public nonce for a TariTransactionKernel - * - * ## Arguments - * `x` - The pointer to a TariTransactionKernel - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array. Note that it returns empty if there - * was an error - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak - */ -char *transaction_kernel_get_excess_public_nonce_hex(TariTransactionKernel *kernel, - int *error_out); - -/** - * Gets the signature for a TariTransactionKernel - * - * ## Arguments - * `x` - The pointer to a TariTransactionKernel - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array. Note that it returns empty if there - * was an error - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak - */ -char *transaction_kernel_get_excess_signature_hex(TariTransactionKernel *kernel, - int *error_out); - -/** - * Frees memory for a TariTransactionKernel - * - * ## Arguments - * `x` - The pointer to a TariTransactionKernel - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void transaction_kernel_destroy(TariTransactionKernel *x); - -/** - * -------------------------------------------------------------------------------------------- /// - * -------------------------------- ByteVector ------------------------------------------------ /// - * Creates a ByteVector - * - * ## Arguments - * `byte_array` - The pointer to the byte array - * `element_count` - The number of elements in byte_array - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut ByteVector` - Pointer to the created ByteVector. Note that it will be ptr::null_mut() - * if the byte_array pointer was null or if the elements in the byte_vector don't match - * element_count when it is created - * - * # Safety - * The ```byte_vector_destroy``` function must be called when finished with a ByteVector to prevent a memory leak - */ -struct ByteVector *byte_vector_create(const unsigned char *byte_array, - unsigned int element_count, - int *error_out); - -/** - * Frees memory for a ByteVector - * - * ## Arguments - * `bytes` - The pointer to a ByteVector - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void byte_vector_destroy(struct ByteVector *bytes); - -/** - * Gets a c_uchar at position in a ByteVector - * - * ## Arguments - * `ptr` - The pointer to a ByteVector - * `position` - The integer position - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_uchar` - Returns a character. Note that the character will be a null terminator (0) if ptr - * is null or if the position is invalid - * - * # Safety - * None - */ -unsigned char byte_vector_get_at(struct ByteVector *ptr, - unsigned int position, - int *error_out); - -/** - * Gets the number of elements in a ByteVector - * - * ## Arguments - * `ptr` - The pointer to a ByteVector - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_uint` - Returns the integer number of elements in the ByteVector. Note that it will be zero - * if ptr is null - * - * # Safety - * None - */ -unsigned int byte_vector_get_length(const struct ByteVector *vec, - int *error_out); - -/** - * -------------------------------------------------------------------------------------------- /// - * -------------------------------- Public Key ------------------------------------------------ /// - * Creates a TariPublicKey from a ByteVector - * - * ## Arguments - * `bytes` - The pointer to a ByteVector - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `TariPublicKey` - Returns a public key. Note that it will be ptr::null_mut() if bytes is null or - * if there was an error with the contents of bytes - * - * # Safety - * The ```public_key_destroy``` function must be called when finished with a TariPublicKey to prevent a memory leak - */ -TariPublicKey *public_key_create(struct ByteVector *bytes, - int *error_out); - -/** - * Frees memory for a TariPublicKey - * - * ## Arguments - * `pk` - The pointer to a TariPublicKey - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void public_key_destroy(TariPublicKey *pk); - -/** - * Frees memory for TariPublicKeys - * - * ## Arguments - * `pks` - The pointer to TariPublicKeys - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void public_keys_destroy(struct TariPublicKeys *pks); - -/** - * Gets a ByteVector from a TariPublicKey - * - * ## Arguments - * `pk` - The pointer to a TariPublicKey - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut ByteVector` - Returns a pointer to a ByteVector. Note that it returns ptr::null_mut() if pk is null - * - * # Safety - * The ```byte_vector_destroy``` function must be called when finished with the ByteVector to prevent a memory leak. - */ -struct ByteVector *public_key_get_bytes(TariPublicKey *pk, - int *error_out); - -/** - * Creates a TariPublicKey from a TariPrivateKey - * - * ## Arguments - * `secret_key` - The pointer to a TariPrivateKey - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPublicKey` - Returns a pointer to a TariPublicKey - * - * # Safety - * The ```private_key_destroy``` method must be called when finished with a private key to prevent a memory leak - */ -TariPublicKey *public_key_from_private_key(TariPrivateKey *secret_key, - int *error_out); - -/** - * Creates a TariPublicKey from a char array - * - * ## Arguments - * `key` - The pointer to a char array which is hex encoded - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPublicKey` - Returns a pointer to a TariPublicKey. Note that it returns ptr::null_mut() - * if key is null or if there was an error creating the TariPublicKey from key - * - * # Safety - * The ```public_key_destroy``` method must be called when finished with a TariPublicKey to prevent a memory leak - */ -TariPublicKey *public_key_from_hex(const char *key, - int *error_out); - -/** - * -------------------------------------------------------------------------------------------- /// - * -------------------------------- Tari Address ---------------------------------------------- /// - * Creates a TariWalletAddress from a ByteVector - * - * ## Arguments - * `bytes` - The pointer to a ByteVector - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `TariWalletAddress` - Returns a public key. Note that it will be ptr::null_mut() if bytes is null or - * if there was an error with the contents of bytes - * - * # Safety - * The ```public_key_destroy``` function must be called when finished with a TariWalletAddress to prevent a memory leak - */ -TariWalletAddress *tari_address_create(struct ByteVector *bytes, - int *error_out); - -/** - * Frees memory for a TariWalletAddress - * - * ## Arguments - * `pk` - The pointer to a TariWalletAddress - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void tari_address_destroy(TariWalletAddress *address); - -/** - * Gets a ByteVector from a TariWalletAddress - * - * ## Arguments - * `address` - The pointer to a TariWalletAddress - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut ByteVector` - Returns a pointer to a ByteVector. Note that it returns ptr::null_mut() if address is null - * - * # Safety - * The ```byte_vector_destroy``` function must be called when finished with the ByteVector to prevent a memory leak. - */ -struct ByteVector *tari_address_get_bytes(TariWalletAddress *address, - int *error_out); - -/** - * Creates a TariWalletAddress from a TariPrivateKey - * - * ## Arguments - * `secret_key` - The pointer to a TariPrivateKey - * `network` - an u8 indicating the network - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - Returns a pointer to a TariWalletAddress - * - * # Safety - * The ```private_key_destroy``` method must be called when finished with a private key to prevent a memory leak - */ -TariWalletAddress *tari_address_from_private_key(TariPrivateKey *secret_key, - unsigned int network, - int *error_out); - -/** - * Creates a TariWalletAddress from a char array - * - * ## Arguments - * `address` - The pointer to a char array which is hex encoded - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - Returns a pointer to a TariWalletAddress. Note that it returns ptr::null_mut() - * if key is null or if there was an error creating the TariWalletAddress from key - * - * # Safety - * The ```public_key_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory leak - */ -TariWalletAddress *tari_address_from_hex(const char *address, - int *error_out); - -/** - * Creates a char array from a TariWalletAddress in emoji format - * - * ## Arguments - * `address` - The pointer to a TariWalletAddress - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array. Note that it returns empty - * if emoji is null or if there was an error creating the emoji string from TariWalletAddress - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak - */ -char *tari_address_to_emoji_id(TariWalletAddress *address, - int *error_out); - -/** - * Creates a TariWalletAddress from a char array in emoji format - * - * ## Arguments - * `const *c_char` - The pointer to a TariWalletAddress - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut c_char` - Returns a pointer to a TariWalletAddress. Note that it returns null on error. - * - * # Safety - * The ```public_key_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory leak - */ -TariWalletAddress *emoji_id_to_tari_address(const char *emoji, - int *error_out); - -/** - * -------------------------------------------------------------------------------------------- /// - * - * ------------------------------- ComAndPubSignature Signature ---------------------------------------/// - * Creates a TariComAndPubSignature from `u_a`. `u_x`, `u_y`, `ephemeral_pubkey` and `ephemeral_commitment_bytes` - * ByteVectors - * - * ## Arguments - * `ephemeral_commitment_bytes` - The public ephemeral commitment component as a ByteVector - * `ephemeral_pubkey_bytes` - The public ephemeral pubkey component as a ByteVector - * `u_a_bytes` - The u_a signature component as a ByteVector - * `u_x_bytes` - The u_x signature component as a ByteVector - * `u_y_bytes` - The u_y signature component as a ByteVector - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `TariComAndPubSignature` - Returns a ComAndPubS signature. Note that it will be ptr::null_mut() if any argument is - * null or if there was an error with the contents of bytes - * - * # Safety - * The ```commitment_signature_destroy``` function must be called when finished with a TariComAndPubSignature to - * prevent a memory leak - */ -TariComAndPubSignature *commitment_and_public_signature_create_from_bytes(const struct ByteVector *ephemeral_commitment_bytes, - const struct ByteVector *ephemeral_pubkey_bytes, - const struct ByteVector *u_a_bytes, - const struct ByteVector *u_x_bytes, - const struct ByteVector *u_y_bytes, - int *error_out); - -/** - * Frees memory for a TariComAndPubSignature - * - * ## Arguments - * `compub_sig` - The pointer to a TariComAndPubSignature - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void commitment_and_public_signature_destroy(TariComAndPubSignature *compub_sig); - -/** - * -------------------------------------------------------------------------------------------- /// - * -------------------------------- Unblinded utxo -------------------------------------------- /// - * Creates an unblinded output - * - * ## Arguments - * `amount` - The value of the UTXO in MicroTari - * `spending_key` - The private spending key - * `source_address` - The tari address of the source of the transaction - * `features` - Options for an output's structure or use - * `metadata_signature` - UTXO signature with the script offset private key, k_O - * `sender_offset_public_key` - Tari script offset pubkey, K_O - * `script_private_key` - Tari script private key, k_S, is used to create the script signature - * `covenant` - The covenant that will be executed when spending this output - * `message` - The message that the transaction will have - * `encrypted_value` - Encrypted value. - * `minimum_value_promise` - The minimum value of the commitment that is proven by the range proof - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * TariUnblindedOutput - Returns the TransactionID of the generated transaction, note that it will be zero if the - * transaction is null - * - * # Safety - * The ```tari_unblinded_output_destroy``` function must be called when finished with a TariUnblindedOutput to - * prevent a memory leak - */ -TariUnblindedOutput *create_tari_unblinded_output(unsigned long long amount, - TariPrivateKey *spending_key, - TariOutputFeatures *features, - const char *script, - const char *input_data, - TariComAndPubSignature *metadata_signature, - TariPublicKey *sender_offset_public_key, - TariPrivateKey *script_private_key, - TariCovenant *covenant, - TariEncryptedValue *encrypted_value, - unsigned long long minimum_value_promise, - unsigned long long script_lock_height, - int *error_out); - -/** - * Frees memory for a TariUnblindedOutput - * - * ## Arguments - * `output` - The pointer to a TariUnblindedOutput - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void tari_unblinded_output_destroy(TariUnblindedOutput *output); - -/** - * returns the TariUnblindedOutput as a json string - * - * ## Arguments - * `output` - The pointer to a TariUnblindedOutput - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array. Note that it returns an empty char array if - * TariUnblindedOutput is null or the position is invalid - * - * # Safety - * The ```tari_unblinded_output_destroy``` function must be called when finished with a TariUnblindedOutput to - * prevent a memory leak - */ -char *tari_unblinded_output_to_json(TariUnblindedOutput *output, - int *error_out); - -/** - * Creates a TariUnblindedOutput from a char array - * - * ## Arguments - * `output_json` - The pointer to a char array which is json of the TariUnblindedOutput - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariUnblindedOutput` - Returns a pointer to a TariUnblindedOutput. Note that it returns ptr::null_mut() - * if key is null or if there was an error creating the TariUnblindedOutput from key - * - * # Safety - * The ```tari_unblinded_output_destroy``` function must be called when finished with a TariUnblindedOutput to - */ -TariUnblindedOutput *create_tari_unblinded_output_from_json(const char *output_json, - int *error_out); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- TariUnblindedOutputs ------------------------------------/// - * Gets the length of TariUnblindedOutputs - * - * ## Arguments - * `outputs` - The pointer to a TariUnblindedOutputs - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_uint` - Returns number of elements in , zero if outputs is null - * - * # Safety - * None - */ -unsigned int unblinded_outputs_get_length(struct TariUnblindedOutputs *outputs, - int *error_out); - -/** - * Gets a TariUnblindedOutput from TariUnblindedOutputs at position - * - * ## Arguments - * `outputs` - The pointer to a TariUnblindedOutputs - * `position` - The integer position - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariUnblindedOutput` - Returns a TariUnblindedOutput, note that it returns ptr::null_mut() if - * TariUnblindedOutputs is null or position is invalid - * - * # Safety - * The ```contact_destroy``` method must be called when finished with a TariContact to prevent a memory leak - */ -TariUnblindedOutput *unblinded_outputs_get_at(struct TariUnblindedOutputs *outputs, - unsigned int position, - int *error_out); - -/** - * Gets a TariUnblindedOutput from TariUnblindedOutputs at position - * - * ## Arguments - * `outputs` - The pointer to a TariUnblindedOutputs - * `position` - The integer position - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariUnblindedOutput` - Returns a TariUnblindedOutput, note that it returns ptr::null_mut() if - * TariUnblindedOutputs is null or position is invalid - * - * # Safety - * The ```contact_destroy``` method must be called when finished with a TariContact to prevent a memory leak - */ -unsigned long long *unblinded_outputs_received_tx_id_get_at(struct TariUnblindedOutputs *outputs, - unsigned int position, - int *error_out); - -/** - * Frees memory for a TariUnblindedOutputs - * - * ## Arguments - * `outputs` - The pointer to a TariUnblindedOutputs - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void unblinded_outputs_destroy(struct TariUnblindedOutputs *outputs); - -/** - * Import an external UTXO into the wallet as a non-rewindable (i.e. non-recoverable) output. This will add a spendable - * UTXO (as EncumberedToBeReceived) and create a faux completed transaction to record the event. - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `amount` - The value of the UTXO in MicroTari - * `spending_key` - The private spending key - * `source_address` - The tari address of the source of the transaction - * `features` - Options for an output's structure or use - * `metadata_signature` - UTXO signature with the script offset private key, k_O - * `sender_offset_public_key` - Tari script offset pubkey, K_O - * `script_private_key` - Tari script private key, k_S, is used to create the script signature - * `covenant` - The covenant that will be executed when spending this output - * `message` - The message that the transaction will have - * `encrypted_value` - Encrypted value. - * `minimum_value_promise` - The minimum value of the commitment that is proven by the range proof - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the TransactionID of the generated transaction, note that it will be zero if the - * transaction is null - * - * # Safety - * None - */ -unsigned long long wallet_import_external_utxo_as_non_rewindable(struct TariWallet *wallet, - TariUnblindedOutput *output, - TariWalletAddress *source_address, - const char *message, - int *error_out); - -/** - * Get the TariUnblindedOutputs from a TariWallet - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariUnblindedOutputs` - returns the unspent unblinded outputs, note that it returns ptr::null_mut() if - * wallet is null - * - * # Safety - * The ```unblinded_outputs_destroy``` method must be called when finished with a TariUnblindedOutput to prevent a - * memory leak - */ -struct TariUnblindedOutputs *wallet_get_unspent_outputs(struct TariWallet *wallet, - int *error_out); - -/** - * -------------------------------------------------------------------------------------------- /// - * -------------------------------- Private Key ----------------------------------------------- /// - * Creates a TariPrivateKey from a ByteVector - * - * ## Arguments - * `bytes` - The pointer to a ByteVector - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPrivateKey` - Returns a pointer to a TariPrivateKey. Note that it returns ptr::null_mut() - * if bytes is null or if there was an error creating the TariPrivateKey from bytes - * - * # Safety - * The ```private_key_destroy``` method must be called when finished with a TariPrivateKey to prevent a memory leak - */ -TariPrivateKey *private_key_create(struct ByteVector *bytes, - int *error_out); - -/** - * Frees memory for a TariPrivateKey - * - * ## Arguments - * `pk` - The pointer to a TariPrivateKey - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void private_key_destroy(TariPrivateKey *pk); - -/** - * Gets a ByteVector from a TariPrivateKey - * - * ## Arguments - * `pk` - The pointer to a TariPrivateKey - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut ByteVectror` - Returns a pointer to a ByteVector. Note that it returns ptr::null_mut() - * if pk is null - * - * # Safety - * The ```byte_vector_destroy``` must be called when finished with a ByteVector to prevent a memory leak - */ -struct ByteVector *private_key_get_bytes(TariPrivateKey *pk, - int *error_out); - -/** - * Generates a TariPrivateKey - * - * ## Arguments - * `()` - Does not take any arguments - * - * ## Returns - * `*mut TariPrivateKey` - Returns a pointer to a TariPrivateKey - * - * # Safety - * The ```private_key_destroy``` method must be called when finished with a TariPrivateKey to prevent a memory leak. - */ -TariPrivateKey *private_key_generate(void); - -/** - * Creates a TariPrivateKey from a char array - * - * ## Arguments - * `key` - The pointer to a char array which is hex encoded - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPrivateKey` - Returns a pointer to a TariPrivateKey. Note that it returns ptr::null_mut() - * if key is null or if there was an error creating the TariPrivateKey from key - * - * # Safety - * The ```private_key_destroy``` method must be called when finished with a TariPrivateKey to prevent a memory leak - */ -TariPrivateKey *private_key_from_hex(const char *key, - int *error_out); - -/** - * -------------------------------------------------------------------------------------------- /// - * --------------------------------------- Covenant --------------------------------------------/// - * Creates a TariCovenant from a ByteVector containing the covenant bytes - * - * ## Arguments - * `covenant_bytes` - The covenant bytes as a ByteVector - * - * ## Returns - * `TariCovenant` - Returns a commitment signature. Note that it will be ptr::null_mut() if any argument is - * null or if there was an error with the contents of bytes - * - * # Safety - * The ```covenant_destroy``` function must be called when finished with a TariCovenant to prevent a memory leak - */ -TariCovenant *covenant_create_from_bytes(const struct ByteVector *covenant_bytes, - int *error_out); - -/** - * Frees memory for a TariCovenant - * - * ## Arguments - * `covenant` - The pointer to a TariCovenant - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void covenant_destroy(TariCovenant *covenant); - -/** - * -------------------------------------------------------------------------------------------- /// - * --------------------------------------- EncryptedValue --------------------------------------------/// - * Creates a TariEncryptedValue from a ByteVector containing the encrypted_value bytes - * - * ## Arguments - * `encrypted_value_bytes` - The encrypted_value bytes as a ByteVector - * - * ## Returns - * `TariEncryptedValue` - Returns an encrypted value. Note that it will be ptr::null_mut() if any argument is - * null or if there was an error with the contents of bytes - * - * # Safety - * The ```encrypted_value_destroy``` function must be called when finished with a TariEncryptedValue to prevent a - * memory leak - */ -TariEncryptedValue *encrypted_value_create_from_bytes(const struct ByteVector *encrypted_value_bytes, - int *error_out); - -/** - * Creates a ByteVector containing the encrypted_value bytes from a TariEncryptedValue - * - * ## Arguments - * `encrypted_value` - The encrypted_value as a TariEncryptedValue - * - * ## Returns - * `ByteVector` - Returns a ByteVector containing the encrypted_value bytes. Note that it will be ptr::null_mut() if - * any argument is null or if there was an error with the contents of bytes - * - * # Safety - * The ```encrypted_value_destroy``` function must be called when finished with a TariEncryptedValue to prevent a - * memory leak - */ -struct ByteVector *encrypted_value_as_bytes(const TariEncryptedValue *encrypted_value, - int *error_out); - -/** - * Frees memory for a TariEncryptedValue - * - * ## Arguments - * `encrypted_value` - The pointer to a TariEncryptedValue - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void encrypted_value_destroy(TariEncryptedValue *encrypted_value); - -/** - * -------------------------------------------------------------------------------------------- /// - * ---------------------------------- Output Features ------------------------------------------/// - * Creates a TariOutputFeatures from byte values - * - * ## Arguments - * `version` - The encoded value of the version as a byte - * `output_type` - The encoded value of the output type as a byte - * `maturity` - The encoded value maturity as bytes - * `metadata` - The metadata componenet as a ByteVector. It cannot be null - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `TariOutputFeatures` - Returns an output features object. Note that it will be ptr::null_mut() if any mandatory - * arguments are null or if there was an error with the contents of bytes - * - * # Safety - * The ```output_features_destroy``` function must be called when finished with a TariOutputFeatures to - * prevent a memory leak - */ -TariOutputFeatures *output_features_create_from_bytes(unsigned char version, - unsigned short output_type, - unsigned long long maturity, - const struct ByteVector *metadata, - int *error_out); - -/** - * Frees memory for a TariOutputFeatures - * - * ## Arguments - * `output_features` - The pointer to a TariOutputFeatures - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void output_features_destroy(TariOutputFeatures *output_features); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- Seed Words ----------------------------------------------/// - * Create an empty instance of TariSeedWords - * - * ## Arguments - * None - * - * ## Returns - * `TariSeedWords` - Returns an empty TariSeedWords instance - * - * # Safety - * None - */ -struct TariSeedWords *seed_words_create(void); - -/** - * Create a TariSeedWords instance containing the entire mnemonic wordlist for the requested language - * - * ## Arguments - * `language` - The required language as a string - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `TariSeedWords` - Returns the TariSeedWords instance containing the entire mnemonic wordlist for the - * requested language. - * - * # Safety - * The `seed_words_destroy` method must be called when finished with a TariSeedWords instance from rust to prevent a - * memory leak - */ -struct TariSeedWords *seed_words_get_mnemonic_word_list_for_language(const char *language, - int *error_out); - -/** - * Gets the length of TariSeedWords - * - * ## Arguments - * `seed_words` - The pointer to a TariSeedWords - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_uint` - Returns number of elements in seed_words, zero if seed_words is null - * - * # Safety - * None - */ -unsigned int seed_words_get_length(const struct TariSeedWords *seed_words, - int *error_out); - -/** - * Gets a seed word from TariSeedWords at position - * - * ## Arguments - * `seed_words` - The pointer to a TariSeedWords - * `position` - The integer position - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array. Note that it returns an empty char array if - * TariSeedWords collection is null or the position is invalid - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak - */ -char *seed_words_get_at(struct TariSeedWords *seed_words, - unsigned int position, - int *error_out); - -/** - * Add a word to the provided TariSeedWords instance - * - * ## Arguments - * `seed_words` - The pointer to a TariSeedWords - * `word` - Word to add - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * 'c_uchar' - Returns a u8 version of the `SeedWordPushResult` enum indicating whether the word was not a valid seed - * word, if the push was successful and whether the push was successful and completed the full Seed Phrase. - * `seed_words` is only modified in the event of a `SuccessfulPush`. - * '0' -> InvalidSeedWord - * '1' -> SuccessfulPush - * '2' -> SeedPhraseComplete - * '3' -> InvalidSeedPhrase - * '4' -> NoLanguageMatch, - * # Safety - * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak - */ -unsigned char seed_words_push_word(struct TariSeedWords *seed_words, - const char *word, - int *error_out); - -/** - * Frees memory for a TariSeedWords - * - * ## Arguments - * `seed_words` - The pointer to a TariSeedWords - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void seed_words_destroy(struct TariSeedWords *seed_words); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- Contact -------------------------------------------------/// - * Creates a TariContact - * - * ## Arguments - * `alias` - The pointer to a char array - * `address` - The pointer to a TariWalletAddress - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariContact` - Returns a pointer to a TariContact. Note that it returns ptr::null_mut() - * if alias is null or if pk is null - * - * # Safety - * The ```contact_destroy``` method must be called when finished with a TariContact - */ -TariContact *contact_create(const char *alias, - TariWalletAddress *address, - int *error_out); - -/** - * Gets the alias of the TariContact - * - * ## Arguments - * `contact` - The pointer to a TariContact - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array. Note that it returns an empty char array if - * contact is null - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak - */ -char *contact_get_alias(TariContact *contact, - int *error_out); - -/** - * Gets the TariWalletAddress of the TariContact - * - * ## Arguments - * `contact` - The pointer to a TariContact - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - Returns a pointer to a TariWalletAddress. Note that it returns - * ptr::null_mut() if contact is null - * - * # Safety - * The ```tari_address_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory leak - */ -TariWalletAddress *contact_get_tari_address(TariContact *contact, - int *error_out); - -/** - * Frees memory for a TariContact - * - * ## Arguments - * `contact` - The pointer to a TariContact - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void contact_destroy(TariContact *contact); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- Contacts -------------------------------------------------/// - * Gets the length of TariContacts - * - * ## Arguments - * `contacts` - The pointer to a TariContacts - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_uint` - Returns number of elements in , zero if contacts is null - * - * # Safety - * None - */ -unsigned int contacts_get_length(struct TariContacts *contacts, - int *error_out); - -/** - * Gets a TariContact from TariContacts at position - * - * ## Arguments - * `contacts` - The pointer to a TariContacts - * `position` - The integer position - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariContact` - Returns a TariContact, note that it returns ptr::null_mut() if contacts is - * null or position is invalid - * - * # Safety - * The ```contact_destroy``` method must be called when finished with a TariContact to prevent a memory leak - */ -TariContact *contacts_get_at(struct TariContacts *contacts, - unsigned int position, - int *error_out); - -/** - * Frees memory for a TariContacts - * - * ## Arguments - * `contacts` - The pointer to a TariContacts - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void contacts_destroy(struct TariContacts *contacts); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- Contacts Liveness Data ----------------------------------/// - * Gets the public_key from a TariContactsLivenessData - * - * ## Arguments - * `liveness_data` - The pointer to a TariContactsLivenessData - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - Returns a pointer to a TariWalletAddress. Note that it returns ptr::null_mut() if - * liveness_data is null. - * - * # Safety - * The ```liveness_data_destroy``` method must be called when finished with a TariContactsLivenessData to prevent a - * memory leak - */ -TariWalletAddress *liveness_data_get_public_key(TariContactsLivenessData *liveness_data, - int *error_out); - -/** - * Gets the latency in milli-seconds (ms) from a TariContactsLivenessData - * - * ## Arguments - * `liveness_data` - The pointer to a TariContactsLivenessData - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut c_int` - Returns a pointer to a c_int if the optional latency data (in milli-seconds (ms)) exists, with a - * value of '-1' if it is None. Note that it also returns '-1' if liveness_data is null. - * - * # Safety - * The ```liveness_data_destroy``` method must be called when finished with a TariContactsLivenessData to prevent a - * memory leak - */ -int liveness_data_get_latency(TariContactsLivenessData *liveness_data, - int *error_out); - -/** - * Gets the last_seen time (in local time) from a TariContactsLivenessData - * - * ## Arguments - * `liveness_data` - The pointer to a TariContactsLivenessData - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array if the optional last_seen data exists, with a value of '?' if it - * is None. Note that it returns ptr::null_mut() if liveness_data is null. - * - * # Safety - * The ```liveness_data_destroy``` method must be called when finished with a TariContactsLivenessData to prevent a - * memory leak - */ -char *liveness_data_get_last_seen(TariContactsLivenessData *liveness_data, - int *error_out); - -/** - * Gets the message_type (ContactMessageType enum) from a TariContactsLivenessData - * - * ## Arguments - * `liveness_data` - The pointer to a TariContactsLivenessData - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_int` - Returns the status which corresponds to: - * | Value | Interpretation | - * |---|---| - * | -1 | NullError | - * | 0 | Ping | - * | 1 | Pong | - * | 2 | NoMessage | - * - * # Safety - * The ```liveness_data_destroy``` method must be called when finished with a TariContactsLivenessData to prevent a - * memory leak - */ -int liveness_data_get_message_type(TariContactsLivenessData *liveness_data, - int *error_out); - -/** - * Gets the online_status (ContactOnlineStatus enum) from a TariContactsLivenessData - * - * ## Arguments - * `liveness_data` - The pointer to a TariContactsLivenessData - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_int` - Returns the status which corresponds to: - * | Value | Interpretation | - * |---|---| - * | -1 | NullError | - * | 0 | Online | - * | 1 | Offline | - * | 2 | NeverSeen | - * | 3 | Banned | - * - * # Safety - * The ```liveness_data_destroy``` method must be called when finished with a TariContactsLivenessData to prevent a - * memory leak - */ -const char *liveness_data_get_online_status(TariContactsLivenessData *liveness_data, - int *error_out); - -/** - * Frees memory for a TariContactsLivenessData - * - * ## Arguments - * `liveness_data` - The pointer to a TariContactsLivenessData - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void liveness_data_destroy(TariContactsLivenessData *liveness_data); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- CompletedTransactions ----------------------------------- /// - * Gets the length of a TariCompletedTransactions - * - * ## Arguments - * `transactions` - The pointer to a TariCompletedTransactions - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_uint` - Returns the number of elements in a TariCompletedTransactions, note that it will be - * zero if transactions is null - * - * # Safety - * None - */ -unsigned int completed_transactions_get_length(struct TariCompletedTransactions *transactions, - int *error_out); - -/** - * Gets a TariCompletedTransaction from a TariCompletedTransactions at position - * - * ## Arguments - * `transactions` - The pointer to a TariCompletedTransactions - * `position` - The integer position - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariCompletedTransaction` - Returns a pointer to a TariCompletedTransaction, - * note that ptr::null_mut() is returned if transactions is null or position is invalid - * - * # Safety - * The ```completed_transaction_destroy``` method must be called when finished with a TariCompletedTransaction to - * prevent a memory leak - */ -TariCompletedTransaction *completed_transactions_get_at(struct TariCompletedTransactions *transactions, - unsigned int position, - int *error_out); - -/** - * Frees memory for a TariCompletedTransactions - * - * ## Arguments - * `transactions` - The pointer to a TariCompletedTransaction - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void completed_transactions_destroy(struct TariCompletedTransactions *transactions); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- OutboundTransactions ------------------------------------ /// - * Gets the length of a TariPendingOutboundTransactions - * - * ## Arguments - * `transactions` - The pointer to a TariPendingOutboundTransactions - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_uint` - Returns the number of elements in a TariPendingOutboundTransactions, note that it will be - * zero if transactions is null - * - * # Safety - * None - */ -unsigned int pending_outbound_transactions_get_length(struct TariPendingOutboundTransactions *transactions, - int *error_out); - -/** - * Gets a TariPendingOutboundTransaction of a TariPendingOutboundTransactions - * - * ## Arguments - * `transactions` - The pointer to a TariPendingOutboundTransactions - * `position` - The integer position - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPendingOutboundTransaction` - Returns a pointer to a TariPendingOutboundTransaction, - * note that ptr::null_mut() is returned if transactions is null or position is invalid - * - * # Safety - * The ```pending_outbound_transaction_destroy``` method must be called when finished with a - * TariPendingOutboundTransaction to prevent a memory leak - */ -TariPendingOutboundTransaction *pending_outbound_transactions_get_at(struct TariPendingOutboundTransactions *transactions, - unsigned int position, - int *error_out); - -/** - * Frees memory for a TariPendingOutboundTransactions - * - * ## Arguments - * `transactions` - The pointer to a TariPendingOutboundTransactions - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void pending_outbound_transactions_destroy(struct TariPendingOutboundTransactions *transactions); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- InboundTransactions ------------------------------------- /// - * Gets the length of a TariPendingInboundTransactions - * - * ## Arguments - * `transactions` - The pointer to a TariPendingInboundTransactions - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_uint` - Returns the number of elements in a TariPendingInboundTransactions, note that - * it will be zero if transactions is null - * - * # Safety - * None - */ -unsigned int pending_inbound_transactions_get_length(struct TariPendingInboundTransactions *transactions, - int *error_out); - -/** - * Gets a TariPendingInboundTransaction of a TariPendingInboundTransactions - * - * ## Arguments - * `transactions` - The pointer to a TariPendingInboundTransactions - * `position` - The integer position - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPendingOutboundTransaction` - Returns a pointer to a TariPendingInboundTransaction, - * note that ptr::null_mut() is returned if transactions is null or position is invalid - * - * # Safety - * The ```pending_inbound_transaction_destroy``` method must be called when finished with a - * TariPendingOutboundTransaction to prevent a memory leak - */ -TariPendingInboundTransaction *pending_inbound_transactions_get_at(struct TariPendingInboundTransactions *transactions, - unsigned int position, - int *error_out); - -/** - * Frees memory for a TariPendingInboundTransactions - * - * ## Arguments - * `transactions` - The pointer to a TariPendingInboundTransactions - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void pending_inbound_transactions_destroy(struct TariPendingInboundTransactions *transactions); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- CompletedTransaction ------------------------------------- /// - * Gets the TransactionID of a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the TransactionID, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long completed_transaction_get_transaction_id(TariCompletedTransaction *transaction, - int *error_out); - -/** - * Gets the destination TariWalletAddress of a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - Returns the destination TariWalletAddress, note that it will be - * ptr::null_mut() if transaction is null - * - * # Safety - * The ```tari_address_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory leak - */ -TariWalletAddress *completed_transaction_get_destination_tari_address(TariCompletedTransaction *transaction, - int *error_out); - -/** - * Gets the TariTransactionKernel of a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariTransactionKernel` - Returns the transaction kernel, note that it will be - * ptr::null_mut() if transaction is null, if the transaction status is Pending, or if the number of kernels is not - * exactly one. - * - * # Safety - * The ```transaction_kernel_destroy``` method must be called when finished with a TariTransactionKernel to prevent a - * memory leak - */ -TariTransactionKernel *completed_transaction_get_transaction_kernel(TariCompletedTransaction *transaction, - int *error_out); - -/** - * Gets the source TariWalletAddress of a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - Returns the source TariWalletAddress, note that it will be - * ptr::null_mut() if transaction is null - * - * # Safety - * The ```tari_address_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory leak - */ -TariWalletAddress *completed_transaction_get_source_tari_address(TariCompletedTransaction *transaction, - int *error_out); - -/** - * Gets the status of a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_int` - Returns the status which corresponds to: - * | Value | Interpretation | - * |---|---| - * | -1 | TxNullError | - * | 0 | Completed | - * | 1 | Broadcast | - * | 2 | MinedUnconfirmed | - * | 3 | Imported | - * | 4 | Pending | - * | 5 | Coinbase | - * | 6 | MinedConfirmed | - * - * # Safety - * None - */ -int completed_transaction_get_status(TariCompletedTransaction *transaction, - int *error_out); - -/** - * Gets the amount of a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the amount, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long completed_transaction_get_amount(TariCompletedTransaction *transaction, - int *error_out); - -/** - * Gets the fee of a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the fee, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long completed_transaction_get_fee(TariCompletedTransaction *transaction, - int *error_out); - -/** - * Gets the timestamp of a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the timestamp, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long completed_transaction_get_timestamp(TariCompletedTransaction *transaction, - int *error_out); - -/** - * Gets the message of a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*const c_char` - Returns the pointer to the char array, note that it will return a pointer - * to an empty char array if transaction is null - * - * # Safety - * The ```string_destroy``` method must be called when finished with string coming from rust to prevent a memory leak - */ -const char *completed_transaction_get_message(TariCompletedTransaction *transaction, - int *error_out); - -/** - * This function checks to determine if a TariCompletedTransaction was originally a TariPendingOutboundTransaction - * - * ## Arguments - * `tx` - The TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Returns if the transaction was originally sent from the wallet - * - * # Safety - * None - */ -bool completed_transaction_is_outbound(TariCompletedTransaction *tx, - int *error_out); - -/** - * Gets the number of confirmations of a TariCompletedTransaction - * - * ## Arguments - * `tx` - The TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the number of confirmations of a Completed Transaction - * - * # Safety - * None - */ -unsigned long long completed_transaction_get_confirmations(TariCompletedTransaction *tx, - int *error_out); - -/** - * Gets the reason a TariCompletedTransaction is cancelled, if it is indeed cancelled - * - * ## Arguments - * `tx` - The TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_int` - Returns the reason for cancellation which corresponds to: - * | Value | Interpretation | - * |---|---| - * | -1 | Not Cancelled | - * | 0 | Unknown | - * | 1 | UserCancelled | - * | 2 | Timeout | - * | 3 | DoubleSpend | - * | 4 | Orphan | - * | 5 | TimeLocked | - * | 6 | InvalidTransaction | - * | 7 | AbandonedCoinbase | - * # Safety - * None - */ -int completed_transaction_get_cancellation_reason(TariCompletedTransaction *tx, - int *error_out); - -/** - * returns the TariCompletedTransaction as a json string - * - * ## Arguments - * `tx` - The pointer to a TariCompletedTransaction - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array. Note that it returns an empty char array if - * TariCompletedTransaction is null or the position is invalid - * - * # Safety - * The ```completed_transaction_destroy``` function must be called when finished with a TariCompletedTransaction to - * prevent a memory leak - */ -char *tari_completed_transaction_to_json(TariCompletedTransaction *tx, - int *error_out); - -/** - * Creates a TariUnblindedOutput from a char array - * - * ## Arguments - * `tx_json` - The pointer to a char array which is json of the TariCompletedTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariCompletedTransaction` - Returns a pointer to a TariCompletedTransaction. Note that it returns - * ptr::null_mut() if key is null or if there was an error creating the TariCompletedTransaction from key - * - * # Safety - * The ```completed_transaction_destroy``` function must be called when finished with a TariCompletedTransaction to - */ -TariCompletedTransaction *create_tari_completed_transaction_from_json(const char *tx_json, - int *error_out); - -/** - * Frees memory for a TariCompletedTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariCompletedTransaction - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void completed_transaction_destroy(TariCompletedTransaction *transaction); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- OutboundTransaction ------------------------------------- /// - * Gets the TransactionId of a TariPendingOutboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingOutboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the TransactionID, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long pending_outbound_transaction_get_transaction_id(TariPendingOutboundTransaction *transaction, - int *error_out); - -/** - * Gets the destination TariWalletAddress of a TariPendingOutboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingOutboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - Returns the destination TariWalletAddress, note that it will be - * ptr::null_mut() if transaction is null - * - * # Safety - * The ```tari_address_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory leak - */ -TariWalletAddress *pending_outbound_transaction_get_destination_tari_address(TariPendingOutboundTransaction *transaction, - int *error_out); - -/** - * Gets the amount of a TariPendingOutboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingOutboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the amount, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long pending_outbound_transaction_get_amount(TariPendingOutboundTransaction *transaction, - int *error_out); - -/** - * Gets the fee of a TariPendingOutboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingOutboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the fee, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long pending_outbound_transaction_get_fee(TariPendingOutboundTransaction *transaction, - int *error_out); - -/** - * Gets the timestamp of a TariPendingOutboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingOutboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the timestamp, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long pending_outbound_transaction_get_timestamp(TariPendingOutboundTransaction *transaction, - int *error_out); - -/** - * Gets the message of a TariPendingOutboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingOutboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*const c_char` - Returns the pointer to the char array, note that it will return a pointer - * to an empty char array if transaction is null - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string coming from rust to prevent a memory - * leak - */ -const char *pending_outbound_transaction_get_message(TariPendingOutboundTransaction *transaction, - int *error_out); - -/** - * Gets the status of a TariPendingOutboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingOutboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_int` - Returns the status which corresponds to: - * | Value | Interpretation | - * |---|---| - * | -1 | TxNullError | - * | 0 | Completed | - * | 1 | Broadcast | - * | 2 | Mined | - * | 3 | Imported | - * | 4 | Pending | - * - * # Safety - * None - */ -int pending_outbound_transaction_get_status(TariPendingOutboundTransaction *transaction, - int *error_out); - -/** - * Frees memory for a TariPendingOutboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingOutboundTransaction - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void pending_outbound_transaction_destroy(TariPendingOutboundTransaction *transaction); - -/** - * -------------------------------------------------------------------------------------------- /// - * - * ----------------------------------- InboundTransaction ------------------------------------- /// - * Gets the TransactionId of a TariPendingInboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingInboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the TransactonId, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long pending_inbound_transaction_get_transaction_id(TariPendingInboundTransaction *transaction, - int *error_out); - -/** - * Gets the source TariWalletAddress of a TariPendingInboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingInboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - Returns a pointer to the source TariWalletAddress, note that it will be - * ptr::null_mut() if transaction is null - * - * # Safety - * The ```tari_address_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory - * leak - */ -TariWalletAddress *pending_inbound_transaction_get_source_tari_address(TariPendingInboundTransaction *transaction, - int *error_out); - -/** - * Gets the amount of a TariPendingInboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingInboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the amount, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long pending_inbound_transaction_get_amount(TariPendingInboundTransaction *transaction, - int *error_out); - -/** - * Gets the timestamp of a TariPendingInboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingInboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the timestamp, note that it will be zero if transaction is null - * - * # Safety - * None - */ -unsigned long long pending_inbound_transaction_get_timestamp(TariPendingInboundTransaction *transaction, - int *error_out); - -/** - * Gets the message of a TariPendingInboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingInboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*const c_char` - Returns the pointer to the char array, note that it will return a pointer - * to an empty char array if transaction is null - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string coming from rust to prevent a memory - * leak - */ -const char *pending_inbound_transaction_get_message(TariPendingInboundTransaction *transaction, - int *error_out); - -/** - * Gets the status of a TariPendingInboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingInboundTransaction - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_int` - Returns the status which corresponds to: - * | Value | Interpretation | - * |---|---| - * | -1 | TxNullError | - * | 0 | Completed | - * | 1 | Broadcast | - * | 2 | Mined | - * | 3 | Imported | - * | 4 | Pending | - * - * # Safety - * None - */ -int pending_inbound_transaction_get_status(TariPendingInboundTransaction *transaction, - int *error_out); - -/** - * Frees memory for a TariPendingInboundTransaction - * - * ## Arguments - * `transaction` - The pointer to a TariPendingInboundTransaction - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void pending_inbound_transaction_destroy(TariPendingInboundTransaction *transaction); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- Transport Send Status -----------------------------------/// - * Decode the transaction send status of a TariTransactionSendStatus - * - * ## Arguments - * `status` - The pointer to a TariTransactionSendStatus - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_uint` - Returns - * !direct_send & !saf_send & queued = 0 - * direct_send & saf_send & !queued = 1 - * direct_send & !saf_send & !queued = 2 - * !direct_send & saf_send & !queued = 3 - * any other combination (is not valid) = 4 - * - * # Safety - * None - */ -unsigned int transaction_send_status_decode(const TariTransactionSendStatus *status, - int *error_out); - -/** - * Frees memory for a TariTransactionSendStatus - * - * ## Arguments - * `status` - The pointer to a TariPendingInboundTransaction - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void transaction_send_status_destroy(TariTransactionSendStatus *status); - -/** - * -------------------------------------------------------------------------------------------- /// - * ----------------------------------- Transport Types -----------------------------------------/// - * Creates a memory transport type - * - * ## Arguments - * `()` - Does not take any arguments - * - * ## Returns - * `*mut TariTransportConfig` - Returns a pointer to a memory TariTransportConfig - * - * # Safety - * The ```transport_type_destroy``` method must be called when finished with a TariTransportConfig to prevent a memory - * leak - */ -TariTransportConfig *transport_memory_create(void); - -/** - * Creates a tcp transport type - * - * ## Arguments - * `listener_address` - The pointer to a char array - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariTransportConfig` - Returns a pointer to a tcp TariTransportConfig, null on error. - * - * # Safety - * The ```transport_type_destroy``` method must be called when finished with a TariTransportConfig to prevent a memory - * leak - */ -TariTransportConfig *transport_tcp_create(const char *listener_address, - int *error_out); - -/** - * Creates a tor transport type - * - * ## Arguments - * `control_server_address` - The pointer to a char array - * `tor_cookie` - The pointer to a ByteVector containing the contents of the tor cookie file, can be null - * `tor_port` - The tor port - * `tor_proxy_bypass_for_outbound` - Whether tor will use a direct tcp connection for a given bypass address instead of - * the tor proxy if tcp is available, if not it has no effect - * `socks_password` - The pointer to a char array containing the socks password, can be null - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariTransportConfig` - Returns a pointer to a tor TariTransportConfig, null on error. - * - * # Safety - * The ```transport_config_destroy``` method must be called when finished with a TariTransportConfig to prevent a - * memory leak - */ -TariTransportConfig *transport_tor_create(const char *control_server_address, - const struct ByteVector *tor_cookie, - unsigned short tor_port, - bool tor_proxy_bypass_for_outbound, - const char *socks_username, - const char *socks_password, - int *error_out); - -/** - * Gets the address for a memory transport type - * - * ## Arguments - * `transport` - Pointer to a TariTransportConfig - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut c_char` - Returns the address as a pointer to a char array, array will be empty on error - * - * # Safety - * Can only be used with a memory transport type, will crash otherwise - */ -char *transport_memory_get_address(const TariTransportConfig *transport, - int *error_out); - -/** - * Frees memory for a TariTransportConfig - * - * ## Arguments - * `transport` - The pointer to a TariTransportConfig - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - */ -void transport_type_destroy(TariTransportConfig *transport); - -/** - * Frees memory for a TariTransportConfig - * - * ## Arguments - * `transport` - The pointer to a TariTransportConfig - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - */ -void transport_config_destroy(TariTransportConfig *transport); - -/** - * ---------------------------------------------------------------------------------------------/// - * ----------------------------------- CommsConfig ---------------------------------------------/// - * Creates a TariCommsConfig. The result from this function is required when initializing a TariWallet. - * - * ## Arguments - * `public_address` - The public address char array pointer. This is the address that the wallet advertises publicly to - * peers - * `transport` - TariTransportConfig that specifies the type of comms transport to be used. - * connections are moved to after initial connection. Default if null is 0.0.0.0:7898 which will accept connections - * from all IP address on port 7898 - * `database_name` - The database name char array pointer. This is the unique name of this - * wallet's database - * `database_path` - The database path char array pointer which. This is the folder path where the - * database files will be created and the application has write access to - * `discovery_timeout_in_secs`: specify how long the Discovery Timeout for the wallet is. - * `network`: name of network to connect to. Valid values are: esmeralda, dibbler, igor, localnet, mainnet - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariCommsConfig` - Returns a pointer to a TariCommsConfig, if any of the parameters are - * null or a problem is encountered when constructing the NetAddress a ptr::null_mut() is returned - * - * # Safety - * The ```comms_config_destroy``` method must be called when finished with a TariCommsConfig to prevent a memory leak - */ -TariCommsConfig *comms_config_create(const char *public_address, - const TariTransportConfig *transport, - const char *database_name, - const char *datastore_path, - unsigned long long discovery_timeout_in_secs, - unsigned long long saf_message_duration_in_secs, - int *error_out); - -/** - * Frees memory for a TariCommsConfig - * - * ## Arguments - * `wc` - The TariCommsConfig pointer - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void comms_config_destroy(TariCommsConfig *wc); - -/** - * This function lists the public keys of all connected peers - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `TariPublicKeys` - Returns a list of connected public keys. Note the result will be null if there was an error - * - * # Safety - * The caller is responsible for null checking and deallocating the returned object using public_keys_destroy. - */ -struct TariPublicKeys *comms_list_connected_public_keys(struct TariWallet *wallet, - int *error_out); - -/** - * Creates a TariWallet - * - * ## Arguments - * `config` - The TariCommsConfig pointer - * `log_path` - An optional file path to the file where the logs will be written. If no log is required pass *null* - * pointer. - * `num_rolling_log_files` - Specifies how many rolling log files to produce, if no rolling files are wanted then set - * this to 0 - * `size_per_log_file_bytes` - Specifies the size, in bytes, at which the logs files will roll over, if no - * rolling files are wanted then set this to 0 - * `passphrase` - An optional string that represents the passphrase used to - * encrypt/decrypt the databases for this wallet. If it is left Null no encryption is used. If the databases have been - * encrypted then the correct passphrase is required or this function will fail. - * `seed_words` - An optional instance of TariSeedWords, used to create a wallet for recovery purposes. - * If this is null, then a new master key is created for the wallet. - * `callback_received_transaction` - The callback function pointer matching the function signature. This will be - * called when an inbound transaction is received. - * `callback_received_transaction_reply` - The callback function - * pointer matching the function signature. This will be called when a reply is received for a pending outbound - * transaction - * `callback_received_finalized_transaction` - The callback function pointer matching the function - * signature. This will be called when a Finalized version on an Inbound transaction is received - * `callback_transaction_broadcast` - The callback function pointer matching the function signature. This will be - * called when a Finalized transaction is detected a Broadcast to a base node mempool. - * `callback_transaction_mined` - The callback function pointer matching the function signature. This will be called - * when a Broadcast transaction is detected as mined AND confirmed. - * `callback_transaction_mined_unconfirmed` - The callback function pointer matching the function signature. This will - * be called when a Broadcast transaction is detected as mined but not yet confirmed. - * `callback_faux_transaction_confirmed` - The callback function pointer matching the function signature. This will be - * called when a one-sided transaction is detected as mined AND confirmed. - * `callback_faux_transaction_unconfirmed` - The callback function pointer matching the function signature. This - * will be called when a one-sided transaction is detected as mined but not yet confirmed. - * `callback_transaction_send_result` - The callback function pointer matching the function signature. This is called - * when a transaction send is completed. The first parameter is the transaction id and the second contains the - * transaction send status, weather it was send direct and/or send via saf on the one hand or queued for further retry - * sending on the other hand. - * !direct_send & !saf_send & queued = 0 - * direct_send & saf_send & !queued = 1 - * direct_send & !saf_send & !queued = 2 - * !direct_send & saf_send & !queued = 3 - * any other combination (is not valid) = 4 - * `callback_transaction_cancellation` - The callback function pointer matching - * the function signature. This is called when a transaction is cancelled. The first parameter is a pointer to the - * cancelled transaction, the second is a reason as to why said transaction failed that is mapped to the - * `TxCancellationReason` enum: pub enum TxCancellationReason { - * Unknown, // 0 - * UserCancelled, // 1 - * Timeout, // 2 - * DoubleSpend, // 3 - * Orphan, // 4 - * TimeLocked, // 5 - * InvalidTransaction, // 6 - * } - * `callback_txo_validation_complete` - The callback function pointer matching the function signature. This is called - * when a TXO validation process is completed. The request_key is used to identify which request this - * callback references and the second parameter the second contains, weather it was successful, already busy, failed - * due to an internal failure or failed due to a communication failure. - * TxoValidationSuccess, // 0 - * TxoValidationAlreadyBusy // 1 - * TxoValidationInternalFailure // 2 - * TxoValidationCommunicationFailure // 3 - * `callback_contacts_liveness_data_updated` - The callback function pointer matching the function signature. This is - * called when a contact's liveness status changed. The data represents the contact's updated status information. - * `callback_balance_updated` - The callback function pointer matching the function signature. This is called whenever - * the balance changes. - * `callback_transaction_validation_complete` - The callback function pointer matching the function signature. This is - * called when a Transaction validation process is completed. The request_key is used to identify which request this - * callback references and the second parameter is a u64 that returns if the validation was successful or not. - * ValidationSuccess, // 0 - * ValidationAlreadyBusy // 1 - * ValidationInternalFailure // 2 - * ValidationCommunicationFailure // 3 - * `callback_saf_message_received` - The callback function pointer that will be called when the Dht has determined that - * is has connected to enough of its neighbours to be confident that it has received any SAF messages that were waiting - * for it. - * `callback_connectivity_status` - This callback is called when the status of connection to the set base node - * changes. it will return an enum encoded as an integer as follows: - * pub enum OnlineStatus { - * Connecting, // 0 - * Online, // 1 - * Offline, // 2 - * } - * `recovery_in_progress` - Pointer to an bool which will be modified to indicate if there is an outstanding recovery - * that should be completed or not to an error code should one occur, may not be null. Functions as an out parameter. - * `error_out` - Pointer to an int which will be modified - * to an error code should one occur, may not be null. Functions as an out parameter. - * ## Returns - * `*mut TariWallet` - Returns a pointer to a TariWallet, note that it returns ptr::null_mut() - * if config is null, a wallet error was encountered or if the runtime could not be created - * - * # Safety - * The ```wallet_destroy``` method must be called when finished with a TariWallet to prevent a memory leak - */ -struct TariWallet *wallet_create(TariCommsConfig *config, - const char *log_path, - unsigned int num_rolling_log_files, - unsigned int size_per_log_file_bytes, - const char *passphrase, - const struct TariSeedWords *seed_words, - const char *network_str, - void (*callback_received_transaction)(TariPendingInboundTransaction*), - void (*callback_received_transaction_reply)(TariCompletedTransaction*), - void (*callback_received_finalized_transaction)(TariCompletedTransaction*), - void (*callback_transaction_broadcast)(TariCompletedTransaction*), - void (*callback_transaction_mined)(TariCompletedTransaction*), - void (*callback_transaction_mined_unconfirmed)(TariCompletedTransaction*, uint64_t), - void (*callback_faux_transaction_confirmed)(TariCompletedTransaction*), - void (*callback_faux_transaction_unconfirmed)(TariCompletedTransaction*, uint64_t), - void (*callback_transaction_send_result)(unsigned long long, TariTransactionSendStatus*), - void (*callback_transaction_cancellation)(TariCompletedTransaction*, uint64_t), - void (*callback_txo_validation_complete)(uint64_t, uint64_t), - void (*callback_contacts_liveness_data_updated)(TariContactsLivenessData*), - void (*callback_balance_updated)(TariBalance*), - void (*callback_transaction_validation_complete)(uint64_t, uint64_t), - void (*callback_saf_messages_received)(void), - void (*callback_connectivity_status)(uint64_t), - bool *recovery_in_progress, - int *error_out); - -/** - * Retrieves the balance from a wallet - * - * ## Arguments - * `wallet` - The TariWallet pointer. - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * ## Returns - * `*mut Balance` - Returns the pointer to the TariBalance or null if error occurs - * - * # Safety - * The ```balance_destroy``` method must be called when finished with a TariBalance to prevent a memory leak - */ -TariBalance *wallet_get_balance(struct TariWallet *wallet, - int *error_out); - -/** - * This function returns a list of unspent UTXO values and commitments. - * - * ## Arguments - * * `wallet` - The TariWallet pointer, - * * `page` - Page offset, - * * `page_size` - A number of items per page, - * * `sorting` - An enum representing desired sorting, - * * `dust_threshold` - A value filtering threshold. Outputs whose values are <= `dust_threshold` are not listed in the - * result. - * * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. - * Functions as an out parameter. - * - * ## Returns - * `*mut TariVector` - Returns a struct with an array pointer, length and capacity (needed for proper destruction - * after use). - * - * # Safety - * `destroy_tari_vector()` must be called after use. - * Items that fail to produce `.as_transaction_output()` are omitted from the list and a `warn!()` message is logged to - * LOG_TARGET. - */ -struct TariVector *wallet_get_utxos(struct TariWallet *wallet, - uintptr_t page, - uintptr_t page_size, - enum TariUtxoSort sorting, - struct TariVector *states, - uint64_t dust_threshold, - int32_t *error_ptr); - -/** - * This function returns a list of all UTXO values, commitment's hex values and states. - * - * ## Arguments - * * `wallet` - The TariWallet pointer, - * * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. - * Functions as an out parameter. - * - * ## Returns - * `*mut TariVector` - Returns a struct with an array pointer, length and capacity (needed for proper destruction - * after use). - * - * ## States - * 0 - Unspent - * 1 - Spent - * 2 - EncumberedToBeReceived - * 3 - EncumberedToBeSpent - * 4 - Invalid - * 5 - CancelledInbound - * 6 - UnspentMinedUnconfirmed - * 7 - ShortTermEncumberedToBeReceived - * 8 - ShortTermEncumberedToBeSpent - * 9 - SpentMinedUnconfirmed - * 10 - AbandonedCoinbase - * 11 - NotStored - * - * # Safety - * `destroy_tari_vector()` must be called after use. - * Items that fail to produce `.as_transaction_output()` are omitted from the list and a `warn!()` message is logged to - * LOG_TARGET. - */ -struct TariVector *wallet_get_all_utxos(struct TariWallet *wallet, - int32_t *error_ptr); - -/** - * This function will tell the wallet to do a coin split. - * - * ## Arguments - * * `wallet` - The TariWallet pointer - * * `commitments` - A `TariVector` of "strings", tagged as `TariTypeTag::String`, containing commitment's hex values - * (see `Commitment::to_hex()`) - * * `number_of_splits` - The number of times to split the amount - * * `fee_per_gram` - The transaction fee - * * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. - * Functions as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns the transaction id. - * - * # Safety - * `TariVector` must be freed after use with `destroy_tari_vector()` - */ -uint64_t wallet_coin_split(struct TariWallet *wallet, - struct TariVector *commitments, - uintptr_t number_of_splits, - uint64_t fee_per_gram, - int32_t *error_ptr); - -/** - * This function will tell the wallet to do a coin join, resulting in a new coin worth a sum of the joined coins minus - * the fee. - * - * ## Arguments - * * `wallet` - The TariWallet pointer - * * `commitments` - A `TariVector` of "strings", tagged as `TariTypeTag::String`, containing commitment's hex values - * (see `Commitment::to_hex()`) - * * `fee_per_gram` - The transaction fee - * * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. - * Functions as an out parameter. - * - * ## Returns - * `TariVector` - Returns the transaction id. - * - * # Safety - * `TariVector` must be freed after use with `destroy_tari_vector()` - */ -uint64_t wallet_coin_join(struct TariWallet *wallet, - struct TariVector *commitments, - uint64_t fee_per_gram, - int32_t *error_ptr); - -/** - * This function will tell what the outcome of a coin join would be. - * - * ## Arguments - * * `wallet` - The TariWallet pointer - * * `commitments` - A `TariVector` of "strings", tagged as `TariTypeTag::String`, containing commitment's hex values - * (see `Commitment::to_hex()`) - * * `fee_per_gram` - The transaction fee - * * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. - * Functions as an out parameter. - * - * ## Returns - * `*mut TariCoinPreview` - A struct with expected output values and the fee. - * - * # Safety - * `TariVector` must be freed after use with `destroy_tari_vector()` - */ -struct TariCoinPreview *wallet_preview_coin_join(struct TariWallet *wallet, - struct TariVector *commitments, - uint64_t fee_per_gram, - int32_t *error_ptr); - -/** - * This function will tell what the outcome of a coin split would be. - * - * ## Arguments - * * `wallet` - The TariWallet pointer - * * `commitments` - A `TariVector` of "strings", tagged as `TariTypeTag::String`, containing commitment's hex values - * (see `Commitment::to_hex()`) - * * `number_of_splits` - The number of times to split the amount - * * `fee_per_gram` - The transaction fee - * * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. - * Functions as an out parameter. - * - * ## Returns - * `*mut TariCoinPreview` - A struct with expected output values and the fee. - * - * # Safety - * `TariVector` must be freed after use with `destroy_tari_vector()` - */ -struct TariCoinPreview *wallet_preview_coin_split(struct TariWallet *wallet, - struct TariVector *commitments, - uintptr_t number_of_splits, - uint64_t fee_per_gram, - int32_t *error_ptr); - -/** - * Signs a message using the public key of the TariWallet - * - * ## Arguments - * `wallet` - The TariWallet pointer. - * `msg` - The message pointer. - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * ## Returns - * `*mut c_char` - Returns the pointer to the hexadecimal representation of the signature and - * public nonce, seperated by a pipe character. Empty if an error occured. - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string coming from rust to prevent a memory leak - */ -char *wallet_sign_message(struct TariWallet *wallet, - const char *msg, - int *error_out); - -/** - * Verifies the signature of the message signed by a TariWallet - * - * ## Arguments - * `wallet` - The TariWallet pointer. - * `public_key` - The pointer to the TariPublicKey of the wallet which originally signed the message - * `hex_sig_nonce` - The pointer to the sting containing the hexadecimal representation of the - * signature and public nonce seperated by a pipe character. - * `msg` - The pointer to the msg the signature will be checked against. - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * ## Returns - * `bool` - Returns if the signature is valid or not, will be false if an error occurs. - * - * # Safety - * None - */ -bool wallet_verify_message_signature(struct TariWallet *wallet, - TariPublicKey *public_key, - const char *hex_sig_nonce, - const char *msg, - int *error_out); - -/** - * Adds a base node peer to the TariWallet - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `public_key` - The TariPublicKey pointer - * `address` - The pointer to a char array - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Returns if successful or not - * - * # Safety - * None - */ -bool wallet_add_base_node_peer(struct TariWallet *wallet, - TariPublicKey *public_key, - const char *address, - int *error_out); - -/** - * Upserts a TariContact to the TariWallet. If the contact does not exist it will be Inserted. If it does exist the - * Alias will be updated. - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `contact` - The TariContact pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Returns if successful or not - * - * # Safety - * None - */ -bool wallet_upsert_contact(struct TariWallet *wallet, - TariContact *contact, - int *error_out); - -/** - * Removes a TariContact from the TariWallet - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `tx` - The TariPendingInboundTransaction pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Returns if successful or not - * - * # Safety - * None - */ -bool wallet_remove_contact(struct TariWallet *wallet, - TariContact *contact, - int *error_out); - -/** - * Gets the available balance from a TariBalance. This is the balance the user can spend. - * - * ## Arguments - * `balance` - The TariBalance pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - The available balance, 0 if wallet is null - * - * # Safety - * None - */ -unsigned long long balance_get_available(TariBalance *balance, - int *error_out); - -/** - * Gets the time locked balance from a TariBalance. This is the balance the user can spend. - * - * ## Arguments - * `balance` - The TariBalance pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - The time locked balance, 0 if wallet is null - * - * # Safety - * None - */ -unsigned long long balance_get_time_locked(TariBalance *balance, - int *error_out); - -/** - * Gets the pending incoming balance from a TariBalance. This is the balance the user can spend. - * - * ## Arguments - * `balance` - The TariBalance pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - The pending incoming, 0 if wallet is null - * - * # Safety - * None - */ -unsigned long long balance_get_pending_incoming(TariBalance *balance, - int *error_out); - -/** - * Gets the pending outgoing balance from a TariBalance. This is the balance the user can spend. - * - * ## Arguments - * `balance` - The TariBalance pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - The pending outgoing balance, 0 if wallet is null - * - * # Safety - * None - */ -unsigned long long balance_get_pending_outgoing(TariBalance *balance, - int *error_out); - -/** - * Frees memory for a TariBalance - * - * ## Arguments - * `balance` - The pointer to a TariBalance - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void balance_destroy(TariBalance *balance); - -/** - * Sends a TariPendingOutboundTransaction - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `destination` - The TariWalletAddress pointer of the peer - * `amount` - The amount - * `commitments` - A `TariVector` of "strings", tagged as `TariTypeTag::String`, containing commitment's hex values - * (see `Commitment::to_hex()`) - * `fee_per_gram` - The transaction fee - * `message` - The pointer to a char array - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `unsigned long long` - Returns 0 if unsuccessful or the TxId of the sent transaction if successful - * - * # Safety - * None - */ -unsigned long long wallet_send_transaction(struct TariWallet *wallet, - TariWalletAddress *destination, - unsigned long long amount, - struct TariVector *commitments, - unsigned long long fee_per_gram, - const char *message, - bool one_sided, - int *error_out); - -/** - * Gets a fee estimate for an amount - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `amount` - The amount - * `commitments` - A `TariVector` of "strings", tagged as `TariTypeTag::String`, containing commitment's hex values - * (see `Commitment::to_hex()`) - * `fee_per_gram` - The fee per gram - * `num_kernels` - The number of transaction kernels - * `num_outputs` - The number of outputs - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `unsigned long long` - Returns 0 if unsuccessful or the fee estimate in MicroTari if successful - * - * # Safety - * None - */ -unsigned long long wallet_get_fee_estimate(struct TariWallet *wallet, - unsigned long long amount, - struct TariVector *commitments, - unsigned long long fee_per_gram, - unsigned long long num_kernels, - unsigned long long num_outputs, - int *error_out); - -/** - * Gets the number of mining confirmations required - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `unsigned long long` - Returns the number of confirmations required - * - * # Safety - * None - */ -unsigned long long wallet_get_num_confirmations_required(struct TariWallet *wallet, - int *error_out); - -/** - * Sets the number of mining confirmations required - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `num` - The number of confirmations to require - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void wallet_set_num_confirmations_required(struct TariWallet *wallet, - unsigned long long num, - int *error_out); - -/** - * Get the TariContacts from a TariWallet - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariContacts` - returns the contacts, note that it returns ptr::null_mut() if - * wallet is null - * - * # Safety - * The ```contacts_destroy``` method must be called when finished with a TariContacts to prevent a memory leak - */ -struct TariContacts *wallet_get_contacts(struct TariWallet *wallet, - int *error_out); - -/** - * Get the TariCompletedTransactions from a TariWallet - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariCompletedTransactions` - returns the transactions, note that it returns ptr::null_mut() if - * wallet is null or an error is encountered - * - * # Safety - * The ```completed_transactions_destroy``` method must be called when finished with a TariCompletedTransactions to - * prevent a memory leak - */ -struct TariCompletedTransactions *wallet_get_completed_transactions(struct TariWallet *wallet, - int *error_out); - -/** - * Get the TariPendingInboundTransactions from a TariWallet - * - * Currently a CompletedTransaction with the Status of Completed and Broadcast is considered Pending by the frontend - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPendingInboundTransactions` - returns the transactions, note that it returns ptr::null_mut() if - * wallet is null or and error is encountered - * - * # Safety - * The ```pending_inbound_transactions_destroy``` method must be called when finished with a - * TariPendingInboundTransactions to prevent a memory leak - */ -struct TariPendingInboundTransactions *wallet_get_pending_inbound_transactions(struct TariWallet *wallet, - int *error_out); - -/** - * Get the TariPendingOutboundTransactions from a TariWallet - * - * Currently a CompletedTransaction with the Status of Completed and Broadcast is considered Pending by the frontend - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPendingOutboundTransactions` - returns the transactions, note that it returns ptr::null_mut() if - * wallet is null or and error is encountered - * - * # Safety - * The ```pending_outbound_transactions_destroy``` method must be called when finished with a - * TariPendingOutboundTransactions to prevent a memory leak - */ -struct TariPendingOutboundTransactions *wallet_get_pending_outbound_transactions(struct TariWallet *wallet, - int *error_out); - -/** - * Get the all Cancelled Transactions from a TariWallet. This function will also get cancelled pending inbound and - * outbound transaction and include them in this list by converting them to CompletedTransactions - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariCompletedTransactions` - returns the transactions, note that it returns ptr::null_mut() if - * wallet is null or an error is encountered - * - * # Safety - * The ```completed_transactions_destroy``` method must be called when finished with a TariCompletedTransactions to - * prevent a memory leak - */ -struct TariCompletedTransactions *wallet_get_cancelled_transactions(struct TariWallet *wallet, - int *error_out); - -/** - * Get the TariCompletedTransaction from a TariWallet by its' TransactionId - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `transaction_id` - The TransactionId - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariCompletedTransaction` - returns the transaction, note that it returns ptr::null_mut() if - * wallet is null, an error is encountered or if the transaction is not found - * - * # Safety - * The ```completed_transaction_destroy``` method must be called when finished with a TariCompletedTransaction to - * prevent a memory leak - */ -TariCompletedTransaction *wallet_get_completed_transaction_by_id(struct TariWallet *wallet, - unsigned long long transaction_id, - int *error_out); - -/** - * Get the TariPendingInboundTransaction from a TariWallet by its' TransactionId - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `transaction_id` - The TransactionId - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPendingInboundTransaction` - returns the transaction, note that it returns ptr::null_mut() if - * wallet is null, an error is encountered or if the transaction is not found - * - * # Safety - * The ```pending_inbound_transaction_destroy``` method must be called when finished with a - * TariPendingInboundTransaction to prevent a memory leak - */ -TariPendingInboundTransaction *wallet_get_pending_inbound_transaction_by_id(struct TariWallet *wallet, - unsigned long long transaction_id, - int *error_out); - -/** - * Get the TariPendingOutboundTransaction from a TariWallet by its' TransactionId - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `transaction_id` - The TransactionId - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariPendingOutboundTransaction` - returns the transaction, note that it returns ptr::null_mut() if - * wallet is null, an error is encountered or if the transaction is not found - * - * # Safety - * The ```pending_outbound_transaction_destroy``` method must be called when finished with a - * TariPendingOutboundtransaction to prevent a memory leak - */ -TariPendingOutboundTransaction *wallet_get_pending_outbound_transaction_by_id(struct TariWallet *wallet, - unsigned long long transaction_id, - int *error_out); - -/** - * Get a Cancelled transaction from a TariWallet by its TransactionId. Pending Inbound or Outbound transaction will be - * converted to a CompletedTransaction - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `transaction_id` - The TransactionId - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariCompletedTransaction` - returns the transaction, note that it returns ptr::null_mut() if - * wallet is null, an error is encountered or if the transaction is not found - * - * # Safety - * The ```completed_transaction_destroy``` method must be called when finished with a TariCompletedTransaction to - * prevent a memory leak - */ -TariCompletedTransaction *wallet_get_cancelled_transaction_by_id(struct TariWallet *wallet, - unsigned long long transaction_id, - int *error_out); - -/** - * Get the TariWalletAddress from a TariWallet - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - returns the address, note that ptr::null_mut() is returned - * if wc is null - * - * # Safety - * The ```tari_address_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory leak - */ -TariWalletAddress *wallet_get_tari_address(struct TariWallet *wallet, - int *error_out); - -/** - * Cancel a Pending Transaction - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `transaction_id` - The TransactionId - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - returns whether the transaction could be cancelled - * - * # Safety - * None - */ -bool wallet_cancel_pending_transaction(struct TariWallet *wallet, - unsigned long long transaction_id, - int *error_out); - -/** - * This function will tell the wallet to query the set base node to confirm the status of transaction outputs - * (TXOs). - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns a unique Request Key that is used to identify which callbacks refer to this specific sync - * request. Note the result will be 0 if there was an error - * - * # Safety - * None - */ -unsigned long long wallet_start_txo_validation(struct TariWallet *wallet, - int *error_out); - -/** - * This function will tell the wallet to query the set base node to confirm the status of mined transactions. - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns a unique Request Key that is used to identify which callbacks refer to this specific sync - * request. Note the result will be 0 if there was an error - * - * # Safety - * None - */ -unsigned long long wallet_start_transaction_validation(struct TariWallet *wallet, - int *error_out); - -/** - * This function will tell the wallet retart any broadcast protocols for completed transactions. Ideally this should be - * called after a successfuly Transaction Validation is complete - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Returns a boolean value indicating if the launch was success or not. - * - * # Safety - * None - */ -bool wallet_restart_transaction_broadcast(struct TariWallet *wallet, - int *error_out); - -/** - * Gets the seed words representing the seed private key of the provided `TariWallet`. - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariSeedWords` - A collection of the seed words - * - * # Safety - * The ```tari_seed_words_destroy``` method must be called when finished with a - * TariSeedWords to prevent a memory leak - */ -struct TariSeedWords *wallet_get_seed_words(struct TariWallet *wallet, - int *error_out); - -/** - * Set the power mode of the wallet to Low Power mode which will reduce the amount of network operations the wallet - * performs to conserve power - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * # Safety - * None - */ -void wallet_set_low_power_mode(struct TariWallet *wallet, - int *error_out); - -/** - * Set the power mode of the wallet to Normal Power mode which will then use the standard level of network traffic - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * # Safety - * None - */ -void wallet_set_normal_power_mode(struct TariWallet *wallet, - int *error_out); - -/** - * Set a Key Value in the Wallet storage used for Client Key Value store - * - * ## Arguments - * `wallet` - The TariWallet pointer. - * `key` - The pointer to a Utf8 string representing the Key - * `value` - The pointer to a Utf8 string representing the Value ot be stored - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Return a boolean value indicating the operation's success or failure. The error_ptr will hold the error - * code if there was a failure - * - * # Safety - * None - */ -bool wallet_set_key_value(struct TariWallet *wallet, - const char *key, - const char *value, - int *error_out); - -/** - * get a stored Value that was previously stored in the Wallet storage used for Client Key Value store - * - * ## Arguments - * `wallet` - The TariWallet pointer. - * `key` - The pointer to a Utf8 string representing the Key - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut c_char` - Returns a pointer to a char array of the Value string. Note that it returns an null pointer if an - * error occured. - * - * # Safety - * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak - */ -char *wallet_get_value(struct TariWallet *wallet, - const char *key, - int *error_out); - -/** - * Clears a Value for the provided Key Value in the Wallet storage used for Client Key Value store - * - * ## Arguments - * `wallet` - The TariWallet pointer. - * `key` - The pointer to a Utf8 string representing the Key - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Return a boolean value indicating the operation's success or failure. The error_ptr will hold the error - * code if there was a failure - * - * # Safety - * None - */ -bool wallet_clear_value(struct TariWallet *wallet, - const char *key, - int *error_out); - -/** - * Check if a Wallet has the data of an In Progress Recovery in its database. - * - * ## Arguments - * `wallet` - The TariWallet pointer. - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Return a boolean value indicating whether there is an in progress recovery or not. An error will also - * result in a false result. - * - * # Safety - * None - */ -bool wallet_is_recovery_in_progress(struct TariWallet *wallet, - int *error_out); - -/** - * Starts the Wallet recovery process. - * - * ## Arguments - * `wallet` - The TariWallet pointer. - * `base_node_public_key` - The TariPublicKey pointer of the Base Node the recovery process will use - * `recovery_progress_callback` - The callback function pointer that will be used to asynchronously communicate - * progress to the client. The first argument of the callback is an event enum encoded as a u8 as follows: - * ``` - * enum RecoveryEvent { - * ConnectingToBaseNode, // 0 - * ConnectedToBaseNode, // 1 - * ConnectionToBaseNodeFailed, // 2 - * Progress, // 3 - * Completed, // 4 - * ScanningRoundFailed, // 5 - * RecoveryFailed, // 6 - * } - * ``` - * The second and third arguments are u64 values that will contain different information depending on the event - * that triggered the callback. The meaning of the second and third argument for each event are as follows: - * - ConnectingToBaseNode, 0, 0 - * - ConnectedToBaseNode, 0, 1 - * - ConnectionToBaseNodeFailed, number of retries, retry limit - * - Progress, current block, total number of blocks - * - Completed, total number of UTXO's recovered, MicroTari recovered, - * - ScanningRoundFailed, number of retries, retry limit - * - RecoveryFailed, 0, 0 - * - * If connection to a base node is successful the flow of callbacks should be: - * - The process will start with a callback with `ConnectingToBaseNode` showing a connection is being attempted - * this could be repeated multiple times until a connection is made. - * - The next a callback with `ConnectedToBaseNode` indicate a successful base node connection and process has - * started - * - In Progress callbacks will be of the form (n, m) where n < m - * - If the process completed successfully then the final `Completed` callback will return how many UTXO's were - * scanned and how much MicroTari was recovered - * - If there is an error in the connection process then the `ConnectionToBaseNodeFailed` will be returned - * - If there is a minor error in scanning then `ScanningRoundFailed` will be returned and another connection/sync - * attempt will be made - * - If a unrecoverable error occurs the `RecoveryFailed` event will be returned and the client will need to start - * a new process. - * - * `recovered_output_message` - A string that will be used as the message for any recovered outputs. If Null the - * default message will be used - * - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Return a boolean value indicating whether the process started successfully or not, the process will - * continue to run asynchronously and communicate it progress via the callback. An error will also produce a false - * result. - * - * # Safety - * None - */ -bool wallet_start_recovery(struct TariWallet *wallet, - TariPublicKey *base_node_public_key, - void (*recovery_progress_callback)(uint8_t, uint64_t, uint64_t), - const char *recovered_output_message, - int *error_out); - -/** - * Set the text message that is applied to a detected One-Side payment transaction when it is scanned from the - * blockchain - * - * ## Arguments - * `wallet` - The TariWallet pointer. - * `message` - The pointer to a Utf8 string representing the Message - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `bool` - Return a boolean value indicating the operation's success or failure. The error_ptr will hold the error - * code if there was a failure - * - * # Safety - * None - */ -bool wallet_set_one_sided_payment_message(struct TariWallet *wallet, - const char *message, - int *error_out); - -/** - * Gets the current emoji set - * - * ## Arguments - * `()` - Does not take any arguments - * - * ## Returns - * `*mut EmojiSet` - Pointer to the created EmojiSet. - * - * # Safety - * The ```emoji_set_destroy``` function must be called when finished with a ByteVector to prevent a memory leak - */ -struct EmojiSet *get_emoji_set(void); - -/** - * Gets the length of the current emoji set - * - * ## Arguments - * `*mut EmojiSet` - Pointer to emoji set - * - * ## Returns - * `c_int` - Pointer to the created EmojiSet. - * - * # Safety - * None - */ -unsigned int emoji_set_get_length(const struct EmojiSet *emoji_set, int *error_out); - -/** - * Gets a ByteVector at position in a EmojiSet - * - * ## Arguments - * `emoji_set` - The pointer to a EmojiSet - * `position` - The integer position - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `ByteVector` - Returns a ByteVector. Note that the ByteVector will be null if ptr - * is null or if the position is invalid - * - * # Safety - * The ```byte_vector_destroy``` function must be called when finished with the ByteVector to prevent a memory leak. - */ -struct ByteVector *emoji_set_get_at(const struct EmojiSet *emoji_set, - unsigned int position, - int *error_out); - -/** - * Frees memory for a EmojiSet - * - * ## Arguments - * `emoji_set` - The EmojiSet pointer - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void emoji_set_destroy(struct EmojiSet *emoji_set); - -/** - * Frees memory for a TariWallet - * - * ## Arguments - * `wallet` - The TariWallet pointer - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void wallet_destroy(struct TariWallet *wallet); - -/** - * This function will log the provided string at debug level. To be used to have a client log messages to the LibWallet - * logs. - * - * ## Arguments - * `msg` - A string that will be logged at the debug level. If msg is null nothing will be done. - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * # Safety - * None - */ -void log_debug_message(const char *msg, - int *error_out); - -/** - * ------------------------------------- FeePerGramStats ------------------------------------ /// - * Get the TariFeePerGramStats from a TariWallet. - * - * ## Arguments - * `wallet` - The TariWallet pointer - * `count` - The maximum number of blocks to be checked - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter - * - * ## Returns - * `*mut TariCompletedTransactions` - returns the transactions, note that it returns ptr::null_mut() if - * wallet is null or an error is encountered. - * - * # Safety - * The ```fee_per_gram_stats_destroy``` method must be called when finished with a TariFeePerGramStats to prevent - * a memory leak. - */ -TariFeePerGramStats *wallet_get_fee_per_gram_stats(struct TariWallet *wallet, - unsigned int count, - int *error_out); - -/** - * Get length of stats from the TariFeePerGramStats. - * - * ## Arguments - * `fee_per_gram_stats` - The pointer to a TariFeePerGramStats - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter - * - * ## Returns - * `c_uint` - length of stats in TariFeePerGramStats - * - * # Safety - * None - */ -unsigned int fee_per_gram_stats_get_length(TariFeePerGramStats *fee_per_gram_stats, - int *error_out); - -/** - * Get TariFeePerGramStat at position from the TariFeePerGramStats. - * - * ## Arguments - * `fee_per_gram_stats` - The pointer to a TariFeePerGramStats. - * `position` - The integer position. - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariCompletedTransactions` - returns the TariFeePerGramStat, note that it returns ptr::null_mut() if - * fee_per_gram_stats is null or an error is encountered. - * - * # Safety - * The ```fee_per_gram_stat_destroy``` method must be called when finished with a TariCompletedTransactions to 4prevent - * a memory leak. - */ -TariFeePerGramStat *fee_per_gram_stats_get_at(TariFeePerGramStats *fee_per_gram_stats, - unsigned int position, - int *error_out); - -/** - * Frees memory for a TariFeePerGramStats - * - * ## Arguments - * `fee_per_gram_stats` - The TariFeePerGramStats pointer - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void fee_per_gram_stats_destroy(TariFeePerGramStats *fee_per_gram_stats); - -/** - * ------------------------------------------------------------------------------------------ /// - * ------------------------------------- FeePerGramStat ------------------------------------- /// - * Get the order of TariFeePerGramStat - * - * ## Arguments - * `fee_per_gram_stats` - The TariFeePerGramStat pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns order - * - * # Safety - * None - */ -unsigned long long fee_per_gram_stat_get_order(TariFeePerGramStat *fee_per_gram_stat, - int *error_out); - -/** - * Get the minimum fee per gram of TariFeePerGramStat - * - * ## Arguments - * `fee_per_gram_stats` - The TariFeePerGramStat pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns minimum fee per gram - * - * # Safety - * None - */ -unsigned long long fee_per_gram_stat_get_min_fee_per_gram(TariFeePerGramStat *fee_per_gram_stat, - int *error_out); - -/** - * Get the average fee per gram of TariFeePerGramStat - * - * ## Arguments - * `fee_per_gram_stats` - The TariFeePerGramStat pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns average fee per gram - * - * # Safety - * None - */ -unsigned long long fee_per_gram_stat_get_avg_fee_per_gram(TariFeePerGramStat *fee_per_gram_stat, - int *error_out); - -/** - * Get the maximum fee per gram of TariFeePerGramStat - * - * ## Arguments - * `fee_per_gram_stats` - The TariFeePerGramStat pointer - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `c_ulonglong` - Returns maximum fee per gram - * - * # Safety - * None - */ -unsigned long long fee_per_gram_stat_get_max_fee_per_gram(TariFeePerGramStat *fee_per_gram_stat, - int *error_out); - -/** - * Frees memory for a TariFeePerGramStat - * - * ## Arguments - * `fee_per_gram_stats` - The TariFeePerGramStat pointer - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void fee_per_gram_stat_destroy(TariFeePerGramStat *fee_per_gram_stat); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus diff --git a/MobileWallet/UIElements/Buttons/ActionButton.swift b/MobileWallet/UIElements/Buttons/ActionButton.swift index 6ccc27b9..09274fdc 100644 --- a/MobileWallet/UIElements/Buttons/ActionButton.swift +++ b/MobileWallet/UIElements/Buttons/ActionButton.swift @@ -78,8 +78,9 @@ final class ActionButton: DynamicThemeBaseButton { contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0) clipsToBounds = true - gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5) - gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5) + gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0) + gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) + gradientLayer.locations = [0.0, 1.0] layer.insertSublayer(gradientLayer, at: 0) } diff --git a/MobileWallet/UIElements/Buttons/BaseButton.swift b/MobileWallet/UIElements/Buttons/BaseButton.swift index 07eb8596..fbcde99c 100644 --- a/MobileWallet/UIElements/Buttons/BaseButton.swift +++ b/MobileWallet/UIElements/Buttons/BaseButton.swift @@ -46,6 +46,19 @@ class BaseButton: UIButton { var onTap: (() -> Void)? + var enabledTintColor: UIColor? + var diabledTintColor: UIColor? + + override var isEnabled: Bool { + didSet { + if isEnabled, let enabledTintColor { + tintColor = enabledTintColor + } else if !isEnabled, let diabledTintColor { + tintColor = diabledTintColor + } + } + } + // MARK: - Initialisers override init(frame: CGRect) { diff --git a/MobileWallet/UIElements/Buttons/RoundedButton.swift b/MobileWallet/UIElements/Buttons/RoundedButton.swift new file mode 100644 index 00000000..a628fa43 --- /dev/null +++ b/MobileWallet/UIElements/Buttons/RoundedButton.swift @@ -0,0 +1,47 @@ +// RoundedButton.swift + +/* + Package MobileWallet + Created by Browncoat on 22/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +class RoundedButton: DynamicThemeBaseButton { + + override func layoutSubviews() { + super.layoutSubviews() + layer.cornerRadius = frame.height * 0.5 + } +} diff --git a/MobileWallet/UIElements/EmojiIdView.swift b/MobileWallet/UIElements/EmojiIdView.swift index 32462d4c..c99908fc 100644 --- a/MobileWallet/UIElements/EmojiIdView.swift +++ b/MobileWallet/UIElements/EmojiIdView.swift @@ -133,7 +133,8 @@ final class EmojiIdView: DynamicThemeView { setupView(textCentered: textCentered) } - private func setupView(textCentered: Bool = true, inViewController vc: UIViewController? = nil, initialWidth: CGFloat = CGFloat(185), initialHeight: CGFloat = CGFloat(40), showContainerViewBlur: Bool = true, cornerRadius: CGFloat = 6.0) { + 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(_:)))) @@ -146,7 +147,8 @@ final class EmojiIdView: DynamicThemeView { 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) { + 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(_:)))) diff --git a/MobileWallet/Common/Extensions/UIBarButtonItem.swift b/MobileWallet/UIElements/EmojiTextField.swift similarity index 65% rename from MobileWallet/Common/Extensions/UIBarButtonItem.swift rename to MobileWallet/UIElements/EmojiTextField.swift index 8239d1ce..cc0766b3 100644 --- a/MobileWallet/Common/Extensions/UIBarButtonItem.swift +++ b/MobileWallet/UIElements/EmojiTextField.swift @@ -1,10 +1,10 @@ -// File.swift +// EmojiTextField.swift /* Package MobileWallet - Created by Jason van den Berg on 2019/12/06 + Created by Adrian Truszczyński on 19/03/2023 Using Swift 5.0 - Running on macOS 10.15 + Running on macOS 13.0 Copyright 2019 The Tari Project @@ -40,18 +40,14 @@ import UIKit -extension UIBarButtonItem { - static func customNavBarItem(target: Any?, image: UIImage, action: Selector) -> UIBarButtonItem { - let menuBtn = UIButton(type: .custom) - menuBtn.setImage(image, for: .normal)// setBackgroundImage(image, for: .normal) - menuBtn.addTarget(target, action: action, for: .touchUpInside) +final class EmojiTextField: UITextField { - let menuBarItem = UIBarButtonItem(customView: menuBtn) - let currWidthConstraint = menuBarItem.customView?.widthAnchor.constraint(equalToConstant: 30) - currWidthConstraint?.isActive = true - let currHeightConstraint = menuBarItem.customView?.heightAnchor.constraint(equalToConstant: 30) - currHeightConstraint?.isActive = true + var isEmojiKeyboardVisible: Bool = false - return menuBarItem + override var textInputContextIdentifier: String? { "" } + + override var textInputMode: UITextInputMode? { + guard isEmojiKeyboardVisible else { return nil } + return .activeInputModes.first { $0.primaryLanguage == "emoji" } } } diff --git a/MobileWallet/UIElements/FloatingPanel/HomeViewFloatingPanelDelegates.swift b/MobileWallet/UIElements/FloatingPanel/HomeViewFloatingPanelDelegates.swift index 587549f1..3a1cd2cb 100644 --- a/MobileWallet/UIElements/FloatingPanel/HomeViewFloatingPanelDelegates.swift +++ b/MobileWallet/UIElements/FloatingPanel/HomeViewFloatingPanelDelegates.swift @@ -42,7 +42,7 @@ import Foundation import FloatingPanel class HomeViewFloatingPanelLayout: FloatingPanelLayout { - static let bottomHalfSurfaceViewInsets: UIEdgeInsets = UIEdgeInsets(top: 37, left: 0, bottom: 116 + UIApplication.shared.windows[0].safeAreaInsets.bottom, right: 0) + static let bottomHalfSurfaceViewInsets: UIEdgeInsets = UIEdgeInsets(top: 37, left: 0, bottom: 52.0 + UIApplication.shared.windows[0].safeAreaInsets.bottom, right: 0) let navBarHeight: CGFloat @@ -65,7 +65,7 @@ class HomeViewFloatingPanelLayout: FloatingPanelLayout { func insetFor(position: FloatingPanelPosition) -> CGFloat? { let topInset: CGFloat = navBarHeight // Raising the lowest postion of the panel slightly for phones without the notch - let lowestHeight = UIScreen.main.bounds.height - 106.0 - (UIApplication.shared.keyWindow?.safeAreaInsets.top ?? 0) + let lowestHeight = UIScreen.main.bounds.height - 199.0 - (UIApplication.shared.keyWindow?.safeAreaInsets.top ?? 0) switch position { case .full: return topInset - 37 // A top inset from safe area diff --git a/MobileWallet/UIElements/Keyboard Form/FormOverlay.swift b/MobileWallet/UIElements/Keyboard Form/FormOverlay.swift new file mode 100644 index 00000000..500bd70a --- /dev/null +++ b/MobileWallet/UIElements/Keyboard Form/FormOverlay.swift @@ -0,0 +1,97 @@ +// FormOverlay.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 01/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +final class FormOverlay: UIViewController { + + // MARK: - Properties + + private let mainView: FormOverlayView + var onClose: (() -> Void)? + + // MARK: - Initialisers + + init(formView: FormShowable) { + mainView = FormOverlayView(formView: formView) + super.init(nibName: nil, bundle: nil) + modalTransitionStyle = .crossDissolve + modalPresentationStyle = .overFullScreen + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View Lifecycle + + override func loadView() { + super.loadView() + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + + mainView.onCloseAction = { [weak self] in + self?.view.endEditing(true) + self?.dismiss(animated: true) + self?.onClose?() + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + mainView.becomeFirstResponder() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + mainView.formView.focusedView?.becomeFirstResponder() + } + + // MARK: - Autolayout + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + mainView.inputAccessoryView?.setNeedsLayout() + mainView.inputAccessoryView?.layoutIfNeeded() + } +} diff --git a/MobileWallet/UIElements/Keyboard Form/FormOverlayView.swift b/MobileWallet/UIElements/Keyboard Form/FormOverlayView.swift new file mode 100644 index 00000000..e0ad2c46 --- /dev/null +++ b/MobileWallet/UIElements/Keyboard Form/FormOverlayView.swift @@ -0,0 +1,95 @@ +// FormOverlayView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 01/03/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +protocol FormShowable: UIView { + var focusedView: UIView? { get } + var initalReturkKeyType: UIReturnKeyType { get } + var onCloseAction: (() -> Void)? { get set } +} + +final class FormOverlayView: UIView, UIKeyInput { + + // MARK: - Properties + + var onCloseAction: (() -> Void)? + + private(set) var formView: FormShowable + + override var canBecomeFirstResponder: Bool { true } + override var canResignFirstResponder: Bool { true } + override var inputAccessoryView: UIView? { formView } + + // MARK: - UIKeyInput + + var returnKeyType: UIReturnKeyType = .default + var hasText: Bool { false } + + func insertText(_ text: String) {} + func deleteBackward() {} + + // MARK: - Initialisers + + init(formView: FormShowable) { + self.formView = formView + super.init(frame: .zero) + setupViews(formView: formView) + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupViews(formView: FormShowable) { + backgroundColor = .static.black?.withAlphaComponent(0.7) + formView.translatesAutoresizingMaskIntoConstraints = false + returnKeyType = formView.initalReturkKeyType + } + + private func setupCallbacks() { + formView.onCloseAction = { [weak self] in + self?.onCloseAction?() + } + } +} diff --git a/MobileWallet/UIElements/Labels/StylizedLabel.swift b/MobileWallet/UIElements/Labels/StylizedLabel.swift index bedbd03b..30f03584 100644 --- a/MobileWallet/UIElements/Labels/StylizedLabel.swift +++ b/MobileWallet/UIElements/Labels/StylizedLabel.swift @@ -55,6 +55,10 @@ final class StylizedLabel: UILabel { // MARK: - Properties + override var text: String? { + didSet { textComponents = [] } + } + var normalFont: UIFont? { didSet { updateText() } } @@ -83,6 +87,8 @@ final class StylizedLabel: UILabel { private func updateText() { + guard !textComponents.isEmpty else { return } + attributedText = textComponents.reduce(into: NSMutableAttributedString()) { result, stylizedText in var font: UIFont? @@ -105,10 +111,10 @@ final class StylizedLabel: UILabel { result.append(NSAttributedString(string: stylizedText.text)) if let font { - result.addAttribute(.font, value: font, range: NSRange(location: location, length: stylizedText.text.count)) + result.addAttribute(.font, value: font, range: NSRange(location: location, length: stylizedText.text.utf16.count)) } if let textColor { - result.addAttribute(.foregroundColor, value: textColor, range: NSRange(location: location, length: stylizedText.text.count)) + result.addAttribute(.foregroundColor, value: textColor, range: NSRange(location: location, length: stylizedText.text.utf16.count)) } } } diff --git a/MobileWallet/UIElements/Menu/MenuCell.swift b/MobileWallet/UIElements/Menu/MenuCell.swift new file mode 100644 index 00000000..132165f4 --- /dev/null +++ b/MobileWallet/UIElements/Menu/MenuCell.swift @@ -0,0 +1,136 @@ +// MenuCell.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 27/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import TariCommon + +final class MenuCell: DynamicThemeCell { + + struct ViewModel: Hashable, Identifiable { + let id: UInt + let title: String? + let isArrowVisible: Bool + let isDestructive: Bool + } + + // MARK: - Subviews + + @View private var titleLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.medium.withSize(15.0) + return view + }() + + @View private var accessoryItemView: UIImageView = { + let view = UIImageView() + view.image = Theme.shared.images.forwardArrow + view.contentMode = .scaleAspectFit + return view + }() + + // MARK: - Properties + + var viewModel: ViewModel? { + didSet { update(viewModel: viewModel) } + } + + // MARK: - Initialisers + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupView() + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupView() { + selectionStyle = .none + } + + private func setupConstraints() { + + [titleLabel, accessoryItemView].forEach(contentView.addSubview) + + let constraints = [ + titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25.0), + titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + accessoryItemView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -25.0), + accessoryItemView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + contentView.heightAnchor.constraint(equalToConstant: 63.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + updateTintColor(theme: theme) + backgroundColor = theme.backgrounds.primary + } + + private func updateTintColor(theme: ColorTheme) { + + let isDestructive = viewModel?.isDestructive ?? false + let tintColor = isDestructive ? theme.system.red : theme.text.heading + + titleLabel.textColor = tintColor + accessoryItemView.tintColor = tintColor + } + + private func update(viewModel: ViewModel?) { + + defer { updateTintColor(theme: theme) } + + guard let viewModel else { + titleLabel.text = nil + accessoryItemView.isHidden = true + return + } + + titleLabel.text = viewModel.title + accessoryItemView.isHidden = !viewModel.isArrowVisible + } +} diff --git a/MobileWallet/UIElements/Menu/MenuTableHeaderView.swift b/MobileWallet/UIElements/Menu/MenuTableHeaderView.swift new file mode 100644 index 00000000..942d3cd5 --- /dev/null +++ b/MobileWallet/UIElements/Menu/MenuTableHeaderView.swift @@ -0,0 +1,91 @@ +// MenuTableHeaderView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 27/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class MenuTableHeaderView: DynamicThemeHeaderFooterView { + + // MARK: - Subviews + + @View private(set) var label: UILabel = { + let view = UILabel() + view.font = Theme.shared.fonts.settingsViewHeader + return view + }() + + @View private var backgroundContentView = UIView() + + // MARK: - Initialisers + + override init(reuseIdentifier: String?) { + super.init(reuseIdentifier: reuseIdentifier) + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [backgroundContentView, label].forEach(addSubview) + + let constraints = [ + backgroundContentView.topAnchor.constraint(equalTo: topAnchor), + backgroundContentView.leadingAnchor.constraint(equalTo: leadingAnchor), + backgroundContentView.trailingAnchor.constraint(equalTo: trailingAnchor), + backgroundContentView.bottomAnchor.constraint(equalTo: bottomAnchor), + label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25.0), + label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -15.0), + heightAnchor.constraint(equalToConstant: 70.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + override func update(theme: ColorTheme) { + super.update(theme: theme) + label.textColor = theme.text.heading + backgroundContentView.backgroundColor = theme.backgrounds.secondary + } +} diff --git a/MobileWallet/UIElements/Menu/MenuTableView.swift b/MobileWallet/UIElements/Menu/MenuTableView.swift new file mode 100644 index 00000000..d299f577 --- /dev/null +++ b/MobileWallet/UIElements/Menu/MenuTableView.swift @@ -0,0 +1,137 @@ +// MenuTableView.swift + +/* + Package MobileWallet + Created by Adrian Truszczyński on 27/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +final class MenuTableView: DynamicThemeTableView { + + struct Section { + let title: String? + let items: [MenuCell.ViewModel] + } + + // MARK: - Properties + + var viewModel: [Section] = [] { + didSet { update(viewModel: viewModel) } + } + + var onSelectRow: ((_ id: UInt) -> Void)? + + private var tableDataSource: UITableViewDiffableDataSource? + + // MARK: - Initialisers + + init() { + super.init(frame: .zero, style: .grouped) + registerReusableViews() + setupView() + setupTableView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func registerReusableViews() { + register(type: MenuCell.self) + register(headerFooterType: MenuTableHeaderView.self) + } + + private func setupView() { + showsVerticalScrollIndicator = false + rowHeight = UITableView.automaticDimension + } + + private func setupTableView() { + + tableDataSource = UITableViewDiffableDataSource(tableView: self, cellProvider: { tableView, indexPath, model in + let cell = tableView.dequeueReusableCell(type: MenuCell.self, indexPath: indexPath) + cell.viewModel = model + return cell + }) + + delegate = self + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + backgroundColor = theme.backgrounds.secondary + separatorColor = theme.neutral.secondary + } + + private func update(viewModel: [Section]) { + + var snapshot = NSDiffableDataSourceSnapshot() + + viewModel + .enumerated() + .forEach { + snapshot.appendSections([$0]) + snapshot.appendItems($1.items) + } + + tableDataSource?.apply(snapshot: snapshot) + } +} + +extension MenuTableView: UITableViewDelegate { + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + + guard let title = viewModel[section].title else { return nil } + + let headerView = tableView.dequeueReusableHeaderFooterView(type: MenuTableHeaderView.self) + headerView.label.text = title + return headerView + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let cell = tableView.cellForRow(at: indexPath) as? MenuCell, let cellID = cell.viewModel?.id else { return } + onSelectRow?(cellID) + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + viewModel[section].title == nil ? 0.0 : UITableView.automaticDimension + } +} diff --git a/MobileWallet/UIElements/NavigationBar.swift b/MobileWallet/UIElements/NavigationBar.swift index fcf628b7..8dd036c0 100644 --- a/MobileWallet/UIElements/NavigationBar.swift +++ b/MobileWallet/UIElements/NavigationBar.swift @@ -182,8 +182,8 @@ final class NavigationBar: DynamicThemeView { progressView.tintColor = theme.brand.purple rightButton.tintColor = theme.icons.default - rightButton.setTitleColor(theme.icons.default, for: .normal) - rightButton.setTitleColor(theme.icons.default?.withAlphaComponent(0.5), for: .highlighted) + rightButton.setTitleColor(theme.brand.purple, for: .normal) + rightButton.setTitleColor(theme.brand.purple?.withAlphaComponent(0.5), for: .highlighted) rightButton.setTitleColor(theme.icons.inactive, for: .disabled) } diff --git a/MobileWallet/UIElements/Pager/PageToolbarView.swift b/MobileWallet/UIElements/Pager/PageToolbarView.swift new file mode 100644 index 00000000..5334f854 --- /dev/null +++ b/MobileWallet/UIElements/Pager/PageToolbarView.swift @@ -0,0 +1,140 @@ +// PageToolbarView.swift + +/* + Package MobileWallet + Created by Browncoat on 22/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class PageToolbarView: DynamicThemeView { + + // MARK: - Subviews + + @View private var selectorLineView = UIView() + + @View private var stackView: UIStackView = { + let view = UIStackView() + view.distribution = .fillEqually + view.alignment = .fill + return view + }() + + // MARK: - Properties + + var indexPosition: CGFloat = 0.0 { + didSet { update(indexPosition: indexPosition) } + } + + var onTap: ((_ index: Int) -> Void)? + + private var selectorLineWidthConstraint: NSLayoutConstraint? + private var selectorLineCenterXConstraint: NSLayoutConstraint? + private var selectorLineConstraints: [NSLayoutConstraint] { [selectorLineWidthConstraint, selectorLineCenterXConstraint].compactMap { $0 } } + + // MARK: - Initialisers + + override init() { + super.init() + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [stackView, selectorLineView].forEach(addSubview) + + let constraints = [ + stackView.topAnchor.constraint(equalTo: topAnchor), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor), + selectorLineView.bottomAnchor.constraint(equalTo: bottomAnchor), + selectorLineView.heightAnchor.constraint(equalToConstant: 3.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + func update(tabs: [String]) { + tabs + .enumerated() + .map { tab in + let button = BaseButton() + button.setTitle(tab.element, for: .normal) + button.titleLabel?.font = .Avenir.medium.withSize(16.0) + button.onTap = { [weak self] in self?.onTap?(tab.offset) } + return button + } + .forEach(stackView.addArrangedSubview) + + updateButtons(theme: theme) + + NSLayoutConstraint.deactivate(selectorLineConstraints) + guard let firstButton = stackView.arrangedSubviews.first else { return } + + selectorLineWidthConstraint = selectorLineView.widthAnchor.constraint(equalTo: firstButton.widthAnchor, constant: -24.0) + selectorLineCenterXConstraint = selectorLineView.centerXAnchor.constraint(equalTo: firstButton.centerXAnchor) + + NSLayoutConstraint.activate(selectorLineConstraints) + } + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.primary + selectorLineView.backgroundColor = theme.brand.purple + updateButtons(theme: theme) + } + + private func updateButtons(theme: ColorTheme) { + stackView.arrangedSubviews + .compactMap { $0 as? BaseButton } + .forEach { $0.setTitleColor(theme.text.heading, for: .normal) } + } + + private func update(indexPosition: CGFloat) { + guard let firstButton = stackView.arrangedSubviews.first else { return } + selectorLineCenterXConstraint?.constant = indexPosition * firstButton.bounds.width + } +} diff --git a/MobileWallet/UIElements/Pager/TariPagerView.swift b/MobileWallet/UIElements/Pager/TariPagerView.swift new file mode 100644 index 00000000..9ae6aa1e --- /dev/null +++ b/MobileWallet/UIElements/Pager/TariPagerView.swift @@ -0,0 +1,81 @@ +// TariPagerView.swift + +/* + Package MobileWallet + Created by Browncoat on 22/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class TariPagerView: UIView { + + // MARK: - Subviews + + @View private(set) var toolbar = PageToolbarView() + @View private(set) var contentView = UIView() + + // MARK: - Initialisers + + init() { + super.init(frame: .zero) + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [toolbar, contentView].forEach(addSubview) + + let constraints = [ + toolbar.topAnchor.constraint(equalTo: topAnchor), + toolbar.leadingAnchor.constraint(equalTo: leadingAnchor), + toolbar.trailingAnchor.constraint(equalTo: trailingAnchor), + toolbar.heightAnchor.constraint(equalToConstant: 42.0), + contentView.topAnchor.constraint(equalTo: toolbar.bottomAnchor), + contentView.leadingAnchor.constraint(equalTo: leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } +} diff --git a/MobileWallet/UIElements/Pager/TariPagerViewController.swift b/MobileWallet/UIElements/Pager/TariPagerViewController.swift new file mode 100644 index 00000000..5631af94 --- /dev/null +++ b/MobileWallet/UIElements/Pager/TariPagerViewController.swift @@ -0,0 +1,101 @@ +// TariPagerViewController.swift + +/* + Package MobileWallet + Created by Browncoat on 22/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon +import Combine + +final class TariPagerViewController: UIViewController { + + struct Page { + let title: String + let controller: UIViewController + } + + // MARK: - Subviews + + @View private var mainView = TariPagerView() + private lazy var pageViewController = PageViewController() + + // MARK: - Properties + + var pages: [Page] = [] { + didSet { update(pages: pages) } + } + + private var cancellables = Set() + + // MARK: - View Lifecycle + + override func loadView() { + view = mainView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupSubviews() + setupCallbacks() + } + + private func setupSubviews() { + add(childController: pageViewController, containerView: mainView.contentView) + } + + private func setupCallbacks() { + + mainView.toolbar.onTap = { [weak self] in + self?.pageViewController.move(toIndex: $0) + } + + pageViewController.$pageIndex + .sink { [weak self] in self?.mainView.toolbar.indexPosition = $0 } + .store(in: &cancellables) + } + + // MARK: - Updates + + private func update(pages: [Page]) { + + let tabModels = pages.map { $0.title } + + mainView.toolbar.update(tabs: tabModels) + pageViewController.controllers = pages.map { $0.controller } + } +} diff --git a/MobileWallet/UIElements/RoundedAvatarView.swift b/MobileWallet/UIElements/RoundedAvatarView.swift new file mode 100644 index 00000000..d5ba0718 --- /dev/null +++ b/MobileWallet/UIElements/RoundedAvatarView.swift @@ -0,0 +1,136 @@ +// RoundedAvatarView.swift + +/* + Package MobileWallet + Created by Browncoat on 22/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import TariCommon + +final class RoundedAvatarView: DynamicThemeView { + + enum Avatar { + case text(_: String?) + case image(_: UIImage?) + case empty + } + + // MARK: - Subviews + + @View private var label: UILabel = { + let label = UILabel() + label.textAlignment = .center + return label + }() + + @View private var imageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + return imageView + }() + + // MARK: - Properties + + var avatar: Avatar = .empty { + didSet { update(avatar: avatar) } + } + + // MARK: - Initialisers + + override init() { + super.init() + setupConstraints() + imageView.clipsToBounds = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupConstraints() { + + [label, imageView].forEach(addSubview) + + let constraints = [ + label.topAnchor.constraint(equalTo: topAnchor), + label.leadingAnchor.constraint(equalTo: leadingAnchor), + label.trailingAnchor.constraint(equalTo: trailingAnchor), + label.bottomAnchor.constraint(equalTo: bottomAnchor), + imageView.topAnchor.constraint(equalTo: topAnchor), + imageView.leadingAnchor.constraint(equalTo: leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: trailingAnchor), + imageView.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Updates + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.backgrounds.primary + label.textColor = theme.text.lightText + apply(shadow: theme.shadows.box) + } + + private func update(avatar: Avatar) { + switch avatar { + case let .text(text): + label.text = text + imageView.image = nil + case let .image(image): + label.text = nil + imageView.image = image + case .empty: + label.text = nil + imageView.image = nil + } + } + + // MARK: - Layout + + override func layoutSubviews() { + super.layoutSubviews() + let size = frame.height / 2.0 + layer.cornerRadius = size + imageView.layer.cornerRadius = size + label.font = .Avenir.medium.withSize(size) + } +} diff --git a/MobileWallet/UIElements/TabBar/MenuTabBarController.swift b/MobileWallet/UIElements/TabBar/MenuTabBarController.swift index 73d642be..b9048f07 100644 --- a/MobileWallet/UIElements/TabBar/MenuTabBarController.swift +++ b/MobileWallet/UIElements/TabBar/MenuTabBarController.swift @@ -40,7 +40,7 @@ import UIKit -class MenuTabBarController: UITabBarController { +final class MenuTabBarController: UITabBarController { enum Tab: Int { case home case ttlStore @@ -49,28 +49,29 @@ class MenuTabBarController: UITabBarController { case settings } - var homeViewController = HomeViewController() - var storeViewController = WebBrowserViewController() - var transactionsViewController = TransactionsViewController() - var profileViewController = ProfileViewController() - var settingsViewController = SettingsViewController() - let customTabBar = CustomTabBar() + let homeViewController = HomeViewController() + private let storeViewController = WebBrowserViewController() + private let transactionsViewController = TransactionsViewController() + private let contactBookViewController = ContactBookConstructor.buildScene() + private let settingsViewController = SettingsViewController() + private let customTabBar = CustomTabBar() override func viewDidLoad() { super.viewDidLoad() setValue(customTabBar, forKey: "tabBar") self.delegate = self + tabBar.isTranslucent = false homeViewController.tabBarItem.image = Theme.shared.images.homeItem storeViewController.tabBarItem.image = Theme.shared.images.ttlItem transactionsViewController.tabBarItem.image = Theme.shared.images.sendItem transactionsViewController.tabBarItem.tag = 1 // Using this to determine which icon to move upwards - profileViewController.tabBarItem.image = Theme.shared.images.profileItem + contactBookViewController.tabBarItem.image = .icons.tabBar.contactBook settingsViewController.tabBarItem.image = Theme.shared.images.settingsItem storeViewController.url = URL(string: TariSettings.shared.storeUrl) - viewControllers = [homeViewController, storeViewController, transactionsViewController, profileViewController, settingsViewController] + viewControllers = [homeViewController, storeViewController, transactionsViewController, contactBookViewController, settingsViewController] for tabBarItem in tabBar.items! { // For the send image we need to raise it higher than the others @@ -82,7 +83,7 @@ class MenuTabBarController: UITabBarController { } } - open override var childForStatusBarStyle: UIViewController? { + override var childForStatusBarStyle: UIViewController? { return selectedViewController?.childForStatusBarStyle ?? selectedViewController } diff --git a/MobileWallet/UIElements/TariGradientView.swift b/MobileWallet/UIElements/TariGradientView.swift new file mode 100644 index 00000000..2aa2a223 --- /dev/null +++ b/MobileWallet/UIElements/TariGradientView.swift @@ -0,0 +1,72 @@ +// TariGradientView.swift + +/* + Package MobileWallet + Created by Browncoat on 22/02/2023 + Using Swift 5.0 + Running on macOS 13.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +class TariGradientView: DynamicThemeView { + + private let gradientLayer: CAGradientLayer = { + let layer = CAGradientLayer() + layer.startPoint = CGPoint(x: 0.0, y: 0.0) + layer.endPoint = CGPoint(x: 1.0, y: 1.0) + layer.locations = [0.0, 1.0] + return layer + }() + + override init() { + super.init() + layer.addSublayer(gradientLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSublayers(of layer: CALayer) { + super.layoutSublayers(of: layer) + gradientLayer.frame = layer.bounds + } + + override func update(theme: ColorTheme) { + super.update(theme: theme) + backgroundColor = theme.neutral.primary + gradientLayer.colors = [theme.buttons.primaryStart, theme.buttons.primaryEnd].compactMap { $0?.cgColor } + } +} diff --git a/MobileWallet/en.lproj/Localizable.strings b/MobileWallet/en.lproj/Localizable.strings index 0e456712..23aa5643 100644 --- a/MobileWallet/en.lproj/Localizable.strings +++ b/MobileWallet/en.lproj/Localizable.strings @@ -7,6 +7,10 @@ "common.close" = "Close"; "common.apply" = "Apply"; "common.next" = "Next"; +"common.add" = "Add"; +"common.edit" = "Edit"; +"common.done" = "Done"; +"common.confirm" = "Confirm"; "common.fee" = "Transaction Fee"; "common.wallet_balance" = "Wallet Balance"; @@ -192,7 +196,6 @@ "settings.done" = "Done"; "settings.item.header.more" = "More"; -"settings.item.header.yat" = "Yat Settings"; "settings.item.header.advanced_settings" = "Advanced Settings"; "settings.item.header.security" = "Security"; @@ -730,3 +733,70 @@ /* Cloud Backup */ "backup.cloud.partial.recovery_message" = "Funds recovered from your previous wallet."; + +/* Contact Book */ + +"contact_book.title" = "Contact Book"; +"contact_book.search_bar.placeholder" = "Find Contact"; +"contact_book.pager.tab.contacts" = "Contacts"; +"contact_book.pager.tab.favorites" = "Favorites"; +"contact_book.section.phone_contacts" = "Phone Contacts"; +"contact_book.section.list.placeholder.message.part1" = "Keep track of your contacts by saving them in the contact book. To"; +"contact_book.section.list.placeholder.message.part2.bold" = "add a new contact,"; +"contact_book.section.list.placeholder.message.part3" = "tap the “add contact” icon at the top of the page."; +"contact_book.section.list.placeholder.title.part1" = "Find your"; +"contact_book.section.list.placeholder.title.part2.bold" = "friends"; +"contact_book.section.list.placeholder.without_permission.message.part1" = "You can"; +"contact_book.section.list.placeholder.without_permission.message.part2.bold" = "add a new contact"; +"contact_book.section.list.placeholder.without_permission.message.part3" = "by tapping on the “add contact” icon at the top of the page. Aurora can also automatically detect contacts with Tari addresses on you phone. We’ll need your permission to access your existing contact book to do this."; +"contact_book.section.list.placeholder.button" = "Grant app access to my contacts"; +"contact_book.section.favorites.placeholder.title.part1" = "Add some"; +"contact_book.section.favorites.placeholder.title.part2.bold" = "favorites"; +"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.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.delete" = "Delete Contact"; +"contact_book.details.menu.option.bitcoin" = "Open Bitcoin Wallet"; +"contact_book.details.menu.option.ethereum" = "Open Ethereum Wallet"; +"contact_book.details.menu.option.monero" = "Open Monero Wallet"; +"contact_book.details.menu.section.connected_wallets" = "Associated Wallets"; +"contact_book.details.popup.delete_contact.title" = "Delete Contact"; +"contact_book.details.popup.delete_contact.message" = "You are about to pernamently remove this contact. Are you sure?"; +"contact_book.details.popup.delete_contact.button.ok" = "Delete"; +"contact_book.details.popup.error.invalid_yat_response" = "Aurora was unable to get data from the Yat service. Some contact details may be unavailable"; +"contact_book.details.popup.error.unable_to_open_url" = "Aurora was unable to navigate to %@ wallet. Please check that the wallet app is installed on your device"; +"contact_book.details.edit_form.title.add" = "Add Contact"; +"contact_book.details.edit_form.title.edit" = "Edit Contact"; +"contact_book.details.edit_form.text_field.name" = "Name"; +"contact_book.details.edit_form.text_field.first_name" = "First Name"; +"contact_book.details.edit_form.text_field.last_name" = "Last Name"; +"contact_book.details.edit_form.text_field.yat" = "Yat"; +"contact_book.details.wallet.bitcoin" = "Bitcoin"; +"contact_book.details.wallet.ethereum" = "Ethereum"; +"contact_book.details.wallet.monero" = "Monero"; +"contact_book.link_contacts.title" = "Link Contact"; +"contact_book.link_contacts.lables.info.part1" = "You can quickly associate an Emoji ID or Yat with a contact already present in your phone. Simply click on a name below and it will be linked to"; +"contact_book.link_contacts.text_field.search" = "Find Contact"; +"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.success.title" = "Success!"; +"contact_book.link_contacts.popup.success.message.part1" = "You have successfully linked %@ to"; +"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.success.title" = "Success!"; +"contact_book.unlink_contact.popup.success.message.part1" = "You have successfully unlinked %@ from"; +"contact_book.contact_type.internal" = "Tari Contact"; +"contact_book.contact_type.external" = "External Contact"; +"contact_book.contact_type.linked" = "Linked Contact"; +"contact_book.add_contact.title" = "Add Contact"; +"contact_book.add_contact.validation_error.invalid_emoji_id" = "Invalid emoji ID entered. Please verify the ID and try again."; +"contact_book.add_contact.validation_error.no_name" = "Please enter a name in order to create a contact."; +"contact_book.add_contact.text_field.search.placeholder" = "Enter Emoji ID"; +"contact_book.add_contact.text_field.name.placeholder" = "Name"; diff --git a/UnitTests/VersionValidatorTests.swift b/UnitTests/VersionValidatorTests.swift index 065e561b..e59793bb 100644 --- a/UnitTests/VersionValidatorTests.swift +++ b/UnitTests/VersionValidatorTests.swift @@ -141,4 +141,95 @@ final class VersionValidatorTests: XCTestCase { let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) XCTAssertTrue(result) } + + func testPreComponentFirstVersionIsHigher() { + + let firstVersion = "1.23.4-pre.2" + let secondVersion = "1.23.4-pre.1" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertTrue(result) + } + + func testPreComponentSecondVersionIsHigher() { + + let firstVersion = "1.23.4-pre.1" + let secondVersion = "1.23.4-pre.2" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertFalse(result) + } + + func testRcComponentFirstVersionIsHigher() { + + let firstVersion = "1.23.4-rc.2" + let secondVersion = "1.23.4-rc.1" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertTrue(result) + } + + func testRcComponentSecondVersionIsHigher() { + + let firstVersion = "1.23.4-rc.1" + let secondVersion = "1.23.4-rc.2" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertFalse(result) + } + + + func testRcComponentIsHigherThanPreComponent() { + + let firstVersion = "1.23.4-rc.1" + let secondVersion = "1.23.4-pre.1" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertTrue(result) + } + + func testNumberComponentIsHigherThanPreComponent() { + + let firstVersion = "1.23.4" + let secondVersion = "1.23.4-pre.1" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertTrue(result) + } + + func testNumberComponentIsHigherThanRcComponent() { + + let firstVersion = "1.23.4" + let secondVersion = "1.23.4-rc.1" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertTrue(result) + } + + func testPreComponentIsLowerThanRcComponent() { + + let firstVersion = "1.23.4-pre.1" + let secondVersion = "1.23.4-rc.1" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertFalse(result) + } + + func testPreComponentIsLowerThanNumberComponent() { + + let firstVersion = "1.23.4-pre.1" + let secondVersion = "1.23.4" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertFalse(result) + } + + func testRcComponentIsLowerThanNumberComponent() { + + let firstVersion = "1.23.4-rc.1" + let secondVersion = "1.23.4" + + let result = VersionValidator.compare(firstVersion, isHigherOrEqualTo: secondVersion) + XCTAssertFalse(result) + } } diff --git a/dependencies.env b/dependencies.env index c5a4cd87..cc2658a6 100644 --- a/dependencies.env +++ b/dependencies.env @@ -1 +1 @@ -FFI_VERSION="0.44.0" \ No newline at end of file +FFI_VERSION="0.49.0-pre.4" \ No newline at end of file diff --git a/update_dependencies.sh b/update_dependencies.sh index b43f5848..c3bcff49 100755 --- a/update_dependencies.sh +++ b/update_dependencies.sh @@ -2,8 +2,10 @@ FILE=env.json WORKING_DIR=Temp -HEADER_FILE_NAME=wallet.h -LIB_FILE_NAME=libtari_wallet_ffi.a +FRAMEWORK_ZIP_FILE_NAME=libtari_wallet_ffi.ios-xcframework.zip +FRAMEWORK_DIRECTORY=libwallet-ios-xcframework +FRAMEWORK_FILE_NAME=libtari_wallet_ffi_ios.xcframework +PROJECT_FRAMEWORK_DIRECTORY=./MobileWallet/TariLib if test ! -f "$FILE"; then echo "$FILE does not exist. Creating default." @@ -17,10 +19,12 @@ source dependencies.env rm -rf $WORKING_DIR mkdir $WORKING_DIR -curl -L "https://github.com/tari-project/tari/releases/download/v$FFI_VERSION/libtari_wallet_ffi.h" -o "./$WORKING_DIR/$HEADER_FILE_NAME" -curl -L "https://github.com/tari-project/tari/releases/download/v$FFI_VERSION/libtari_wallet_ffi.ios_universal.a" -o "./$WORKING_DIR/$LIB_FILE_NAME" +curl -L "https://github.com/tari-project/tari/releases/download/v$FFI_VERSION/$FRAMEWORK_ZIP_FILE_NAME" -o "./$WORKING_DIR/$FRAMEWORK_ZIP_FILE_NAME" +unzip "./$WORKING_DIR/$FRAMEWORK_ZIP_FILE_NAME" -d "./$WORKING_DIR" +rm -f "./$WORKING_DIR/$FRAMEWORK_ZIP_FILE_NAME" -mv $WORKING_DIR/* ./MobileWallet/TariLib +rm -rf $PROJECT_FRAMEWORK_DIRECTORY/$FRAMEWORK_FILE_NAME +mv $WORKING_DIR/$FRAMEWORK_DIRECTORY/$FRAMEWORK_FILE_NAME $PROJECT_FRAMEWORK_DIRECTORY rm -rf $WORKING_DIR # Check for cocoapods and install if missing. @@ -57,13 +61,7 @@ if [ $API_KEY != null ]; then done fi -# Constants.plist - -CONSTS_PLIST_PATH="./MobileWallet/Constants.plist" -FFI_VERSION_KEY="FFI Version" - -plutil -create xml1 $CONSTS_PLIST_PATH -plutil -replace "$FFI_VERSION_KEY" -string $FFI_VERSION $CONSTS_PLIST_PATH +# Git Hooks echo "\n\n***Updating Git Hooks***"