From bdecd3395e79c3f611e5de9b3face2c600abc086 Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Sun, 1 Sep 2024 15:57:58 +0700 Subject: [PATCH 1/5] fix: add dash-shared-core to DashSync target --- DashSyncCurrentCommit | 2 +- Podfile | 1 + Podfile.lock | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DashSyncCurrentCommit b/DashSyncCurrentCommit index 5554751eb..530482057 100644 --- a/DashSyncCurrentCommit +++ b/DashSyncCurrentCommit @@ -1 +1 @@ -03f3d451e8ce15e99ec2ef20b4b67fe07b717ca3 +01e0c602fdb67517b9bb562fabbf8344f3aca636 diff --git a/Podfile b/Podfile index df1cec330..82fe377e5 100644 --- a/Podfile +++ b/Podfile @@ -28,6 +28,7 @@ target 'dashpay' do platform :ios, '14.0' pod 'DashSync', :path => '../DashSync/' + pod 'DashSharedCore', :path => '../dash-shared-core/' pod 'SQLite.swift', '~> 0.13.3' pod 'SQLiteMigrationManager.swift' pod 'CloudInAppMessaging', '0.1.0' diff --git a/Podfile.lock b/Podfile.lock index 2700ece4b..e4d7ba00e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -868,6 +868,6 @@ SPEC CHECKSUMS: TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 UIViewController-KeyboardAdditions: a691dc7e63a49854d341455a778ee8497dfc4662 -PODFILE CHECKSUM: 026ae123f0be550c31403c9f3af202a867705388 +PODFILE CHECKSUM: f2fa4cb9f3987af3acdd6a2624930750c1c1ddee COCOAPODS: 1.15.2 From 2aae77005fb520c21e3c47bf7e9869400dfd66ce Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Sun, 1 Sep 2024 15:58:20 +0700 Subject: [PATCH 2/5] chore: move SettingsMenuViewController to Swift --- DashWallet.xcodeproj/project.pbxproj | 36 +- .../UI/Menu/Main/DWMainMenuViewController.m | 1 - .../Settings/About/AboutViewController.swift | 54 --- .../Settings/About/DWAboutViewController.h | 2 +- .../Settings/About/DWAboutViewController.m | 2 +- .../Settings/DWSettingsMenuViewController.h | 36 -- .../Settings/DWSettingsMenuViewController.m | 375 ------------------ .../Settings/SettingsMenuViewController.swift | 266 +++++++++++++ .../UI/SwiftUI Components/MenuItem.swift | 75 +++- DashWallet/dashwallet-Bridging-Header.h | 4 +- 10 files changed, 352 insertions(+), 499 deletions(-) delete mode 100644 DashWallet/Sources/UI/Menu/Settings/About/AboutViewController.swift delete mode 100644 DashWallet/Sources/UI/Menu/Settings/DWSettingsMenuViewController.h delete mode 100644 DashWallet/Sources/UI/Menu/Settings/DWSettingsMenuViewController.m create mode 100644 DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index e2f0db40c..a64fa4b4a 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -191,7 +191,7 @@ 2A7A7BC92347E0D700451078 /* DWBaseFormTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BC82347E0D700451078 /* DWBaseFormTableViewCell.m */; }; 2A7A7BCD2347F01B00451078 /* DWSecurityMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BCC2347F01B00451078 /* DWSecurityMenuViewController.m */; }; 2A7A7BD02348A34800451078 /* DWSecurityMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BCF2348A34800451078 /* DWSecurityMenuModel.m */; }; - 2A7A7BD62348CB6600451078 /* DWSettingsMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BD52348CB6600451078 /* DWSettingsMenuViewController.m */; }; + 2A7A7BD62348CB6600451078 /* SettingsMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BD52348CB6600451078 /* SettingsMenuViewController.swift */; }; 2A7A7BD92348CB7300451078 /* DWSettingsMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BD82348CB7300451078 /* DWSettingsMenuModel.m */; }; 2A7A7C16234B763600451078 /* DWLocalCurrencyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7C15234B763600451078 /* DWLocalCurrencyViewController.m */; }; 2A7A7C1D234B771400451078 /* DWLocalCurrencyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7C1C234B771400451078 /* DWLocalCurrencyModel.m */; }; @@ -224,7 +224,6 @@ 2A8C24B423336FEA00000D43 /* DWQuickReceiveViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C24B323336FEA00000D43 /* DWQuickReceiveViewController.m */; }; 2A8C24B6233370A600000D43 /* QuickReceive.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A8C24B5233370A600000D43 /* QuickReceive.storyboard */; }; 2A8F420921BED16300858B91 /* DashSyncCurrentCommit in Resources */ = {isa = PBXBuildFile; fileRef = 2A8F420821BED16300858B91 /* DashSyncCurrentCommit */; }; - 2A8F420F21BEE95D00858B91 /* DWAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8F420E21BEE95D00858B91 /* DWAboutViewController.m */; }; 2A8F422021BEFEEA00858B91 /* DWAboutModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8F421F21BEFEEA00858B91 /* DWAboutModel.m */; }; 2A913E6623A11DFE006A2A59 /* DWURLActions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E6523A11DFE006A2A59 /* DWURLActions.m */; }; 2A913E6823A1473A006A2A59 /* DWURLRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E6723A14739006A2A59 /* DWURLRequestHandler.m */; }; @@ -476,7 +475,6 @@ 47AE8C1828C63F9C00490F5E /* PointOfUseDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1728C63F9C00490F5E /* PointOfUseDetailsView.swift */; }; 47AE8C1A28C6A21A00490F5E /* AllMerchantLocationsDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1928C6A21A00490F5E /* AllMerchantLocationsDataProvider.swift */; }; 47AE8C1C28C6AA2500490F5E /* PointOfUseLocationServicePopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1B28C6AA2400490F5E /* PointOfUseLocationServicePopup.swift */; }; - 47AE8C1E28C7491C00490F5E /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1D28C7491C00490F5E /* AboutViewController.swift */; }; 47AF180529070B720025803E /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AF180429070B720025803E /* Types.swift */; }; 47AF18082907B7880025803E /* BaseAmountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AF18072907B7880025803E /* BaseAmountModel.swift */; }; 47B30D78290BFCA60080C326 /* NumberFormatter+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B30D77290BFCA60080C326 /* NumberFormatter+DashWallet.swift */; }; @@ -558,6 +556,8 @@ 754495DF2AE91D3500492817 /* UsernameRequestCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754495DE2AE91D3500492817 /* UsernameRequestCell.swift */; }; 754BEA122C0B6BD700E8C93C /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754BEA112C0B6BD700E8C93C /* HomeViewModel.swift */; }; 754BEA132C0B6BD700E8C93C /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754BEA112C0B6BD700E8C93C /* HomeViewModel.swift */; }; + 755049A92C846299008FA7EB /* DWAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 755049A72C846299008FA7EB /* DWAboutViewController.m */; }; + 755049AA2C846299008FA7EB /* DWAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 755049A72C846299008FA7EB /* DWAboutViewController.m */; }; 755A22BD2B1385FD001F170D /* IconAttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755A22BC2B1385FD001F170D /* IconAttributedText.swift */; }; 755B4B222B0C903500B844F0 /* DWDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755B4B212B0C903500B844F0 /* DWDateFormatter.swift */; }; 755B4B232B0C903500B844F0 /* DWDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755B4B212B0C903500B844F0 /* DWDateFormatter.swift */; }; @@ -960,8 +960,7 @@ C9D2C6AD2A320AA000D15901 /* HairlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1428C6378E00490F5E /* HairlineView.swift */; }; C9D2C6AE2A320AA000D15901 /* DWInitialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E9423A3F75F006A2A59 /* DWInitialViewController.m */; }; C9D2C6AF2A320AA000D15901 /* BuySellPortalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751136B28D9A3DB00223B77 /* BuySellPortalViewController.swift */; }; - C9D2C6B02A320AA000D15901 /* DWSettingsMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BD52348CB6600451078 /* DWSettingsMenuViewController.m */; }; - C9D2C6B12A320AA000D15901 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1D28C7491C00490F5E /* AboutViewController.swift */; }; + C9D2C6B02A320AA000D15901 /* SettingsMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BD52348CB6600451078 /* SettingsMenuViewController.swift */; }; C9D2C6B22A320AA000D15901 /* UISpringTimingParameters+DWInit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69D92314727F001B8C90 /* UISpringTimingParameters+DWInit.m */; }; C9D2C6B62A320AA000D15901 /* NumberKeyboardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661AC28F972BD00028A8D /* NumberKeyboardButton.swift */; }; C9D2C6B72A320AA000D15901 /* IsDefaultEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11AE3DD72997C599000856EE /* IsDefaultEmail.swift */; }; @@ -1404,7 +1403,6 @@ C9D2C8D12A320AA000D15901 /* DWSeedWordModel+DWLayoutSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE9622DD0E8E00C99324 /* DWSeedWordModel+DWLayoutSupport.m */; }; C9D2C8D22A320AA000D15901 /* CoinbaseAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EEE242293F436200049E0B /* CoinbaseAPIClient.swift */; }; C9D2C8D32A320AA000D15901 /* SendAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B30D7F291123D30080C326 /* SendAmountViewController.swift */; }; - C9D2C8D42A320AA000D15901 /* DWAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8F420E21BEE95D00858B91 /* DWAboutViewController.m */; }; C9D2C8D52A320AA000D15901 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF46A0296540EF0067B6EE /* AccountService.swift */; }; C9D2C8D62A320AA000D15901 /* ListHandlerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C6E6E6291A90B2003FEDF2 /* ListHandlerView.swift */; }; C9D2C8D72A320AA000D15901 /* DWSecurityMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BCC2347F01B00451078 /* DWSecurityMenuViewController.m */; }; @@ -1914,8 +1912,7 @@ 2A7A7BCC2347F01B00451078 /* DWSecurityMenuViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSecurityMenuViewController.m; sourceTree = ""; }; 2A7A7BCE2348A34800451078 /* DWSecurityMenuModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSecurityMenuModel.h; sourceTree = ""; }; 2A7A7BCF2348A34800451078 /* DWSecurityMenuModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSecurityMenuModel.m; sourceTree = ""; }; - 2A7A7BD42348CB6600451078 /* DWSettingsMenuViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSettingsMenuViewController.h; sourceTree = ""; }; - 2A7A7BD52348CB6600451078 /* DWSettingsMenuViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSettingsMenuViewController.m; sourceTree = ""; }; + 2A7A7BD52348CB6600451078 /* SettingsMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMenuViewController.swift; sourceTree = ""; }; 2A7A7BD72348CB7300451078 /* DWSettingsMenuModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSettingsMenuModel.h; sourceTree = ""; }; 2A7A7BD82348CB7300451078 /* DWSettingsMenuModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSettingsMenuModel.m; sourceTree = ""; }; 2A7A7C14234B763600451078 /* DWLocalCurrencyViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWLocalCurrencyViewController.h; sourceTree = ""; }; @@ -1978,8 +1975,6 @@ 2A8E79BC2406772900AA7C3D /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 2A8E79BD2406772900AA7C3D /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = ""; }; 2A8F420821BED16300858B91 /* DashSyncCurrentCommit */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DashSyncCurrentCommit; sourceTree = SOURCE_ROOT; }; - 2A8F420D21BEE95D00858B91 /* DWAboutViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWAboutViewController.h; sourceTree = ""; }; - 2A8F420E21BEE95D00858B91 /* DWAboutViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWAboutViewController.m; sourceTree = ""; }; 2A8F421E21BEFEEA00858B91 /* DWAboutModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWAboutModel.h; sourceTree = ""; }; 2A8F421F21BEFEEA00858B91 /* DWAboutModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWAboutModel.m; sourceTree = ""; }; 2A913E6023A11DCA006A2A59 /* DWURLRequestHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWURLRequestHandler.h; sourceTree = ""; }; @@ -2358,7 +2353,6 @@ 47AE8C1728C63F9C00490F5E /* PointOfUseDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfUseDetailsView.swift; sourceTree = ""; }; 47AE8C1928C6A21A00490F5E /* AllMerchantLocationsDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMerchantLocationsDataProvider.swift; sourceTree = ""; }; 47AE8C1B28C6AA2400490F5E /* PointOfUseLocationServicePopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfUseLocationServicePopup.swift; sourceTree = ""; }; - 47AE8C1D28C7491C00490F5E /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 47AF180429070B720025803E /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; 47AF18072907B7880025803E /* BaseAmountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseAmountModel.swift; sourceTree = ""; }; 47B30D77290BFCA60080C326 /* NumberFormatter+DashWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NumberFormatter+DashWallet.swift"; sourceTree = ""; }; @@ -2436,6 +2430,8 @@ 754495DC2AE91B6300492817 /* GroupedRequestCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupedRequestCell.swift; sourceTree = ""; }; 754495DE2AE91D3500492817 /* UsernameRequestCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsernameRequestCell.swift; sourceTree = ""; }; 754BEA112C0B6BD700E8C93C /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; + 755049A72C846299008FA7EB /* DWAboutViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAboutViewController.m; sourceTree = ""; }; + 755049A82C846299008FA7EB /* DWAboutViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAboutViewController.h; sourceTree = ""; }; 755A22BC2B1385FD001F170D /* IconAttributedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconAttributedText.swift; sourceTree = ""; }; 755B4B212B0C903500B844F0 /* DWDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DWDateFormatter.swift; sourceTree = ""; }; 755C32372C358FBD007DA721 /* BackupSeedPhraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSeedPhraseViewController.swift; sourceTree = ""; }; @@ -4190,8 +4186,7 @@ children = ( 2A7A7C13234B761700451078 /* LocalCurrency */, 2A8F420A21BEE69E00858B91 /* About */, - 2A7A7BD42348CB6600451078 /* DWSettingsMenuViewController.h */, - 2A7A7BD52348CB6600451078 /* DWSettingsMenuViewController.m */, + 2A7A7BD52348CB6600451078 /* SettingsMenuViewController.swift */, 2A7A7BD72348CB7300451078 /* DWSettingsMenuModel.h */, 2A7A7BD82348CB7300451078 /* DWSettingsMenuModel.m */, ); @@ -4405,12 +4400,11 @@ 2A8F420A21BEE69E00858B91 /* About */ = { isa = PBXGroup; children = ( - 2A8F420D21BEE95D00858B91 /* DWAboutViewController.h */, - 2A8F420E21BEE95D00858B91 /* DWAboutViewController.m */, + 755049A82C846299008FA7EB /* DWAboutViewController.h */, + 755049A72C846299008FA7EB /* DWAboutViewController.m */, 2A8F421E21BEFEEA00858B91 /* DWAboutModel.h */, 2A8F421F21BEFEEA00858B91 /* DWAboutModel.m */, 2AB7C906234DB82700A56795 /* About.storyboard */, - 47AE8C1D28C7491C00490F5E /* AboutViewController.swift */, ); path = About; sourceTree = ""; @@ -8464,8 +8458,7 @@ 47AE8C1528C6378E00490F5E /* HairlineView.swift in Sources */, 2A913E9523A3F75F006A2A59 /* DWInitialViewController.m in Sources */, 4751136C28D9A3DB00223B77 /* BuySellPortalViewController.swift in Sources */, - 2A7A7BD62348CB6600451078 /* DWSettingsMenuViewController.m in Sources */, - 47AE8C1E28C7491C00490F5E /* AboutViewController.swift in Sources */, + 2A7A7BD62348CB6600451078 /* SettingsMenuViewController.swift in Sources */, 2A0C69DA2314727F001B8C90 /* UISpringTimingParameters+DWInit.m in Sources */, 47C661AD28F972BD00028A8D /* NumberKeyboardButton.swift in Sources */, 11AE3DD82997C599000856EE /* IsDefaultEmail.swift in Sources */, @@ -8857,6 +8850,7 @@ 75CED09E2ACFD0ED0095F10C /* CoinbaseDepositRequest.swift in Sources */, C94D98212A4CC78F00F3BEE1 /* DashInputField.swift in Sources */, 2A8B9E6822FFE4CC00FF8653 /* DWPayOptionModel.m in Sources */, + 755049A92C846299008FA7EB /* DWAboutViewController.m in Sources */, 0F6EDFC928C896BD000427E7 /* CoinbaseTokenResponse.swift in Sources */, C9F42FB229DD5141001BC549 /* BackupInfoViewController.swift in Sources */, 47C6E6E5291A68B6003FEDF2 /* AppliedFiltersView.swift in Sources */, @@ -8954,7 +8948,6 @@ 2AD1CE9722DD0E8E00C99324 /* DWSeedWordModel+DWLayoutSupport.m in Sources */, 47EEE243293F436200049E0B /* CoinbaseAPIClient.swift in Sources */, 47B30D80291123D30080C326 /* SendAmountViewController.swift in Sources */, - 2A8F420F21BEE95D00858B91 /* DWAboutViewController.m in Sources */, 47CF46A1296540EF0067B6EE /* AccountService.swift in Sources */, 47C6E6E7291A90B3003FEDF2 /* ListHandlerView.swift in Sources */, 7566F4832BB6949E005238D2 /* ToolsMenuViewController.swift in Sources */, @@ -9073,8 +9066,7 @@ C9D2C6AE2A320AA000D15901 /* DWInitialViewController.m in Sources */, C943B52D2A40A54600AF23C5 /* DWDashPayContactsUpdater.m in Sources */, C9D2C6AF2A320AA000D15901 /* BuySellPortalViewController.swift in Sources */, - C9D2C6B02A320AA000D15901 /* DWSettingsMenuViewController.m in Sources */, - C9D2C6B12A320AA000D15901 /* AboutViewController.swift in Sources */, + C9D2C6B02A320AA000D15901 /* SettingsMenuViewController.swift in Sources */, 751B61C52ADFFD0700D1C2EF /* IntegrationViewController+Uphold.swift in Sources */, C943B5022A40A54600AF23C5 /* DWDPGenericStatusItemView.m in Sources */, C9D2C6B22A320AA000D15901 /* UISpringTimingParameters+DWInit.m in Sources */, @@ -9301,6 +9293,7 @@ C943B4BC2A40A54600AF23C5 /* DWRootContactsViewController.m in Sources */, C943B4C32A40A54600AF23C5 /* DWTitleActionHeaderView.m in Sources */, 7531308E2B47EC910069C9B7 /* UpholdClient.swift in Sources */, + 755049AA2C846299008FA7EB /* DWAboutViewController.m in Sources */, C9D2C7502A320AA000D15901 /* ServiceOverviewViewController.swift in Sources */, C9D2C7512A320AA000D15901 /* AccountListModel.swift in Sources */, C9D2C7532A320AA000D15901 /* DWRecoverWalletCommand.m in Sources */, @@ -9774,7 +9767,6 @@ C943B4ED2A40A54600AF23C5 /* DWPendingContactInfoView.m in Sources */, C9D2C8D32A320AA000D15901 /* SendAmountViewController.swift in Sources */, 757514E02B15D8DE0026AD8E /* VotingConstants.swift in Sources */, - C9D2C8D42A320AA000D15901 /* DWAboutViewController.m in Sources */, C943B51E2A40A54600AF23C5 /* InvitationTopView.swift in Sources */, C9D2C8D52A320AA000D15901 /* AccountService.swift in Sources */, C9D2C8D62A320AA000D15901 /* ListHandlerView.swift in Sources */, diff --git a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m index deebf02c4..ad591d168 100644 --- a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m +++ b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m @@ -25,7 +25,6 @@ #import "DWMainMenuContentView.h" #import "DWMainMenuModel.h" #import "DWSecurityMenuViewController.h" -#import "DWSettingsMenuViewController.h" #import "SFSafariViewController+DashWallet.h" #import "dashwallet-Swift.h" diff --git a/DashWallet/Sources/UI/Menu/Settings/About/AboutViewController.swift b/DashWallet/Sources/UI/Menu/Settings/About/AboutViewController.swift deleted file mode 100644 index f5023df2c..000000000 --- a/DashWallet/Sources/UI/Menu/Settings/About/AboutViewController.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Created by Pavel Tikhonenko -// Copyright © 2022 Dash Core Group. All rights reserved. -// -// Licensed under the MIT License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://opensource.org/licenses/MIT -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import UIKit - -// MARK: - AboutViewController - -@objc -class AboutViewController: UIViewController { - private var model = DWAboutModel() - - private weak var techInfoAlert: UIAlertController? - - @objc - static func controller() -> AboutViewController { - let controller = AboutViewController() - controller.hidesBottomBarWhenPushed = true - controller.title = NSLocalizedString("About", comment: "") - return controller - } - - override func viewDidLoad() { - super.viewDidLoad() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - navigationController?.navigationBar.prefersLargeTitles = true - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - navigationController?.navigationBar.prefersLargeTitles = false - } -} - -extension AboutViewController { - private func configureHierarchy() { } -} - diff --git a/DashWallet/Sources/UI/Menu/Settings/About/DWAboutViewController.h b/DashWallet/Sources/UI/Menu/Settings/About/DWAboutViewController.h index 36e943dc0..d79322fcf 100644 --- a/DashWallet/Sources/UI/Menu/Settings/About/DWAboutViewController.h +++ b/DashWallet/Sources/UI/Menu/Settings/About/DWAboutViewController.h @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @interface DWAboutViewController : UIViewController -+ (instancetype)controller; ++ (instancetype)createController; @end diff --git a/DashWallet/Sources/UI/Menu/Settings/About/DWAboutViewController.m b/DashWallet/Sources/UI/Menu/Settings/About/DWAboutViewController.m index 1191e0c7b..28787a364 100644 --- a/DashWallet/Sources/UI/Menu/Settings/About/DWAboutViewController.m +++ b/DashWallet/Sources/UI/Menu/Settings/About/DWAboutViewController.m @@ -52,7 +52,7 @@ @interface DWAboutViewController () @implementation DWAboutViewController -+ (instancetype)controller { ++ (instancetype)createController { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"About" bundle:nil]; DWAboutViewController *controller = [storyboard instantiateInitialViewController]; controller.hidesBottomBarWhenPushed = YES; diff --git a/DashWallet/Sources/UI/Menu/Settings/DWSettingsMenuViewController.h b/DashWallet/Sources/UI/Menu/Settings/DWSettingsMenuViewController.h deleted file mode 100644 index 2ad6ca642..000000000 --- a/DashWallet/Sources/UI/Menu/Settings/DWSettingsMenuViewController.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 Dash Core Group. All rights reserved. -// -// Licensed under the MIT License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://opensource.org/licenses/MIT -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DWSettingsMenuViewController; - -@protocol DWSettingsMenuViewControllerDelegate - -- (void)settingsMenuViewControllerDidRescanBlockchain:(DWSettingsMenuViewController *)controller; - -@end - -@interface DWSettingsMenuViewController : UIViewController - -@property (nullable, nonatomic, weak) id delegate; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Menu/Settings/DWSettingsMenuViewController.m b/DashWallet/Sources/UI/Menu/Settings/DWSettingsMenuViewController.m deleted file mode 100644 index 86be2b2fb..000000000 --- a/DashWallet/Sources/UI/Menu/Settings/DWSettingsMenuViewController.m +++ /dev/null @@ -1,375 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 Dash Core Group. All rights reserved. -// -// Licensed under the MIT License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://opensource.org/licenses/MIT -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "DWSettingsMenuViewController.h" - -#import "DWAboutViewController.h" -#import "DWFormTableViewController.h" -#import "DWLocalCurrencyViewController.h" -#import "DWSettingsMenuModel.h" -#import "DWUIKit.h" -#import "UIView+DWHUD.h" -#import "UIViewController+DWDisplayError.h" -#import -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWSettingsMenuViewController () - -@property (null_resettable, nonatomic, strong) DWSettingsMenuModel *model; -@property (nonatomic, strong) DWFormTableViewController *formController; -@property (strong, nonatomic) DWSelectorFormCellModel *localCurrencyCellModel; -@property (strong, nonatomic) DWSelectorFormCellModel *switchNetworkCellModel; - -@end - -@implementation DWSettingsMenuViewController - -- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - self.title = NSLocalizedString(@"Settings", nil); - self.hidesBottomBarWhenPushed = YES; - } - return self; -} - -- (DWSettingsMenuModel *)model { - if (!_model) { - _model = [[DWSettingsMenuModel alloc] init]; - } - - return _model; -} - -- (NSArray *)items { - __weak typeof(self) weakSelf = self; - - NSMutableArray *items = [NSMutableArray array]; - - { - DWSelectorFormCellModel *cellModel = [[DWSelectorFormCellModel alloc] initWithTitle:NSLocalizedString(@"Local Currency", nil)]; - self.localCurrencyCellModel = cellModel; - [self updateLocalCurrencyCellModel]; - cellModel.accessoryType = DWSelectorFormAccessoryType_DisclosureIndicator; - cellModel.didSelectBlock = ^(DWSelectorFormCellModel *_Nonnull cellModel, NSIndexPath *_Nonnull indexPath) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - [strongSelf showCurrencySelector]; - }; - [items addObject:cellModel]; - } - - { - DWSwitcherFormCellModel *cellModel = [[DWSwitcherFormCellModel alloc] initWithTitle:NSLocalizedString(@"Enable Receive Notifications", nil)]; - cellModel.on = self.model.notificationsEnabled; - cellModel.didChangeValueBlock = ^(DWSwitcherFormCellModel *_Nonnull cellModel) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - strongSelf.model.notificationsEnabled = cellModel.on; - }; - [items addObject:cellModel]; - } - - { - DWSelectorFormCellModel *cellModel = [[DWSelectorFormCellModel alloc] initWithTitle:NSLocalizedString(@"Network", nil)]; - self.switchNetworkCellModel = cellModel; - [self updateSwitchNetworkCellModel]; - cellModel.accessoryType = DWSelectorFormAccessoryType_DisclosureIndicator; - cellModel.didSelectBlock = ^(DWSelectorFormCellModel *_Nonnull cellModel, NSIndexPath *_Nonnull indexPath) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - UITableView *tableView = self.formController.tableView; - UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; - [strongSelf showChangeNetworkFromSourceView:tableView sourceRect:cell.frame]; - }; - [items addObject:cellModel]; - } - - { - DWSelectorFormCellModel *cellModel = [[DWSelectorFormCellModel alloc] initWithTitle:NSLocalizedString(@"Rescan Blockchain", nil)]; - cellModel.didSelectBlock = ^(DWSelectorFormCellModel *_Nonnull cellModel, NSIndexPath *_Nonnull indexPath) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - UITableView *tableView = self.formController.tableView; - UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; - [strongSelf showWarningAboutReclassifiedTransactionsRypes:tableView sourceRect:cell.frame]; - }; - [items addObject:cellModel]; - } - - { - DWSelectorFormCellModel *cellModel = [[DWSelectorFormCellModel alloc] initWithTitle:NSLocalizedString(@"About", nil)]; - cellModel.accessoryType = DWSelectorFormAccessoryType_DisclosureIndicator; - cellModel.didSelectBlock = ^(DWSelectorFormCellModel *_Nonnull cellModel, NSIndexPath *_Nonnull indexPath) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - [strongSelf showAboutController]; - }; - [items addObject:cellModel]; - } - -#if DASHPAY - { - DWSelectorFormCellModel *cellModel = [[DWSelectorFormCellModel alloc] initWithTitle:NSLocalizedString(@"CoinJoin", nil)]; - cellModel.didSelectBlock = ^(DWSelectorFormCellModel *_Nonnull cellModel, NSIndexPath *_Nonnull indexPath) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - [strongSelf showCoinJoinController]; - }; - [items addObject:cellModel]; - } - - { - DWSwitcherFormCellModel *cellModel = [[DWSwitcherFormCellModel alloc] initWithTitle:@"Enable Voting"]; - cellModel.on = [VotingPrefsWrapper getIsEnabled]; - cellModel.didChangeValueBlock = ^(DWSwitcherFormCellModel *_Nonnull cellModel) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - [VotingPrefsWrapper setIsEnabledWithValue:cellModel.on]; - }; - [items addObject:cellModel]; - } -#endif - - return [items copy]; -} - -- (NSArray *)sections { - NSMutableArray *sections = [NSMutableArray array]; - - for (DWBaseFormCellModel *item in [self items]) { - DWFormSectionModel *section = [[DWFormSectionModel alloc] init]; - section.items = @[ item ]; - [sections addObject:section]; - } - - return [sections copy]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - DWFormTableViewController *formController = [[DWFormTableViewController alloc] initWithStyle:UITableViewStylePlain]; - [formController setSections:[self sections] placeholderText:nil]; - - [self dw_embedChild:formController]; - self.formController = formController; -} - -- (UIStatusBarStyle)preferredStatusBarStyle { - return UIStatusBarStyleLightContent; -} - -#pragma mark - DWLocalCurrencyViewControllerDelegate - -- (void)localCurrencyViewController:(DWLocalCurrencyViewController *)controller - didSelectCurrency:(nonnull NSString *)currencyCode { - [self updateLocalCurrencyCellModel]; - [self.navigationController popViewControllerAnimated:YES]; -} - -- (void)localCurrencyViewControllerDidCancel:(DWLocalCurrencyViewController *)controller { - NSAssert(NO, @"Not supported"); -} - -#pragma mark - Private - -- (void)updateLocalCurrencyCellModel { - self.localCurrencyCellModel.subTitle = self.model.localCurrencyCode; -} - -- (void)updateSwitchNetworkCellModel { - self.switchNetworkCellModel.subTitle = self.model.networkName; -} - -- (void)rescanBlockchainActionFromSourceView:(UIView *)sourceView sourceRect:(CGRect)sourceRect { - [DWSettingsMenuModel rescanBlockchainActionFromController:self - sourceView:sourceView - sourceRect:sourceRect - completion:^(BOOL confirmed) { - if (confirmed) { - [self.delegate settingsMenuViewControllerDidRescanBlockchain:self]; - } - }]; -} - -- (void)showCurrencySelector { - DWLocalCurrencyViewController *controller = - [[DWLocalCurrencyViewController alloc] initWithNavigationAppearance:DWNavigationAppearance_Default - presentationMode:DWCurrencyPickerPresentationMode_Screen - currencyCode:nil]; - controller.delegate = self; - [self.navigationController pushViewController:controller animated:YES]; -} - -- (void)showAboutController { - DWAboutViewController *aboutViewController = [DWAboutViewController controller]; - [self.navigationController pushViewController:aboutViewController animated:YES]; -} - -- (void)showCoinJoinController { - UIViewController *vc; - - if (CoinJoinObjcWrapper.infoShown) { - vc = [CoinJoinLevelsViewController controller]; - } else { - vc = [CoinJoinInfoViewController controller]; - } - - [self.navigationController pushViewController:vc animated:YES]; -} - -- (void)showChangeNetworkFromSourceView:(UIView *)sourceView sourceRect:(CGRect)sourceRect { - UIAlertController *actionSheet = [UIAlertController - alertControllerWithTitle:NSLocalizedString(@"Network", nil) - message:nil - preferredStyle:UIAlertControllerStyleActionSheet]; - UIAlertAction *mainnet = [UIAlertAction - actionWithTitle:DSLocalizedString(@"Mainnet", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction *action) { - [DWSettingsMenuModel switchToMainnetWithCompletion:^(BOOL success) { - if (success) { - [self updateSwitchNetworkCellModel]; - } - }]; - }]; - UIAlertAction *testnet = [UIAlertAction - actionWithTitle:DSLocalizedString(@"Testnet", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction *action) { - [DWSettingsMenuModel switchToTestnetWithCompletion:^(BOOL success) { - if (success) { - [self updateSwitchNetworkCellModel]; - } - }]; - }]; - - // UIAlertAction *evonet = [UIAlertAction - // actionWithTitle:DSLocalizedString(@"Evonet", nil) - // style:UIAlertActionStyleDefault - // handler:^(UIAlertAction *action) { - // [DWSettingsMenuModel switchToEvonetWithCompletion:^(BOOL success) { - // if (success) { - // [self updateSwitchNetworkCellModel]; - // } - // }]; - // }]; - - UIAlertAction *cancel = [UIAlertAction - actionWithTitle:NSLocalizedString(@"Cancel", nil) - style:UIAlertActionStyleCancel - handler:nil]; - [actionSheet addAction:mainnet]; - [actionSheet addAction:testnet]; - // [actionSheet addAction:evonet]; - [actionSheet addAction:cancel]; - if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { - actionSheet.popoverPresentationController.sourceView = sourceView; - actionSheet.popoverPresentationController.sourceRect = sourceRect; - } - [self presentViewController:actionSheet - animated:YES - completion:nil]; -} - -- (void)showWarningAboutReclassifiedTransactionsRypes:(UIView *)sourceView sourceRect:(CGRect)sourceRect { - __weak typeof(self) weakSelf = self; - - UIAlertController *actionSheet = [UIAlertController - alertControllerWithTitle:NSLocalizedString(@"You will lose all your manually reclassified transactions types", nil) - message:NSLocalizedString(@"If you would like to save manually reclassified types for transactions you should export a CSV transaction file.", nil) - preferredStyle:UIAlertControllerStyleActionSheet]; - UIAlertAction *continueAction = [UIAlertAction - actionWithTitle:DSLocalizedString(@"Continue", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction *action) { - [self rescanBlockchainActionFromSourceView:sourceView sourceRect:sourceRect]; - }]; - UIAlertAction *export = [UIAlertAction - actionWithTitle:DSLocalizedString(@"Export CSV", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction *action) { - [weakSelf exportTransactionsInCSV]; - }]; - UIAlertAction *cancel = [UIAlertAction - actionWithTitle:NSLocalizedString(@"Cancel", nil) - style:UIAlertActionStyleCancel - handler:nil]; - [actionSheet addAction:export]; - [actionSheet addAction:continueAction]; - [actionSheet addAction:cancel]; - - if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { - actionSheet.popoverPresentationController.sourceView = sourceView; - actionSheet.popoverPresentationController.sourceRect = sourceRect; - } - [self presentViewController:actionSheet - animated:YES - completion:nil]; -} - -- (void)exportTransactionsInCSV { - [self.view dw_showProgressHUDWithMessage:NSLocalizedString(@"Generating CSV Report", nil)]; - __weak typeof(self) weakSelf = self; - - [DWSettingsMenuModel - generateCSVReportWithCompletionHandler:^(NSString *_Nonnull fileName, NSURL *_Nonnull file) { - [weakSelf.view dw_hideProgressHUD]; - - UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[ file ] applicationActivities:nil]; - [activityViewController setValue:fileName forKey:@"subject"]; - if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { - activityViewController.popoverPresentationController.sourceView = weakSelf.view; - activityViewController.popoverPresentationController.sourceRect = weakSelf.view.bounds; - } - - [weakSelf presentViewController:activityViewController animated:YES completion:nil]; - } - errorHandler:^(NSError *_Nonnull error) { - [weakSelf.view dw_hideProgressHUD]; - [weakSelf dw_displayErrorModally:error]; - }]; -} -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift new file mode 100644 index 000000000..3ca927f37 --- /dev/null +++ b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift @@ -0,0 +1,266 @@ +// +// Created by Andrew Podkovyrin +// Copyright © 2019 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +@objc(DWSettingsMenuViewControllerDelegate) +protocol SettingsMenuViewControllerDelegate: AnyObject { + func settingsMenuViewControllerDidRescanBlockchain(_ controller: SettingsMenuViewController) +} + +@objc(DWSettingsMenuViewController) +class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControllerDelegate { + + @objc weak var delegate: SettingsMenuViewControllerDelegate? + + private lazy var model: DWSettingsMenuModel = DWSettingsMenuModel() + private var formController: DWFormTableViewController! + private var localCurrencyCellModel: DWSelectorFormCellModel! + private var switchNetworkCellModel: DWSelectorFormCellModel! + + init() { + super.init(nibName: nil, bundle: nil) + title = NSLocalizedString("Settings", comment: "") + hidesBottomBarWhenPushed = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .dw_secondaryBackground() + + formController = DWFormTableViewController(style: .plain) + formController.setSections(sections, placeholderText: nil) + + dw_embedChild(formController) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + // MARK: - LocalCurrencyViewControllerDelegate + + func localCurrencyViewController(_ controller: DWLocalCurrencyViewController, didSelectCurrency currencyCode: String) { + updateLocalCurrencyCellModel() + navigationController?.popViewController(animated: true) + } + + func localCurrencyViewControllerDidCancel(_ controller: DWLocalCurrencyViewController) { + assertionFailure("Not supported") + } + + // MARK: - Private + + private var items: [DWBaseFormCellModel] { + var items: [DWBaseFormCellModel] = [] + + let localCurrencyCell = DWSelectorFormCellModel(title: NSLocalizedString("Local Currency", comment: "")) + localCurrencyCellModel = localCurrencyCell + updateLocalCurrencyCellModel() + localCurrencyCell.accessoryType = .disclosureIndicator + localCurrencyCell.didSelectBlock = { [weak self] _, _ in + self?.showCurrencySelector() + } + items.append(localCurrencyCell) + + let notificationsCell = DWSwitcherFormCellModel(title: NSLocalizedString("Enable Receive Notifications", comment: "")) + notificationsCell.isOn = model.notificationsEnabled + notificationsCell.didChangeValueBlock = { [weak self] cellModel in + self?.model.notificationsEnabled = cellModel.isOn + } + items.append(notificationsCell) + + let networkCell = DWSelectorFormCellModel(title: NSLocalizedString("Network", comment: "")) + switchNetworkCellModel = networkCell + updateSwitchNetworkCellModel() + networkCell.accessoryType = .disclosureIndicator + networkCell.didSelectBlock = { [weak self] _, indexPath in + guard let self = self else { return } + let tableView = self.formController.tableView! + guard let cell = tableView.cellForRow(at: indexPath) else { return } + self.showChangeNetwork(from: tableView, sourceRect: cell.frame) + } + items.append(networkCell) + + let rescanCell = DWSelectorFormCellModel(title: NSLocalizedString("Rescan Blockchain", comment: "")) + rescanCell.didSelectBlock = { [weak self] _, indexPath in + guard let self = self else { return } + let tableView = self.formController.tableView! + guard let cell = tableView.cellForRow(at: indexPath) else { return } + self.showWarningAboutReclassifiedTransactions(tableView, sourceRect: cell.frame) + } + items.append(rescanCell) + + let aboutCell = DWSelectorFormCellModel(title: NSLocalizedString("About", comment: "")) + aboutCell.accessoryType = .disclosureIndicator + aboutCell.didSelectBlock = { [weak self] _, _ in + self?.showAboutController() + } + items.append(aboutCell) + + #if DASHPAY + let coinJoinCell = DWSelectorFormCellModel(title: NSLocalizedString("CoinJoin", comment: "")) + coinJoinCell.didSelectBlock = { [weak self] _, _ in + self?.showCoinJoinController() + } + items.append(coinJoinCell) + + let votingCell = DWSwitcherFormCellModel(title: "Enable Voting") + votingCell.isOn = VotingPrefsWrapper.getIsEnabled() + votingCell.didChangeValueBlock = { cellModel in + VotingPrefsWrapper.setIsEnabled(value: cellModel.isOn) + } + items.append(votingCell) + #endif + + return items + } + + private var sections: [DWFormSectionModel] { + return items.map { item in + let section = DWFormSectionModel() + section.items = [item] + return section + } + } + + private func updateLocalCurrencyCellModel() { + localCurrencyCellModel.subTitle = model.localCurrencyCode + } + + private func updateSwitchNetworkCellModel() { + switchNetworkCellModel.subTitle = model.networkName + } + + private func rescanBlockchainAction(from sourceView: UIView, sourceRect: CGRect) { + DWSettingsMenuModel.rescanBlockchainAction(from: self, sourceView: sourceView, sourceRect: sourceRect) { [weak self] confirmed in + if confirmed { + self?.delegate?.settingsMenuViewControllerDidRescanBlockchain(self!) + } + } + } + + private func showCurrencySelector() { + let controller = DWLocalCurrencyViewController(navigationAppearance: .default, presentationMode: .screen, currencyCode: nil) + controller.delegate = self + navigationController?.pushViewController(controller, animated: true) + } + + private func showAboutController() { + let aboutViewController = DWAboutViewController.create() + navigationController?.pushViewController(aboutViewController, animated: true) + } + + private func showCoinJoinController() { + let vc: UIViewController + + if CoinJoinViewModel.shared.infoShown { + vc = CoinJoinLevelsViewController.controller() + } else { + vc = CoinJoinInfoViewController.controller() + } + + navigationController?.pushViewController(vc, animated: true) + } + + private func showChangeNetwork(from sourceView: UIView, sourceRect: CGRect) { + let actionSheet = UIAlertController(title: NSLocalizedString("Network", comment: ""), message: nil, preferredStyle: .actionSheet) + + let mainnetAction = UIAlertAction(title: NSLocalizedString("Mainnet", comment: ""), style: .default) { [weak self] _ in + DWSettingsMenuModel.switchToMainnet { success in + if success { + self?.updateSwitchNetworkCellModel() + } + } + } + + let testnetAction = UIAlertAction(title: NSLocalizedString("Testnet", comment: ""), style: .default) { [weak self] _ in + DWSettingsMenuModel.switchToTestnet { success in + if success { + self?.updateSwitchNetworkCellModel() + } + } + } + + let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil) + + actionSheet.addAction(mainnetAction) + actionSheet.addAction(testnetAction) + actionSheet.addAction(cancelAction) + + if UIDevice.current.userInterfaceIdiom == .pad { + actionSheet.popoverPresentationController?.sourceView = sourceView + actionSheet.popoverPresentationController?.sourceRect = sourceRect + } + + present(actionSheet, animated: true, completion: nil) + } + + private func showWarningAboutReclassifiedTransactions(_ sourceView: UIView, sourceRect: CGRect) { + let actionSheet = UIAlertController( + title: NSLocalizedString("You will lose all your manually reclassified transactions types", comment: ""), + message: NSLocalizedString("If you would like to save manually reclassified types for transactions you should export a CSV transaction file.", comment: ""), + preferredStyle: .actionSheet) + + let continueAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { [weak self] _ in + self?.rescanBlockchainAction(from: sourceView, sourceRect: sourceRect) + } + + let exportAction = UIAlertAction(title: NSLocalizedString("Export CSV", comment: ""), style: .default) { [weak self] _ in + self?.exportTransactionsInCSV() + } + + let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil) + + actionSheet.addAction(exportAction) + actionSheet.addAction(continueAction) + actionSheet.addAction(cancelAction) + + if UIDevice.current.userInterfaceIdiom == .pad { + actionSheet.popoverPresentationController?.sourceView = sourceView + actionSheet.popoverPresentationController?.sourceRect = sourceRect + } + + present(actionSheet, animated: true, completion: nil) + } + + private func exportTransactionsInCSV() { + view.dw_showProgressHUD(withMessage: NSLocalizedString("Generating CSV Report", comment: "")) + + DWSettingsMenuModel.generateCSVReport { [weak self] fileName, file in + self?.view.dw_hideProgressHUD() + + let activityViewController = UIActivityViewController(activityItems: [file], applicationActivities: nil) + activityViewController.setValue(fileName, forKey: "subject") + + if UIDevice.current.userInterfaceIdiom == .pad { + activityViewController.popoverPresentationController?.sourceView = self?.view + activityViewController.popoverPresentationController?.sourceRect = self?.view.bounds ?? .zero + } + + self?.present(activityViewController, animated: true, completion: nil) + } errorHandler: { [weak self] error in + self?.view.dw_hideProgressHUD() + self?.dw_displayErrorModally(error) + } + } +} diff --git a/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift b/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift index 4060f0a0d..257901e2a 100644 --- a/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift +++ b/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift @@ -21,7 +21,7 @@ typealias TransactionPreview = MenuItem struct MenuItem: View { var title: String - var subtitle: String? = nil + var subtitleView: AnyView? = nil var details: String? = nil var topText: String? = nil var icon: IconName? = nil @@ -32,6 +32,70 @@ struct MenuItem: View { var overrideFiatAmount: String? = nil var isToggled: Binding? = nil var action: (() -> Void)? = nil + + init(title: String, + subtitle: String? = nil, + details: String? = nil, + topText: String? = nil, + icon: IconName? = nil, + secondaryIcon: IconName? = nil, + showInfo: Bool = false, + showChevron: Bool = false, + dashAmount: Int64? = nil, + overrideFiatAmount: String? = nil, + isToggled: Binding? = nil, + action: (() -> Void)? = nil + ) { + self.init(title: title, + subtitleView: subtitle.map { + AnyView( + Text($0) + .font(.caption) + .lineSpacing(3) + .foregroundColor(.tertiaryText) + .padding(.leading, 4) + .padding(.top, 2) + ) + }, + details: details, + topText: topText, + icon: icon, + secondaryIcon: secondaryIcon, + showInfo: showInfo, + showChevron: showChevron, + dashAmount: dashAmount, + overrideFiatAmount: overrideFiatAmount, + isToggled: isToggled, + action: action + ) + } + + init(title: String, + subtitleView: AnyView? = nil, + details: String? = nil, + topText: String? = nil, + icon: IconName? = nil, + secondaryIcon: IconName? = nil, + showInfo: Bool = false, + showChevron: Bool = false, + dashAmount: Int64? = nil, + overrideFiatAmount: String? = nil, + isToggled: Binding? = nil, + action: (() -> Void)? = nil + ) { + self.title = title + self.subtitleView = subtitleView + self.details = details + self.topText = topText + self.icon = icon + self.secondaryIcon = secondaryIcon + self.showInfo = showInfo + self.showChevron = showChevron + self.dashAmount = dashAmount + self.overrideFiatAmount = overrideFiatAmount + self.isToggled = isToggled + self.action = action + } var body: some View { HStack(spacing: 4) { @@ -85,13 +149,8 @@ struct MenuItem: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.leading, 4) - if let subtitle = subtitle { - Text(subtitle) - .font(.caption) - .lineSpacing(3) - .foregroundColor(.tertiaryText) - .padding(.leading, 4) - .padding(.top, 2) + if let subtitle = subtitleView { + subtitle } if let details = details { diff --git a/DashWallet/dashwallet-Bridging-Header.h b/DashWallet/dashwallet-Bridging-Header.h index edbaa365a..95602335d 100644 --- a/DashWallet/dashwallet-Bridging-Header.h +++ b/DashWallet/dashwallet-Bridging-Header.h @@ -161,8 +161,10 @@ static const bool _SNAPSHOT = 0; #import "DWBasePayViewController.h" #import "DWHomeProtocol.h" -//MARK: Tools menu +//MARK: Settings menu #import "UIViewController+DWDisplayError.h" +#import "DWFormTableViewController.h" +#import "DWAboutViewController.h" //MARK: Onboarding #import "DWTransactionStub.h" From be7bc3b459b9f7cfa77b4d491113f0e077468ead Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Sun, 1 Sep 2024 20:53:44 +0700 Subject: [PATCH 3/5] chore: moving SettingsMenu to SwiftUI --- DashWallet.xcodeproj/project.pbxproj | 6 + .../Sources/UI/Menu/MenuItemModel.swift | 36 +++ .../Settings/SettingsMenuViewController.swift | 225 ++++++++++-------- .../Menu/Tools/ToolsMenuViewController.swift | 17 -- .../UI/SwiftUI Components/MenuItem.swift | 71 +++--- 5 files changed, 210 insertions(+), 145 deletions(-) create mode 100644 DashWallet/Sources/UI/Menu/MenuItemModel.swift diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index a64fa4b4a..8e8f52308 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -558,6 +558,8 @@ 754BEA132C0B6BD700E8C93C /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754BEA112C0B6BD700E8C93C /* HomeViewModel.swift */; }; 755049A92C846299008FA7EB /* DWAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 755049A72C846299008FA7EB /* DWAboutViewController.m */; }; 755049AA2C846299008FA7EB /* DWAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 755049A72C846299008FA7EB /* DWAboutViewController.m */; }; + 755049AC2C846576008FA7EB /* MenuItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755049AB2C846576008FA7EB /* MenuItemModel.swift */; }; + 755049AD2C846576008FA7EB /* MenuItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755049AB2C846576008FA7EB /* MenuItemModel.swift */; }; 755A22BD2B1385FD001F170D /* IconAttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755A22BC2B1385FD001F170D /* IconAttributedText.swift */; }; 755B4B222B0C903500B844F0 /* DWDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755B4B212B0C903500B844F0 /* DWDateFormatter.swift */; }; 755B4B232B0C903500B844F0 /* DWDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755B4B212B0C903500B844F0 /* DWDateFormatter.swift */; }; @@ -2432,6 +2434,7 @@ 754BEA112C0B6BD700E8C93C /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; 755049A72C846299008FA7EB /* DWAboutViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAboutViewController.m; sourceTree = ""; }; 755049A82C846299008FA7EB /* DWAboutViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAboutViewController.h; sourceTree = ""; }; + 755049AB2C846576008FA7EB /* MenuItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemModel.swift; sourceTree = ""; }; 755A22BC2B1385FD001F170D /* IconAttributedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconAttributedText.swift; sourceTree = ""; }; 755B4B212B0C903500B844F0 /* DWDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DWDateFormatter.swift; sourceTree = ""; }; 755C32372C358FBD007DA721 /* BackupSeedPhraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSeedPhraseViewController.swift; sourceTree = ""; }; @@ -4122,6 +4125,7 @@ 2A7A7BCA2347EF7A00451078 /* Security */, 2A7A7BD32348CB4F00451078 /* Settings */, 2A7A7BDA2348DBE700451078 /* Tools */, + 755049AB2C846576008FA7EB /* MenuItemModel.swift */, ); path = Menu; sourceTree = ""; @@ -8943,6 +8947,7 @@ C9F42FB829DFC507001BC549 /* SpendableTransaction.swift in Sources */, 11ED906B29681773003784F9 /* StakingInfoDialogController.swift in Sources */, 0F36937E2919A5DB007F4E91 /* TwoFactorAuthViewController.swift in Sources */, + 755049AC2C846576008FA7EB /* MenuItemModel.swift in Sources */, 75EBAA292BBBE385004488E3 /* ZenLedgerViewModel.swift in Sources */, 471A260A289ACDF70056B7B2 /* Taxes.swift in Sources */, 2AD1CE9722DD0E8E00C99324 /* DWSeedWordModel+DWLayoutSupport.m in Sources */, @@ -9176,6 +9181,7 @@ C9D2C7022A320AA000D15901 /* CrowdNodeWebViewController.swift in Sources */, C9D2C7032A320AA000D15901 /* UIViewController+DWDisplayError.m in Sources */, C9D2C7042A320AA000D15901 /* BRAppleWatchData.m in Sources */, + 755049AD2C846576008FA7EB /* MenuItemModel.swift in Sources */, C9D2C7052A320AA000D15901 /* DWRequestAmountContentView.m in Sources */, C943B3312A408CED00AF23C5 /* DWProfileAboutCellModel.m in Sources */, C943B4C42A40A54600AF23C5 /* DWSearchViewController.m in Sources */, diff --git a/DashWallet/Sources/UI/Menu/MenuItemModel.swift b/DashWallet/Sources/UI/Menu/MenuItemModel.swift new file mode 100644 index 000000000..f58c385fe --- /dev/null +++ b/DashWallet/Sources/UI/Menu/MenuItemModel.swift @@ -0,0 +1,36 @@ +// +// Created by Andrei Ashikhmin +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct MenuItemModel: Identifiable, Equatable { + let id = UUID() + + var title: String + var subtitle: String? = nil + var details: String? = nil + var icon: IconName? = nil + var showInfo: Bool = false + var showChevron: Bool = false + var showToggle: Bool = false + @State var isToggled: Bool = false + var action: (() -> Void)? = nil + + static func == (lhs: MenuItemModel, rhs: MenuItemModel) -> Bool { + lhs.id == rhs.id + } +} diff --git a/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift index 3ca927f37..4ba781626 100644 --- a/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift +++ b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift @@ -16,6 +16,7 @@ // import UIKit +import SwiftUI @objc(DWSettingsMenuViewControllerDelegate) protocol SettingsMenuViewControllerDelegate: AnyObject { @@ -28,9 +29,6 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle @objc weak var delegate: SettingsMenuViewControllerDelegate? private lazy var model: DWSettingsMenuModel = DWSettingsMenuModel() - private var formController: DWFormTableViewController! - private var localCurrencyCellModel: DWSelectorFormCellModel! - private var switchNetworkCellModel: DWSelectorFormCellModel! init() { super.init(nibName: nil, bundle: nil) @@ -47,10 +45,21 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle view.backgroundColor = .dw_secondaryBackground() - formController = DWFormTableViewController(style: .plain) - formController.setSections(sections, placeholderText: nil) - - dw_embedChild(formController) + let content = SettingsMenuContent( + items: menuItems(), + onLocalCurrencyChange: { [weak self] in + self?.showCurrencySelector() + }, + onNetworkChange: { [weak self] in + self?.showChangeNetwork() + }, + onRescanBlockchain: { [weak self] in + self?.showWarningAboutReclassifiedTransactions() + } + ) + let swiftUIController = UIHostingController(rootView: content) + swiftUIController.view.backgroundColor = .dw_secondaryBackground() + dw_embedChild(swiftUIController) } override var preferredStatusBarStyle: UIStatusBarStyle { @@ -60,7 +69,6 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle // MARK: - LocalCurrencyViewControllerDelegate func localCurrencyViewController(_ controller: DWLocalCurrencyViewController, didSelectCurrency currencyCode: String) { - updateLocalCurrencyCellModel() navigationController?.popViewController(animated: true) } @@ -70,95 +78,71 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle // MARK: - Private - private var items: [DWBaseFormCellModel] { - var items: [DWBaseFormCellModel] = [] - - let localCurrencyCell = DWSelectorFormCellModel(title: NSLocalizedString("Local Currency", comment: "")) - localCurrencyCellModel = localCurrencyCell - updateLocalCurrencyCellModel() - localCurrencyCell.accessoryType = .disclosureIndicator - localCurrencyCell.didSelectBlock = { [weak self] _, _ in - self?.showCurrencySelector() - } - items.append(localCurrencyCell) - - let notificationsCell = DWSwitcherFormCellModel(title: NSLocalizedString("Enable Receive Notifications", comment: "")) - notificationsCell.isOn = model.notificationsEnabled - notificationsCell.didChangeValueBlock = { [weak self] cellModel in - self?.model.notificationsEnabled = cellModel.isOn - } - items.append(notificationsCell) - - let networkCell = DWSelectorFormCellModel(title: NSLocalizedString("Network", comment: "")) - switchNetworkCellModel = networkCell - updateSwitchNetworkCellModel() - networkCell.accessoryType = .disclosureIndicator - networkCell.didSelectBlock = { [weak self] _, indexPath in - guard let self = self else { return } - let tableView = self.formController.tableView! - guard let cell = tableView.cellForRow(at: indexPath) else { return } - self.showChangeNetwork(from: tableView, sourceRect: cell.frame) - } - items.append(networkCell) - - let rescanCell = DWSelectorFormCellModel(title: NSLocalizedString("Rescan Blockchain", comment: "")) - rescanCell.didSelectBlock = { [weak self] _, indexPath in - guard let self = self else { return } - let tableView = self.formController.tableView! - guard let cell = tableView.cellForRow(at: indexPath) else { return } - self.showWarningAboutReclassifiedTransactions(tableView, sourceRect: cell.frame) - } - items.append(rescanCell) - - let aboutCell = DWSelectorFormCellModel(title: NSLocalizedString("About", comment: "")) - aboutCell.accessoryType = .disclosureIndicator - aboutCell.didSelectBlock = { [weak self] _, _ in - self?.showAboutController() - } - items.append(aboutCell) + private func menuItems() -> [MenuItemModel] { + var items: [MenuItemModel] = [ + MenuItemModel( + title: NSLocalizedString("Local Currency", comment: ""), + subtitle: model.localCurrencyCode, + showChevron: true, + action: { [weak self] in + self?.showCurrencySelector() + } + ), + MenuItemModel( + title: NSLocalizedString("Enable Receive Notifications", comment: ""), + showToggle: true, + isToggled: self.model.notificationsEnabled, + action: { [weak self] in + self?.model.notificationsEnabled.toggle() + } + ), + MenuItemModel( + title: NSLocalizedString("Network", comment: ""), + subtitle: model.networkName, + showChevron: true, + action: { [weak self] in + self?.showChangeNetwork() + } + ), + MenuItemModel( + title: NSLocalizedString("Rescan Blockchain", comment: ""), + showChevron: true, + action: { [weak self] in + self?.showWarningAboutReclassifiedTransactions() + } + ), + MenuItemModel( + title: NSLocalizedString("About", comment: ""), + showChevron: true, + action: { [weak self] in + self?.showAboutController() + } + ) + ] #if DASHPAY - let coinJoinCell = DWSelectorFormCellModel(title: NSLocalizedString("CoinJoin", comment: "")) - coinJoinCell.didSelectBlock = { [weak self] _, _ in - self?.showCoinJoinController() - } - items.append(coinJoinCell) - - let votingCell = DWSwitcherFormCellModel(title: "Enable Voting") - votingCell.isOn = VotingPrefsWrapper.getIsEnabled() - votingCell.didChangeValueBlock = { cellModel in - VotingPrefsWrapper.setIsEnabled(value: cellModel.isOn) - } - items.append(votingCell) + items.append(contentsOf: [ + MenuItemModel( + title: NSLocalizedString("CoinJoin", comment: ""), + showChevron: true, + action: { [weak self] in + self?.showCoinJoinController() + } + ), + MenuItemModel( + title: "Enable Voting", + showToggle: true, + isToggled: VotingPrefs.shared.votingEnabled, + action: { + VotingPrefs.shared.votingEnabled.toggle() + } + ) + ]) #endif return items } - private var sections: [DWFormSectionModel] { - return items.map { item in - let section = DWFormSectionModel() - section.items = [item] - return section - } - } - - private func updateLocalCurrencyCellModel() { - localCurrencyCellModel.subTitle = model.localCurrencyCode - } - - private func updateSwitchNetworkCellModel() { - switchNetworkCellModel.subTitle = model.networkName - } - - private func rescanBlockchainAction(from sourceView: UIView, sourceRect: CGRect) { - DWSettingsMenuModel.rescanBlockchainAction(from: self, sourceView: sourceView, sourceRect: sourceRect) { [weak self] confirmed in - if confirmed { - self?.delegate?.settingsMenuViewControllerDidRescanBlockchain(self!) - } - } - } - private func showCurrencySelector() { let controller = DWLocalCurrencyViewController(navigationAppearance: .default, presentationMode: .screen, currencyCode: nil) controller.delegate = self @@ -182,13 +166,13 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle navigationController?.pushViewController(vc, animated: true) } - private func showChangeNetwork(from sourceView: UIView, sourceRect: CGRect) { + private func showChangeNetwork() { let actionSheet = UIAlertController(title: NSLocalizedString("Network", comment: ""), message: nil, preferredStyle: .actionSheet) let mainnetAction = UIAlertAction(title: NSLocalizedString("Mainnet", comment: ""), style: .default) { [weak self] _ in DWSettingsMenuModel.switchToMainnet { success in if success { - self?.updateSwitchNetworkCellModel() + self?.updateView() } } } @@ -196,7 +180,7 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle let testnetAction = UIAlertAction(title: NSLocalizedString("Testnet", comment: ""), style: .default) { [weak self] _ in DWSettingsMenuModel.switchToTestnet { success in if success { - self?.updateSwitchNetworkCellModel() + self?.updateView() } } } @@ -208,21 +192,21 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle actionSheet.addAction(cancelAction) if UIDevice.current.userInterfaceIdiom == .pad { - actionSheet.popoverPresentationController?.sourceView = sourceView - actionSheet.popoverPresentationController?.sourceRect = sourceRect + actionSheet.popoverPresentationController?.sourceView = view + actionSheet.popoverPresentationController?.sourceRect = view.bounds } present(actionSheet, animated: true, completion: nil) } - private func showWarningAboutReclassifiedTransactions(_ sourceView: UIView, sourceRect: CGRect) { + private func showWarningAboutReclassifiedTransactions() { let actionSheet = UIAlertController( title: NSLocalizedString("You will lose all your manually reclassified transactions types", comment: ""), message: NSLocalizedString("If you would like to save manually reclassified types for transactions you should export a CSV transaction file.", comment: ""), preferredStyle: .actionSheet) let continueAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { [weak self] _ in - self?.rescanBlockchainAction(from: sourceView, sourceRect: sourceRect) + self?.rescanBlockchainAction() } let exportAction = UIAlertAction(title: NSLocalizedString("Export CSV", comment: ""), style: .default) { [weak self] _ in @@ -236,13 +220,21 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle actionSheet.addAction(cancelAction) if UIDevice.current.userInterfaceIdiom == .pad { - actionSheet.popoverPresentationController?.sourceView = sourceView - actionSheet.popoverPresentationController?.sourceRect = sourceRect + actionSheet.popoverPresentationController?.sourceView = view + actionSheet.popoverPresentationController?.sourceRect = view.bounds } present(actionSheet, animated: true, completion: nil) } + private func rescanBlockchainAction() { + DWSettingsMenuModel.rescanBlockchainAction(from: self, sourceView: view, sourceRect: view.bounds) { [weak self] confirmed in + if confirmed { + self?.delegate?.settingsMenuViewControllerDidRescanBlockchain(self!) + } + } + } + private func exportTransactionsInCSV() { view.dw_showProgressHUD(withMessage: NSLocalizedString("Generating CSV Report", comment: "")) @@ -263,4 +255,39 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle self?.dw_displayErrorModally(error) } } + + private func updateView() { + // Trigger a view update + viewDidLoad() + } +} + +struct SettingsMenuContent: View { + var items: [MenuItemModel] + var onLocalCurrencyChange: () -> Void + var onNetworkChange: () -> Void + var onRescanBlockchain: () -> Void + + var body: some View { + List(items) { item in + MenuItem( + title: item.title, + subtitle: item.subtitle, + details: item.details, + icon: item.icon, + showInfo: item.showInfo, + showChevron: item.showChevron, + showToggle: item.showToggle, + isToggled: item.isToggled, + action: item.action + ) + .background(Color.secondaryBackground) + .cornerRadius(8) + .shadow(color: .shadow, radius: 10, x: 0, y: 5) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } + .listStyle(.plain) + .background(Color.clear) + } } diff --git a/DashWallet/Sources/UI/Menu/Tools/ToolsMenuViewController.swift b/DashWallet/Sources/UI/Menu/Tools/ToolsMenuViewController.swift index a0fc3e770..92b14f481 100644 --- a/DashWallet/Sources/UI/Menu/Tools/ToolsMenuViewController.swift +++ b/DashWallet/Sources/UI/Menu/Tools/ToolsMenuViewController.swift @@ -167,23 +167,6 @@ extension DWImportWalletInfoViewController { } } -struct MenuItemModel: Identifiable, Equatable { - let id = UUID() - - var title: String - var subtitle: String? = nil - var details: String? = nil - var icon: IconName? = nil - var showInfo: Bool = false - var showChevron: Bool = false - var isToggled: Binding? = nil - var action: (() -> Void)? = nil - - static func == (lhs: MenuItemModel, rhs: MenuItemModel) -> Bool { - lhs.id == rhs.id - } -} - struct ToolsMenuContent: View { var items: [MenuItemModel] @State private var showZenLedgerSheet: Bool = false diff --git a/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift b/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift index 257901e2a..136d0b5f5 100644 --- a/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift +++ b/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift @@ -30,7 +30,8 @@ struct MenuItem: View { var showChevron: Bool = false var dashAmount: Int64? = nil var overrideFiatAmount: String? = nil - var isToggled: Binding? = nil + var showToggle: Bool = false + @State var isToggled: Bool = false var action: (() -> Void)? = nil init(title: String, @@ -43,30 +44,33 @@ struct MenuItem: View { showChevron: Bool = false, dashAmount: Int64? = nil, overrideFiatAmount: String? = nil, - isToggled: Binding? = nil, + showToggle: Bool = false, + isToggled: Bool = false, action: (() -> Void)? = nil ) { - self.init(title: title, - subtitleView: subtitle.map { - AnyView( - Text($0) - .font(.caption) - .lineSpacing(3) - .foregroundColor(.tertiaryText) - .padding(.leading, 4) - .padding(.top, 2) - ) - }, - details: details, - topText: topText, - icon: icon, - secondaryIcon: secondaryIcon, - showInfo: showInfo, - showChevron: showChevron, - dashAmount: dashAmount, - overrideFiatAmount: overrideFiatAmount, - isToggled: isToggled, - action: action + self.init( + title: title, + subtitleView: subtitle.map { + AnyView( + Text($0) + .font(.caption) + .lineSpacing(3) + .foregroundColor(.tertiaryText) + .padding(.leading, 4) + .padding(.top, 2) + ) + }, + details: details, + topText: topText, + icon: icon, + secondaryIcon: secondaryIcon, + showInfo: showInfo, + showChevron: showChevron, + dashAmount: dashAmount, + overrideFiatAmount: overrideFiatAmount, + showToggle: showToggle, + isToggled: isToggled, + action: action ) } @@ -80,7 +84,8 @@ struct MenuItem: View { showChevron: Bool = false, dashAmount: Int64? = nil, overrideFiatAmount: String? = nil, - isToggled: Binding? = nil, + showToggle: Bool = false, + isToggled: Bool = false, action: (() -> Void)? = nil ) { self.title = title @@ -93,7 +98,8 @@ struct MenuItem: View { self.showChevron = showChevron self.dashAmount = dashAmount self.overrideFiatAmount = overrideFiatAmount - self.isToggled = isToggled + self._isToggled = State(initialValue: isToggled) + self.showToggle = showToggle self.action = action } @@ -152,7 +158,7 @@ struct MenuItem: View { if let subtitle = subtitleView { subtitle } - + if let details = details { Text(details) .font(.caption) @@ -164,8 +170,10 @@ struct MenuItem: View { } .frame(maxWidth: .infinity) - if let isToggled = isToggled { - Toggle(isOn: isToggled) { } + if showToggle { + Toggle(isOn: $isToggled) { } + .tint(Color.dashBlue) + .scaleEffect(0.75) .frame(maxWidth: 60) } @@ -196,6 +204,10 @@ struct MenuItem: View { .padding(10) .frame(maxWidth: .infinity, minHeight: 66) .onTapGesture { + if showToggle { + isToggled.toggle() + } + action?() } } @@ -216,6 +228,7 @@ struct MenuItem: View { subtitle: "Easily stake Dash and earn passive income with a few simple steps", icon: .system("faceid"), showInfo: true, - isToggled: .constant(true) + showToggle: true, + isToggled: true ) } From 1354fdf469f7c1cddbb12c7f831607bd97ef879e Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Sun, 1 Sep 2024 21:47:14 +0700 Subject: [PATCH 4/5] feat: coinjoin menu item UI --- .../Contents.json | 23 +++++++ .../image.coinjoin.menu.png | Bin 0 -> 645 bytes .../image.coinjoin.menu@2x.png | Bin 0 -> 1226 bytes .../image.coinjoin.menu@3x.png | Bin 0 -> 1587 bytes .../Sources/UI/Menu/MenuItemModel.swift | 25 ++++++- .../Settings/SettingsMenuViewController.swift | 65 ++++++++++++++---- 6 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/Contents.json create mode 100644 DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/image.coinjoin.menu.png create mode 100644 DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/image.coinjoin.menu@2x.png create mode 100644 DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/image.coinjoin.menu@3x.png diff --git a/DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/Contents.json b/DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/Contents.json new file mode 100644 index 000000000..f381203f8 --- /dev/null +++ b/DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "image.coinjoin.menu.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "image.coinjoin.menu@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "image.coinjoin.menu@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/image.coinjoin.menu.png b/DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/image.coinjoin.menu.png new file mode 100644 index 0000000000000000000000000000000000000000..f65f5980d1a2907d1968c626d70ad3d370f55be6 GIT binary patch literal 645 zcmV;00($+4P)o=gETM0A(?(s6 z2+6!9*UneZDW6RgkfTbv3dc9R1lfzW6($@7_c9n&mqQB`rYuEu%9OiN4BJV&KJ%H* zT|ERBRPmF2$6!;`gL6|&QOOV1^Nl;}$L>(!OuRLH`wYc)onEbe)x@qx)js-Lbw<_z z=83I;whRw6&k? zUDz7ZBIE(1n3_!RH`zvdmm{%t)V2?MJZ}}#9h#(}Sh-S-ch3;Q>%-Xx_uKEBxZlyA z++9wLo<>qloGU`yNvk$$$FC)pGCv2r(oLMX8p}G~J z#S!hL)gd;4_4uc)K`pe6O-Ng0F`( fzPZa>II4wz-p%8j*TjJH00000NkvXXu0mjforxj_ literal 0 HcmV?d00001 diff --git a/DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/image.coinjoin.menu@2x.png b/DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/image.coinjoin.menu@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d13f537c3ab82bfca430f67e4e04b9b77634b77e GIT binary patch literal 1226 zcmV;*1U37KP) z(dheYy28w zMI`(gnc@8Fn+w|A?;%~m~bSIeFc7kyCs*y zOLBdPCFHv&C+Pczdo_Y@r+vu02!Ud)1MVR6lUNJ=brQH_$aS(Ah7NGH)XZfBI$7$HgdEob8P`ito{k2&O$;B`DD|7$O9Q{0|XAZb)aAun$BN1VIh* zC^~}1gbl5rt!6c)Nk(#;wncREC}u-U`v`@Bzpjh|aT)Ot73NT>tv{K(PydU(d%gsE!-eC5u$M-BSpO7kh{E5iK@ky z=##y+m0uT^+F46*OQi0_RLC0J3e~?g2*J9>k)*n}gm}Ybp`KSaS4Z|@=6!Fm)6S^6 zJ!U#{CDpKtcS-j|JIRb3QO;JMD|Yv?b}YJ*SY>i1y+Iv#_*n-Z+0xS?>XATpWT0=E zEFJpm$b3|PaL2thU35=$G9z`w|21tTILWpXYP;vf70b&V?}&nyt!{$b_;gWl@o>ok?Up<7i(<)&2-Ce)u&30SK80r)!N2it%}0xVTJ6iV za%m$`4~5g$+uY5BH`LbVuR{#1jV_m0YG=!@hdz)Kc}Gys<&DFln3|}j(@y=~V;&(C zazuihptbLTQB?Y0@BR2*GCM+@b{c(z5XcWBw|&W>B?uBTM9isIWC^pte-3tZPKpcv2FgNkBjLf zGo`zOY)MbM{qpVV!=qi2tRN?-wp-F5^fcfpkY`ko6Eq)S%6ZLJ>u3WcTg6eI)t_Al z9s%<@n$Cxdwf1SmW_AnEW?3s9pHx=g1eRzz3fz%BiP*B)wy>JX7E38{)}nFIVwrO$ oShlElY`NI~@w$4p-M=Az0=3r6s?3S6S^xk507*qoM6N<$f+9#yU;qFB literal 0 HcmV?d00001 diff --git a/DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/image.coinjoin.menu@3x.png b/DashWallet/Resources/AppAssets.xcassets/CoinJoin/image.coinjoin.menu.imageset/image.coinjoin.menu@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..1949a1f54701c09b56c6859f0b2f78eafb497962 GIT binary patch literal 1587 zcmV-32F&@1P)Ro60s z&H$n)Qv3lx$UkpkStK6Grx^e<0}x?T7=G&lM17FrC4K7x!jAq_(95+$i0N+(3TFWF z4-hZ`xShhu^BHUa2qB?GJ~;xV`XsCmZGa?P!DkTB1Wta6ArQg{9)2f*c-C5EHO3&+ zXE=E_ftF||c=JN@V5?(v*v8_1g0<^*&OpL3+z-g&(YcRscfo8`iC=&Rp!-D(QXRsn zo0|)l#xVRrR>f<$f3b`n2znNNCI{GA*o?kI`b7uuFfoQ-w-0R^HVbC4N#}Yok3L)= zOabS6P4joX;Kw31g@x$)J}jS%9*tOrqGy$zVO^7I!Fn)I2k`hXs2j-2UHZdc2Y})W zJZ4C>S2ks>3m*Q{1DWvD(*qwM%pg&}(SH-T-HKuBwnxVfXbRb1OB{+Eg_n>-Gb>Ir zVi5D)Vt-y2eDPyMt~>xAaOQ*2UTFlxKcu1`vMgJ2<=EpBO;;!R+ZAshlW*V)5k0RB z_ssRbLK?WRWii@y;<%#V+WA3CB_r_1xG}cX=e4^p?gy7v9HdOi7ZK(LuuQPdd|G5Z zN?zHuQ`6=D(o%?26YvFVdQaLG$OKFA8rnctEG!U-7+euqUb~vVw6YP*{RCRZ$pU+J z4OP|`QQ7s&MEUjK{%cKu&fnet$tDc0B9S5U*XbagH@P`p>`>=H=^p#Q! zZ+7yYM3G!0htCJd(h&B|2b)@U{)I5$zIH-$-+1jzb>Uz}gVL+gL_Tz}LCl}x;-c3O z6cjOm`knlT#OM<3e)A0A!*PU?ueRvoeeeZpi>A{|FN=tD(s4l7UVIJ8*`afO7DwFK zB0ImxU)_etu`is%>Gr4G@r#=QojWTH1(~DB-QN`4y`B@(2)$mnVh%YxIJ-Ljg7ER? z+R1gA!ojlmw=P~zImPhNhtvG*Qy!&z-~zT%T{y_+*eZF>c_T+jcMeb+7f@F~at{}O zp3w;W)T-sh4Uha?FAiJx&GvIAjwRg6vHD%5E3P+ULD6Q2#g?LdMI?Ent$C8kg|G#nvZkdTLi*hCFkwW zm!%vZutw5q=3(PXEupUT1J;3s@y?tKRNCF1HgZB3WN}|3Sj@-J8hB~fPG;?_OS|A- zmrlXkpCA<+!mXu>t=dJM%%ZlXU153cYAhECvd}V7ZmCug)@9n;tleFeX;%vVnc%j4 z1(jV)n=8|z9j8s1mNiwbEw!<-!q>H-Aj1^=``H0Jvg8cF7rc>e1$n`G6kS>Da)SvU zs|1vWJ{Jj%)@zK%G8&XWQJp z)WJ!)5W^PpbQn}Ev^BuB5O0!lp3p<2Y`)-V5a+wDVj14WEDf_#IJ(o9JQD4~6YxYk z!6`U0LmX^uPzxY5yHrS+V85@0hj~%!N>^h7zNHpc@GA6f`lxSmUh!g)?Dr~O#pB%p lG}o&?b=f#dCYyQ{?|&@P(`L^TwqXDO002ovPDHLkV1j1m@{j-k literal 0 HcmV?d00001 diff --git a/DashWallet/Sources/UI/Menu/MenuItemModel.swift b/DashWallet/Sources/UI/Menu/MenuItemModel.swift index f58c385fe..fcf3993ba 100644 --- a/DashWallet/Sources/UI/Menu/MenuItemModel.swift +++ b/DashWallet/Sources/UI/Menu/MenuItemModel.swift @@ -17,7 +17,7 @@ import SwiftUI -struct MenuItemModel: Identifiable, Equatable { +class MenuItemModel: Identifiable, Equatable { let id = UUID() var title: String @@ -30,7 +30,30 @@ struct MenuItemModel: Identifiable, Equatable { @State var isToggled: Bool = false var action: (() -> Void)? = nil + init(title: String, subtitle: String? = nil, details: String? = nil, icon: IconName? = nil, showInfo: Bool = false, showChevron: Bool = false, showToggle: Bool = false, isToggled: Bool = false, action: (() -> Void)? = nil) { + self.title = title + self.subtitle = subtitle + self.details = details + self.icon = icon + self.showInfo = showInfo + self.showChevron = showChevron + self.showToggle = showToggle + self.isToggled = isToggled + self.action = action + } + static func == (lhs: MenuItemModel, rhs: MenuItemModel) -> Bool { lhs.id == rhs.id } } + +class CoinJoinMenuItemModel: MenuItemModel { + var mixingPercentage: String + var dashAmount: String + + init(title: String, mixingPercentage: String, dashAmount: String, action: (() -> Void)? = nil) { + self.mixingPercentage = mixingPercentage + self.dashAmount = dashAmount + super.init(title: title, action: action) + } +} diff --git a/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift index 4ba781626..33ab4c986 100644 --- a/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift +++ b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift @@ -122,9 +122,10 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle #if DASHPAY items.append(contentsOf: [ - MenuItemModel( + CoinJoinMenuItemModel( title: NSLocalizedString("CoinJoin", comment: ""), - showChevron: true, + mixingPercentage: "70%", + dashAmount: "0.085 of 0.199", action: { [weak self] in self?.showCoinJoinController() } @@ -270,17 +271,28 @@ struct SettingsMenuContent: View { var body: some View { List(items) { item in - MenuItem( - title: item.title, - subtitle: item.subtitle, - details: item.details, - icon: item.icon, - showInfo: item.showInfo, - showChevron: item.showChevron, - showToggle: item.showToggle, - isToggled: item.isToggled, - action: item.action - ) + Group { + if let cjItem = item as? CoinJoinMenuItemModel { + MenuItem( + title: cjItem.title, + subtitleView: AnyView(CoinJoinSubtitle(cjItem.mixingPercentage, cjItem.dashAmount)), + icon: .custom("image.coinjoin.menu"), + action: cjItem.action + ) + } else { + MenuItem( + title: item.title, + subtitle: item.subtitle, + details: item.details, + icon: item.icon, + showInfo: item.showInfo, + showChevron: item.showChevron, + showToggle: item.showToggle, + isToggled: item.isToggled, + action: item.action + ) + } + } .background(Color.secondaryBackground) .cornerRadius(8) .shadow(color: .shadow, radius: 10, x: 0, y: 5) @@ -290,4 +302,31 @@ struct SettingsMenuContent: View { .listStyle(.plain) .background(Color.clear) } + + @ViewBuilder + private func CoinJoinSubtitle(_ mixingPercentage: String, _ dashAmount: String) -> some View { + HStack(spacing: 0) { + SwiftUI.ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .dashBlue)) + .scaleEffect(0.5) + Text(NSLocalizedString("Mixing ·", comment: "CoinJoin")) + .font(.caption) + .foregroundColor(.tertiaryText) + .padding(.leading, 2) + Text(mixingPercentage) + .font(.caption) + .foregroundColor(.tertiaryText) + .padding(.leading, 4) + Spacer() + Text(dashAmount) + .font(.caption) + .foregroundColor(.tertiaryText) + Image("icon_dash_currency") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 12, height: 12) + .padding(.leading, 2) + .foregroundColor(.tertiaryText) + } + } } From 5ab861dc12ed874f7b42533e91448011a2d36f94 Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Mon, 16 Sep 2024 13:38:42 +0200 Subject: [PATCH 5/5] feat: coinjoin wallet integration & mixing info --- DashSyncCurrentCommit | 2 +- DashWallet.xcodeproj/project.pbxproj | 26 ++ .../Models/CoinJoin/CoinJoinService.swift | 298 ++++++++++++++++++ .../CoinJoinLevelsViewController.swift | 26 +- .../DashPay/CoinJoin/CoinJoinViewModel.swift | 56 ++-- .../Views/Cells/CoinJoinProgressView.swift | 98 ++++++ .../Sources/UI/Home/Views/HomeView.swift | 18 +- .../Sources/UI/Home/Views/HomeViewModel.swift | 74 ++++- .../Sources/UI/Menu/MenuItemModel.swift | 20 +- .../Settings/SettingsMenuViewController.swift | 154 +++------ .../UI/Menu/Settings/SettingsViewModel.swift | 134 ++++++++ .../UI/SwiftUI Components/MenuItem.swift | 4 +- DashWallet/dashwallet-Bridging-Header.h | 4 +- 13 files changed, 762 insertions(+), 152 deletions(-) create mode 100644 DashWallet/Sources/Models/CoinJoin/CoinJoinService.swift create mode 100644 DashWallet/Sources/UI/Home/Views/Cells/CoinJoinProgressView.swift create mode 100644 DashWallet/Sources/UI/Menu/Settings/SettingsViewModel.swift diff --git a/DashSyncCurrentCommit b/DashSyncCurrentCommit index 530482057..8de791cf2 100644 --- a/DashSyncCurrentCommit +++ b/DashSyncCurrentCommit @@ -1 +1 @@ -01e0c602fdb67517b9bb562fabbf8344f3aca636 +315879b4d157a026fc760d3742f6a55a8883fde5 diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 8e8f52308..3ca502a2e 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -520,6 +520,12 @@ 47FA3AFF29350929008D58DC /* SyncingActivityMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA3AFE29350929008D58DC /* SyncingActivityMonitor.swift */; }; 47FA3B0229364991008D58DC /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA3B0129364991008D58DC /* HTTPClient.swift */; }; 7502A4872AE401EF00ACDDD3 /* UsernameVotingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7502A4862AE401EF00ACDDD3 /* UsernameVotingViewController.swift */; }; + 7503643A2C89CFB70029EC0D /* CoinJoinProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750364392C89CFB70029EC0D /* CoinJoinProgressView.swift */; }; + 7503643B2C89CFB70029EC0D /* CoinJoinProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750364392C89CFB70029EC0D /* CoinJoinProgressView.swift */; }; + 7503643E2C89D49A0029EC0D /* CoinJoinService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7503643D2C89D49A0029EC0D /* CoinJoinService.swift */; }; + 7503643F2C89D49A0029EC0D /* CoinJoinService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7503643D2C89D49A0029EC0D /* CoinJoinService.swift */; }; + 750CED602C94BFD7000FB837 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750CED5F2C94BFD7000FB837 /* SettingsViewModel.swift */; }; + 750CED612C94BFD7000FB837 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750CED5F2C94BFD7000FB837 /* SettingsViewModel.swift */; }; 7513DA882AB175E0005D55F6 /* TopperViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7527720E2AA9F58E0066557E /* TopperViewModel.swift */; }; 7513DA892AB17606005D55F6 /* Topper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E2F3C92AA4D1B900C3B458 /* Topper.swift */; }; 7513DA8A2AB17666005D55F6 /* SupportedTopperAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7527720C2AA9B2630066557E /* SupportedTopperAssets.swift */; }; @@ -2405,9 +2411,12 @@ 5FD4C91FB8EAB529E8E41227 /* Pods-dashwallet no watch.testnet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashwallet no watch.testnet.xcconfig"; path = "Pods/Target Support Files/Pods-dashwallet no watch/Pods-dashwallet no watch.testnet.xcconfig"; sourceTree = ""; }; 6FBBFC90577C940D8C04E0B1 /* Pods-DashWalletScreenshotsUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashWalletScreenshotsUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DashWalletScreenshotsUITests/Pods-DashWalletScreenshotsUITests.debug.xcconfig"; sourceTree = ""; }; 7502A4862AE401EF00ACDDD3 /* UsernameVotingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsernameVotingViewController.swift; sourceTree = ""; }; + 750364392C89CFB70029EC0D /* CoinJoinProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinJoinProgressView.swift; sourceTree = ""; }; + 7503643D2C89D49A0029EC0D /* CoinJoinService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinJoinService.swift; sourceTree = ""; }; 7509C10E1AF3076100D03FD5 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 7509C1121AF3720100D03FD5 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 750C6CC01B5C8EB60038AAE9 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; + 750CED5F2C94BFD7000FB837 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; 7511E8CB1AE5FF240025F1B3 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; 7511E8CF1AE5FF2D0025F1B3 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; 7511E8D31AE5FF390025F1B3 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; @@ -3933,6 +3942,7 @@ 2A44313D22CF632F009BAF7F /* Models */ = { isa = PBXGroup; children = ( + 7503643C2C89D4890029EC0D /* CoinJoin */, 75A8C1612AE571E30042256E /* Voting */, 47081194298CF1E3003FCA3D /* Transactions */, 11BD737F28E7354200A34022 /* CrowdNode */, @@ -4033,6 +4043,7 @@ isa = PBXGroup; children = ( C9F451F22A0C933700825057 /* SyncingHeaderView.swift */, + 750364392C89CFB70029EC0D /* CoinJoinProgressView.swift */, ); path = Cells; sourceTree = ""; @@ -4193,6 +4204,7 @@ 2A7A7BD52348CB6600451078 /* SettingsMenuViewController.swift */, 2A7A7BD72348CB7300451078 /* DWSettingsMenuModel.h */, 2A7A7BD82348CB7300451078 /* DWSettingsMenuModel.m */, + 750CED5F2C94BFD7000FB837 /* SettingsViewModel.swift */, ); path = Settings; sourceTree = ""; @@ -5997,6 +6009,14 @@ path = Voting; sourceTree = ""; }; + 7503643C2C89D4890029EC0D /* CoinJoin */ = { + isa = PBXGroup; + children = ( + 7503643D2C89D49A0029EC0D /* CoinJoinService.swift */, + ); + path = CoinJoin; + sourceTree = ""; + }; 7531308F2B47EE480069C9B7 /* Model */ = { isa = PBXGroup; children = ( @@ -8482,6 +8502,7 @@ 75FFD6BB2BF48DF80032879E /* HomeViewController+JailbreakCheck.swift in Sources */, 477F50102950A55A003C7508 /* Coinbase+Error.swift in Sources */, 2A63003F2327B4BB00827825 /* DWPaymentOutput+DWView.m in Sources */, + 750CED602C94BFD7000FB837 /* SettingsViewModel.swift in Sources */, 2ACD53EE234C9D8E00650AD3 /* UIView+DWRecursiveSubview.m in Sources */, 11860923297598B400279FCC /* AddressStatus.swift in Sources */, C91E919729FBACE6003E7883 /* ExtendedPublicKeysModel.swift in Sources */, @@ -8814,6 +8835,7 @@ 472D13E3299E23B7006903F1 /* BalanceNotifier.swift in Sources */, 2AD1CE6422D9127600C99324 /* DWSeedWordModel.m in Sources */, 7592AA7C2B9B08C000417F9E /* SupportedTopperPaymentMethods.swift in Sources */, + 7503643E2C89D49A0029EC0D /* CoinJoinService.swift in Sources */, 75AA33CC2BF9C82700F12465 /* ModalDialog.swift in Sources */, 2A44314022CF642C009BAF7F /* DWRootModel.m in Sources */, 47C661AF28FDAA3400028A8D /* BaseAmountViewController.swift in Sources */, @@ -8901,6 +8923,7 @@ C909615B29F6535300002D82 /* DerivationPathKeysHeaderView.swift in Sources */, 4751CAD02970224D00F63AC4 /* ConvertCryptoOrderPreviewModel.swift in Sources */, 47A2E3A92972B15F0032A63B /* RatesProvider.swift in Sources */, + 7503643A2C89CFB70029EC0D /* CoinJoinProgressView.swift in Sources */, 47A2A2E9293E612900938DB7 /* CBAuth.swift in Sources */, 7566F48A2BB6CAF2005238D2 /* MenuItem.swift in Sources */, 0F6EDFD128C896BD000427E7 /* CoinbaseCreateAddressesRequest.swift in Sources */, @@ -9046,6 +9069,7 @@ C9D2C69A2A320AA000D15901 /* CrowdNodeAPI.swift in Sources */, C9D2C69B2A320AA000D15901 /* DWStartModel.m in Sources */, 754495DD2AE91B6300492817 /* GroupedRequestCell.swift in Sources */, + 7503643B2C89CFB70029EC0D /* CoinJoinProgressView.swift in Sources */, C9D2C69C2A320AA000D15901 /* DWAdvancedSecurityModelStub.m in Sources */, C9D2C69D2A320AA000D15901 /* Foundation+Bitcoin.swift in Sources */, C9D2C69E2A320AA000D15901 /* AtmDetailsView.swift in Sources */, @@ -9213,6 +9237,7 @@ C9D2C7152A320AA000D15901 /* DWBasePayViewController.m in Sources */, C943B4AC2A40A54600AF23C5 /* DWContactsViewController.m in Sources */, C9D2C7162A320AA000D15901 /* CrowdNodeTransferModel.swift in Sources */, + 750CED612C94BFD7000FB837 /* SettingsViewModel.swift in Sources */, C9D2C7172A320AA000D15901 /* DWSetPinViewController.m in Sources */, C943B3322A408CED00AF23C5 /* DWProfileDisplayNameCellModel.m in Sources */, C9D2C7182A320AA000D15901 /* ConfirmOrderController.swift in Sources */, @@ -9469,6 +9494,7 @@ C9D2C7D12A320AA000D15901 /* DWMainMenuContentView.m in Sources */, C9D2C7D32A320AA000D15901 /* CrowdNodeAPIConfirmationTx.swift in Sources */, C9D2C7D42A320AA000D15901 /* UIViewController+DWEmbedding.m in Sources */, + 7503643F2C89D49A0029EC0D /* CoinJoinService.swift in Sources */, C9D2C7D62A320AA000D15901 /* CALayer+DWShadow.m in Sources */, C9D2C7D72A320AA000D15901 /* MerchantDAO.swift in Sources */, C9D2C7D82A320AA000D15901 /* ExploreDatabaseSyncManager.swift in Sources */, diff --git a/DashWallet/Sources/Models/CoinJoin/CoinJoinService.swift b/DashWallet/Sources/Models/CoinJoin/CoinJoinService.swift new file mode 100644 index 000000000..500d1376b --- /dev/null +++ b/DashWallet/Sources/Models/CoinJoin/CoinJoinService.swift @@ -0,0 +1,298 @@ +// +// Created by Andrei Ashikhmin +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Combine + +enum MixingStatus: Int { + case notStarted + case mixing + case paused + case finished + case error + + var isInProgress: Bool { + get { + return self == .mixing || self == .paused || self == .error + } + } + + var localizedValue: String { + get { + switch self { + case .notStarted: + NSLocalizedString("Not started", comment: "CoinJoin") + case .mixing: + NSLocalizedString("Mixing ·", comment: "CoinJoin") + case .paused: + NSLocalizedString("Mixing Paused ·", comment: "CoinJoin") + case .finished: + NSLocalizedString("Fully mixed", comment: "CoinJoin") + case .error: + NSLocalizedString("Error ·", comment: "CoinJoin") + } + } + } +} + +enum CoinJoinMode { + case none + case intermediate + case advanced +} + +private let kDefaultMultisession = false // for stability, need to investigate +private let kDefaultRounds: Int32 = 1 //4 TODO +private let kDefaultSessions: Int32 = 1 //6 TODO +private let kDefaultDenominationGoal: Int32 = 50 +private let kDefaultDenominationHardcap: Int32 = 300 +private let kCoinJoinMode = "coinJoinModeKey" + +class CoinJoinService: NSObject { + static let shared: CoinJoinService = { + return CoinJoinService() + }() + + private var cancellableBag = Set() + private let updateMutex = NSLock() + private let updateMixingStateMutex = NSLock() + private var coinJoinManager: DSCoinJoinManager? = nil + private var hasAnonymizableBalance: Bool = false + private var networkStatus: NetworkStatus = .online + + @Published private(set) var mode: CoinJoinMode = .none + @Published var mixingState: MixingStatus = .notStarted + @Published private(set) var progress: Double = 0.0 + @Published private(set) var totalBalance: UInt64 = 0 + @Published private(set) var coinJoinBalance: UInt64 = 0 + @Published private(set) var activeSessions: Int = 0 + + override init() { + super.init() + NotificationCenter.default.publisher(for: NSNotification.Name.DSWalletBalanceDidChange) + .sink { [weak self] _ in self?.updateBalance(balance: DWEnvironment.sharedInstance().currentAccount.balance) } + .store(in: &cancellableBag) + } + + func updateMode(mode: CoinJoinMode) { + self.coinJoinManager?.updateOptions(withEnabled: mode != .none) + let account = DWEnvironment.sharedInstance().currentAccount + let balance = account.balance + + if (mode != .none && self.mode == .none) { + configureMixing(amount: balance) + } + + updateBalance(balance: balance) + // TODO: timeskew + updateState(balance: balance, mode: mode, timeSkew: TimeInterval(0), hasAnonymizableBalance: self.hasAnonymizableBalance, networkStatus: self.networkStatus, chain: DWEnvironment.sharedInstance().currentChain) + } + + private func prepareMixing() { + guard let coinJoinManager = self.coinJoinManager ?? createCoinJoinManager() else { return } + + coinJoinManager.setStopOnNothingToDo(true) + coinJoinManager.start() + } + + private func startMixing() { + guard let coinJoinManager = self.coinJoinManager else { return } + + if !coinJoinManager.startMixing() { + print("[SW] CoinJoin: Mixing has been started already.") + } else { + coinJoinManager.refreshUnusedKeys() + coinJoinManager.initMasternodeGroup() + coinJoinManager.doAutomaticDenominating() + + DSLogger.log("[SW] CoinJoin: Mixing \(coinJoinManager.startMixing() ? "started successfully" : "start failed, will retry")") // TODO: failed statuses: \(coinJoinManager.statuses) + } + } + + private func configureMixing(amount: UInt64) { + guard let coinJoinManager = self.coinJoinManager ?? createCoinJoinManager() else { return } + + let rounds: Int32 + switch mode { + case .none: + return + case .intermediate: + rounds = kDefaultRounds + case .advanced: + rounds = kDefaultRounds * 2 + } + + coinJoinManager.configureMixing(withAmount: amount, rounds: rounds, sessions: kDefaultSessions, withMultisession: kDefaultMultisession, denominationGoal: kDefaultDenominationGoal, denominationHardCap: kDefaultDenominationHardcap) + } + + private func updateProgress() { + guard let coinJoinManager = self.coinJoinManager else { return } + self.progress = coinJoinManager.getMixingProgress() + let coinJoinBalance = coinJoinManager.getBalance() + self.totalBalance = coinJoinBalance.myTrusted + self.coinJoinBalance = coinJoinBalance.anonymized + } + + private func createCoinJoinManager() -> DSCoinJoinManager? { + self.coinJoinManager = DSCoinJoinManager.sharedInstance(for: DWEnvironment().currentChain) + coinJoinManager?.managerDelegate = self + return self.coinJoinManager + } + + private func synchronized(_ lock: NSLock, closure: () -> Void) { + lock.lock() + defer { lock.unlock() } + closure() + } + + private func updateBalance(balance: UInt64) { + guard let coinJoinManager = self.coinJoinManager else { return } + + coinJoinManager.updateOptions(withAmount: balance) + DSLogger.log("[SW] CoinJoin: total balance: \(balance)") + let canDenominate = coinJoinManager.doAutomaticDenominating(withDryRun: true) + + let coinJoinBalance = coinJoinManager.getBalance() + DSLogger.log("[SW] CoinJoin: mixed balance: \(coinJoinBalance.anonymized)") + + let anonBalance = coinJoinManager.getAnonymizableBalance(withSkipDenominated: false, skipUnconfirmed: false) + DSLogger.log("[SW] CoinJoin: anonymizable balance \(anonBalance)") + + let smallestDenomination = coinJoinManager.getSmallestDenomination() + let hasPartiallyMixedCoins = (coinJoinBalance.denominatedTrusted - coinJoinBalance.anonymized) > 0 + let hasAnonymizableBalance = anonBalance > smallestDenomination + let hasBalanceLeftToMix: Bool + + if hasPartiallyMixedCoins { + hasBalanceLeftToMix = true + } else if hasAnonymizableBalance && canDenominate { + hasBalanceLeftToMix = true + } else { + hasBalanceLeftToMix = false + } + + DSLogger.log("[SW] CoinJoin: can mix balance: \(hasBalanceLeftToMix) = balance: (\(anonBalance > smallestDenomination) && canDenominate: \(canDenominate)) || partially-mixed: \(hasPartiallyMixedCoins)") + + updateState( + balance: balance, + mode: self.mode, + timeSkew: TimeInterval(0), // TODO + hasAnonymizableBalance: hasBalanceLeftToMix, + networkStatus: self.networkStatus, + chain: DWEnvironment.sharedInstance().currentChain + ) + } + + private func stopMixing() { + self.coinJoinManager?.managerDelegate = nil + self.coinJoinManager?.stop() + } + + private func updateState( + balance: UInt64, + mode: CoinJoinMode, + timeSkew: TimeInterval, + hasAnonymizableBalance: Bool, + networkStatus: NetworkStatus, + chain: DSChain + ) { + synchronized(self.updateMutex) { + DSLogger.log("[SW] CoinJoin: \(mode), \(timeSkew) ms, \(hasAnonymizableBalance), \(networkStatus), synced: \(chain.chainManager!.isSynced)") + + self.networkStatus = networkStatus + self.hasAnonymizableBalance = hasAnonymizableBalance + self.mode = mode + // self.timeSkew = timeSkew + + if mode == .none /*|| !isInsideTimeSkewBounds(timeSkew) || blockchainState.replaying*/ { // TODO + updateMixingState(state: .notStarted) + } else { + configureMixing(amount: balance) + + if hasAnonymizableBalance { + if networkStatus == .online && chain.chainManager!.isSynced { + updateMixingState(state: .mixing) + } else { + updateMixingState(state: .paused) + } + } else { + updateMixingState(state: .finished) + } + } + + updateProgress() + } + } + + private func updateMixingState(state: MixingStatus) { + synchronized(self.updateMixingStateMutex) { + let previousMixingStatus = self.mixingState + DSLogger.log("[SW] CoinJoin: \(previousMixingStatus) -> \(state)") + + if previousMixingStatus == .paused && state != .paused { + DSLogger.log("[SW] CoinJoin: moving from paused to \(state)") + } + + self.mixingState = state + + if state == .mixing && previousMixingStatus != .mixing { + // start mixing + prepareMixing() + startMixing() + } else if previousMixingStatus == .mixing && state != .mixing { + // finish mixing + stopMixing() + } + } + } +} + +extension CoinJoinService: DSCoinJoinManagerDelegate { + func sessionStarted(withId baseId: Int32, clientSessionId clientId: UInt256, denomination denom: UInt32, poolState state: PoolState, poolMessage message: PoolMessage, ipAddress address: UInt128, isJoined joined: Bool) { + updateActiveSessions() + } + + func sessionComplete(withId baseId: Int32, clientSessionId clientId: UInt256, denomination denom: UInt32, poolState state: PoolState, poolMessage message: PoolMessage, ipAddress address: UInt128, isJoined joined: Bool) { + updateActiveSessions() + } + + func mixingStarted() { } + + func mixingComplete(_ withError: Bool) { + if withError { + DSLogger.log("[SW] CoinJoin: Mixing Error. \(progress)% mixed") + } else { + DSLogger.log("[SW] CoinJoin: Mixing Complete. \(progress)% mixed") + } + + self.updateMixingState(state: withError ? .error : .finished) // TODO: paused? + } + + func transactionProcessed(withId txId: UInt256, type: CoinJoinTransactionType) { + self.updateProgress() + } + + private func updateActiveSessions() { + guard let coinJoinManager = self.coinJoinManager else { return } + + let activeSessions = coinJoinManager.getActiveSessionCount() + self.activeSessions = Int(activeSessions) + + DSLogger.log("[SW] CoinJoin: Active sessions: \(activeSessions)") + } +} + diff --git a/DashWallet/Sources/UI/DashPay/CoinJoin/CoinJoinLevelsViewController.swift b/DashWallet/Sources/UI/DashPay/CoinJoin/CoinJoinLevelsViewController.swift index b4827eeb8..a095163d0 100644 --- a/DashWallet/Sources/UI/DashPay/CoinJoin/CoinJoinLevelsViewController.swift +++ b/DashWallet/Sources/UI/DashPay/CoinJoin/CoinJoinLevelsViewController.swift @@ -32,8 +32,6 @@ class CoinJoinLevelsViewController: UIViewController { @IBOutlet private var advancedTime: UILabel! @IBOutlet private var continueButton: ActionButton! - @Published private(set) var selectedMode: CoinJoinMode = .none - @objc static func controller() -> CoinJoinLevelsViewController { vc(CoinJoinLevelsViewController.self, from: sb("CoinJoin")) @@ -47,9 +45,9 @@ class CoinJoinLevelsViewController: UIViewController { @IBAction func continueButtonAction() { - if viewModel.status == .notStarted { + if viewModel.mixingState == .notStarted { self.navigationController?.popViewController(animated: true) - viewModel.startMixing(mode: selectedMode) + viewModel.startMixing() } else { let alert = UIAlertController(title: NSLocalizedString("Are you sure you want to stop mixing?", comment: "CoinJoin"), message: NSLocalizedString("Any funds that have been mixed will be combined with your un mixed funds", comment: "CoinJoin"), preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("Stop Mixing", comment: "CoinJoin"), style: .destructive, handler: { [weak self] _ in @@ -64,8 +62,6 @@ class CoinJoinLevelsViewController: UIViewController { extension CoinJoinLevelsViewController { private func configureHierarchy() { - selectedMode = viewModel.mode - titleLabel.text = NSLocalizedString("Select mixing level", comment: "CoinJoin") intermediateTitle.text = NSLocalizedString("Intermediate", comment: "CoinJoin") intermediateDescription.text = NSLocalizedString("Advanced users who have a very high level of technical expertise can determine your transaction history", comment: "Coinbase") @@ -92,32 +88,32 @@ extension CoinJoinLevelsViewController { @objc private func selectIntermediate() { - if selectedMode == .intermediate { + if viewModel.selectedMode == .intermediate { return } - if viewModel.status == .mixing { + if viewModel.mixingState == .mixing { confirmFor(.intermediate) } else { - selectedMode = .intermediate + viewModel.selectedMode = .intermediate } } @objc private func selectAdvanced() { - if selectedMode == .advanced { + if viewModel.selectedMode == .advanced { return } - if viewModel.status == .mixing { + if viewModel.mixingState == .mixing { confirmFor(.advanced) } else { - selectedMode = .advanced + viewModel.selectedMode = .advanced } } private func configureObservers() { - $selectedMode + viewModel.$selectedMode .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] mode in guard let self = self else { return } @@ -135,7 +131,7 @@ extension CoinJoinLevelsViewController { }) .store(in: &cancellableBag) - viewModel.$status + viewModel.$mixingState .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] status in guard let self = self else { return } @@ -179,7 +175,7 @@ extension CoinJoinLevelsViewController { let alert = UIAlertController(title: "", message: NSLocalizedString("Are you sure you want to change the privacy level?", comment: "CoinJoin"), preferredStyle: .alert) alert.addAction(UIAlertAction(title: title, style: .default, handler: { [weak self] _ in - self?.selectedMode = mode + self?.viewModel.selectedMode = mode })) let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) alert.addAction(cancelAction) diff --git a/DashWallet/Sources/UI/DashPay/CoinJoin/CoinJoinViewModel.swift b/DashWallet/Sources/UI/DashPay/CoinJoin/CoinJoinViewModel.swift index 7a1958fb5..db84313f3 100644 --- a/DashWallet/Sources/UI/DashPay/CoinJoin/CoinJoinViewModel.swift +++ b/DashWallet/Sources/UI/DashPay/CoinJoin/CoinJoinViewModel.swift @@ -16,6 +16,7 @@ // import Foundation +import Combine @objc public class CoinJoinObjcWrapper: NSObject { @@ -25,28 +26,18 @@ public class CoinJoinObjcWrapper: NSObject { } } - -enum CoinJoinMode { - case none - case intermediate - case advanced -} - -enum MixingStatus { - case notStarted - case mixing - case paused - case finished - case error -} - private let kInfoShown = "coinJoinInfoShownKey" -class CoinJoinViewModel { +class CoinJoinViewModel: ObservableObject { static let shared = CoinJoinViewModel() + private var cancellableBag = Set() + private let coinJoinService = CoinJoinService.shared - private(set) var mode: CoinJoinMode = .none - @Published private(set) var status: MixingStatus = .notStarted + @Published var selectedMode: CoinJoinMode = .none + @Published private(set) var mixingState: MixingStatus = .notStarted + @Published private(set) var progress: Double = 0.0 + @Published private(set) var totalBalance: UInt64 = 0 + @Published private(set) var coinJoinBalance: UInt64 = 0 private var _infoShown: Bool? = nil var infoShown: Bool { @@ -57,12 +48,33 @@ class CoinJoinViewModel { } } - func startMixing(mode: CoinJoinMode) { - self.mode = mode - status = .mixing + init() { + coinJoinService.$mixingState + .receive(on: DispatchQueue.main) + .sink { [weak self] state in + self?.mixingState = state + } + .store(in: &cancellableBag) + + coinJoinService.$progress + .receive(on: DispatchQueue.main) + .sink { [weak self] progress in + guard let self = self else { return } + self.progress = progress + self.totalBalance = coinJoinService.totalBalance + self.coinJoinBalance = coinJoinService.coinJoinBalance + } + .store(in: &cancellableBag) + } + + func startMixing() { + if self.selectedMode != .none { + coinJoinService.updateMode(mode: self.selectedMode) + } } func stopMixing() { - status = .notStarted + selectedMode = .none + coinJoinService.updateMode(mode: .none) } } diff --git a/DashWallet/Sources/UI/Home/Views/Cells/CoinJoinProgressView.swift b/DashWallet/Sources/UI/Home/Views/Cells/CoinJoinProgressView.swift new file mode 100644 index 000000000..00f174120 --- /dev/null +++ b/DashWallet/Sources/UI/Home/Views/Cells/CoinJoinProgressView.swift @@ -0,0 +1,98 @@ +// +// Created by Andrei Ashikhmin +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct CoinJoinProgressView: View { + @State var state: MixingStatus + @State var progress: Double + @State var mixed: Double + @State var total: Double + + var body: some View { + HStack(spacing: 12) { + ZStack { + Circle() + .fill(Color.dashBlue.opacity(0.1)) + .frame(width: 38, height: 38) + + Image("image.coinjoin.menu") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + } + + VStack(alignment: .leading, spacing: 4) { + CoinJoinProgressInfo(state: state, progress: progress, mixed: mixed, total: total, textColor: .primaryText, font: .subheadline) + .padding(.leading, -6) + SwiftUI.ProgressView(value: progress) + .progressViewStyle(LinearProgressViewStyle(tint: .dashBlue)) + .frame(height: 6) + .padding(.top, 2) + } + } + .padding(12) + .background(Color.secondaryBackground) + .cornerRadius(8) + } +} + +struct CoinJoinProgressInfo: View { + @State var state: MixingStatus + @State var progress: Double + @State var mixed: Double + @State var total: Double + var textColor: Color + var font: Font + + var body: some View { + HStack(spacing: 0) { + if state == .mixing { + SwiftUI.ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .dashBlue)) + .scaleEffect(0.5) + } + + Text(state.localizedValue) + .foregroundColor(textColor) + .font(font) + .padding(.leading, state == .mixing ? 2 : 5) + + if state.isInProgress { + Text(progress.formatted(.percent.precision(.fractionLength(0...2)))) + .foregroundColor(textColor) + .font(font) + .padding(.leading, 4) + } + + Spacer() + Text("\(mixed, format: .number.precision(.fractionLength(0...3))) of \(total, format: .number.precision(.fractionLength(0...3)))") // TODO + .foregroundColor(textColor) + .font(font) + Image("icon_dash_currency") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: font.pointSize, height: font.pointSize) + .padding(.leading, 2) + .foregroundColor(textColor) + } + } +} + +#Preview { + CoinJoinProgressView(state: .mixing, progress: 0.45, mixed: 0.123, total: 0.321) +} diff --git a/DashWallet/Sources/UI/Home/Views/HomeView.swift b/DashWallet/Sources/UI/Home/Views/HomeView.swift index 95b028243..b2eb3f33b 100644 --- a/DashWallet/Sources/UI/Home/Views/HomeView.swift +++ b/DashWallet/Sources/UI/Home/Views/HomeView.swift @@ -192,6 +192,7 @@ final class HomeView: UIView, DWHomeModelUpdatesObserver, DWDPRegistrationErrorR private func setIdentity(dpInfoHidden: Bool, model: DWHomeProtocol) { headerView.isDPWelcomeViewHidden = dpInfoHidden headerView.isVotingViewHidden = true + viewModel.showJoinDashpay = !headerView.isDPWelcomeViewHidden let status = model.dashPayModel.registrationStatus let completed = model.dashPayModel.registrationCompleted @@ -212,6 +213,7 @@ final class HomeView: UIView, DWHomeModelUpdatesObserver, DWDPRegistrationErrorR let now = Date().timeIntervalSince1970 headerView.isVotingViewHidden = dpInfoHidden || wasClosed || now < VotingConstants.votingEndTime headerView.isDPWelcomeViewHidden = true + viewModel.showJoinDashpay = !headerView.isDPWelcomeViewHidden let dao = UsernameRequestsDAOImpl.shared Task { @@ -287,7 +289,18 @@ struct TransactionList: View { LazyVStack(pinnedViews: [.sectionHeaders]) { balanceHeader() - .frame(height: viewModel.hasNetwork ? 250 : 335) + .frame(height: viewModel.balanceHeaderHeight) + + if viewModel.coinJoinItem.isOn { + CoinJoinProgressView( + state: viewModel.coinJoinItem.state, + progress: viewModel.coinJoinItem.progress, + mixed: viewModel.coinJoinItem.mixed, + total: viewModel.coinJoinItem.total + ) + .padding(.horizontal, 15) + .id(viewModel.coinJoinItem.id) + } syncingHeader() .frame(height: 50) @@ -322,6 +335,9 @@ struct TransactionList: View { .sheet(item: $selectedTxDataItem) { item in TransactionDetailsSheet(item: item) } + .onChange(of: viewModel.coinJoinItem) { new in + DSLogger.log("[SW] CoinJoin: on change of coinJoinItem: \(viewModel.coinJoinItem.description)") + } } @ViewBuilder diff --git a/DashWallet/Sources/UI/Home/Views/HomeViewModel.swift b/DashWallet/Sources/UI/Home/Views/HomeViewModel.swift index f6b3df166..4ad97c71c 100644 --- a/DashWallet/Sources/UI/Home/Views/HomeViewModel.swift +++ b/DashWallet/Sources/UI/Home/Views/HomeViewModel.swift @@ -16,17 +16,31 @@ // import Foundation +import Combine + +let kBaseBalanceHeaderHeight: CGFloat = 250 class HomeViewModel: ObservableObject { + private var cancellableBag = Set() + private let coinJoinService = CoinJoinService.shared + @Published var txItems: Array<(DateKey, [TransactionListDataItem])> = [] - @Published var hasNetwork: Bool = true + @Published var balanceHeaderHeight: CGFloat = kBaseBalanceHeaderHeight // TDOO: move back to HomeView when fully transitioned to SwiftUI + @Published var coinJoinItem = CoinJoinMenuItemModel(title: NSLocalizedString("Mixing", comment: "CoinJoin"), isOn: false, state: .notStarted, progress: 0.0, mixed: 0.0, total: 0.0) + private var model: SyncModel = SyncModelImpl() + var showJoinDashpay: Bool = false { + didSet { + self.recalculateHeight() + } + } init() { model.networkStatusDidChange = { status in - self.hasNetwork = status == .online + self.recalculateHeight() } - self.hasNetwork = model.networkStatus == .online + self.recalculateHeight() + self.observeCoinJoin() } func updateItems(transactions: [DSTransaction]) { @@ -60,6 +74,60 @@ class HomeViewModel: ObservableObject { } } } + + private func recalculateHeight() { + var height = kBaseBalanceHeaderHeight + let hasNetwork = model.networkStatus == .online + + if !hasNetwork { + height += 85 + } + + if showJoinDashpay { + height += 50 + } + + self.balanceHeaderHeight = height + } +} + +extension HomeViewModel { + private func observeCoinJoin() { + coinJoinService.$progress + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.refreshCoinJoinItem() + } + .store(in: &cancellableBag) + + coinJoinService.$mode + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.refreshCoinJoinItem() + } + .store(in: &cancellableBag) + + coinJoinService.$mixingState + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.refreshCoinJoinItem() + } + .store(in: &cancellableBag) + } + + private func refreshCoinJoinItem() { + self.coinJoinItem = CoinJoinMenuItemModel( + title: NSLocalizedString("Mixing", comment: "CoinJoin"), + isOn: coinJoinService.mixingState.isInProgress, + state: coinJoinService.mixingState, + progress: coinJoinService.progress, + mixed: Double(coinJoinService.coinJoinBalance) / Double(DUFFS), + total: Double(coinJoinService.totalBalance) / Double(DUFFS) + ) + } } // MARK: - TransactionListDataItem diff --git a/DashWallet/Sources/UI/Menu/MenuItemModel.swift b/DashWallet/Sources/UI/Menu/MenuItemModel.swift index fcf3993ba..e4ca2a82e 100644 --- a/DashWallet/Sources/UI/Menu/MenuItemModel.swift +++ b/DashWallet/Sources/UI/Menu/MenuItemModel.swift @@ -48,12 +48,22 @@ class MenuItemModel: Identifiable, Equatable { } class CoinJoinMenuItemModel: MenuItemModel { - var mixingPercentage: String - var dashAmount: String + @State var isOn: Bool + @State var state: MixingStatus + @State var progress: Double + @State var mixed: Double + @State var total: Double - init(title: String, mixingPercentage: String, dashAmount: String, action: (() -> Void)? = nil) { - self.mixingPercentage = mixingPercentage - self.dashAmount = dashAmount + init(title: String, isOn: Bool, state: MixingStatus, progress: Double, mixed: Double, total: Double, action: (() -> Void)? = nil) { + self.isOn = isOn + self.state = state + self.progress = progress + self.mixed = mixed + self.total = total super.init(title: title, action: action) } + + var description: String { + return "CoinJoinMenuItemModel(title: \(title), isOn: \(isOn), state: \(state), progress: \(progress), mixed: \(mixed), total: \(total))" + } } diff --git a/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift index 33ab4c986..d97dbbf76 100644 --- a/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift +++ b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift @@ -17,6 +17,7 @@ import UIKit import SwiftUI +import Combine @objc(DWSettingsMenuViewControllerDelegate) protocol SettingsMenuViewControllerDelegate: AnyObject { @@ -29,6 +30,8 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle @objc weak var delegate: SettingsMenuViewControllerDelegate? private lazy var model: DWSettingsMenuModel = DWSettingsMenuModel() + private lazy var viewModel: SettingsViewModel = SettingsViewModel(model: model) + private var cancellables = Set() init() { super.init(nibName: nil, bundle: nil) @@ -46,7 +49,7 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle view.backgroundColor = .dw_secondaryBackground() let content = SettingsMenuContent( - items: menuItems(), + viewModel: self.viewModel, onLocalCurrencyChange: { [weak self] in self?.showCurrencySelector() }, @@ -60,6 +63,29 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle let swiftUIController = UIHostingController(rootView: content) swiftUIController.view.backgroundColor = .dw_secondaryBackground() dw_embedChild(swiftUIController) + setupNavigationObserver() + } + + private func setupNavigationObserver() { + viewModel.$navigationDestination + .receive(on: DispatchQueue.main) + .sink { [weak self] dest in + switch dest { + case .coinjoin: + self?.showCoinJoinController() + case .currencySelector: + self?.showCurrencySelector() + case .network: + self?.showChangeNetwork() + case .rescan: + self?.showWarningAboutReclassifiedTransactions() + case .about: + self?.showAboutController() + default: + break + } + } + .store(in: &cancellables) } override var preferredStatusBarStyle: UIStatusBarStyle { @@ -78,72 +104,6 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle // MARK: - Private - private func menuItems() -> [MenuItemModel] { - var items: [MenuItemModel] = [ - MenuItemModel( - title: NSLocalizedString("Local Currency", comment: ""), - subtitle: model.localCurrencyCode, - showChevron: true, - action: { [weak self] in - self?.showCurrencySelector() - } - ), - MenuItemModel( - title: NSLocalizedString("Enable Receive Notifications", comment: ""), - showToggle: true, - isToggled: self.model.notificationsEnabled, - action: { [weak self] in - self?.model.notificationsEnabled.toggle() - } - ), - MenuItemModel( - title: NSLocalizedString("Network", comment: ""), - subtitle: model.networkName, - showChevron: true, - action: { [weak self] in - self?.showChangeNetwork() - } - ), - MenuItemModel( - title: NSLocalizedString("Rescan Blockchain", comment: ""), - showChevron: true, - action: { [weak self] in - self?.showWarningAboutReclassifiedTransactions() - } - ), - MenuItemModel( - title: NSLocalizedString("About", comment: ""), - showChevron: true, - action: { [weak self] in - self?.showAboutController() - } - ) - ] - - #if DASHPAY - items.append(contentsOf: [ - CoinJoinMenuItemModel( - title: NSLocalizedString("CoinJoin", comment: ""), - mixingPercentage: "70%", - dashAmount: "0.085 of 0.199", - action: { [weak self] in - self?.showCoinJoinController() - } - ), - MenuItemModel( - title: "Enable Voting", - showToggle: true, - isToggled: VotingPrefs.shared.votingEnabled, - action: { - VotingPrefs.shared.votingEnabled.toggle() - } - ) - ]) - #endif - - return items - } - private func showCurrencySelector() { let controller = DWLocalCurrencyViewController(navigationAppearance: .default, presentationMode: .screen, currencyCode: nil) controller.delegate = self @@ -155,18 +115,6 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle navigationController?.pushViewController(aboutViewController, animated: true) } - private func showCoinJoinController() { - let vc: UIViewController - - if CoinJoinViewModel.shared.infoShown { - vc = CoinJoinLevelsViewController.controller() - } else { - vc = CoinJoinInfoViewController.controller() - } - - navigationController?.pushViewController(vc, animated: true) - } - private func showChangeNetwork() { let actionSheet = UIAlertController(title: NSLocalizedString("Network", comment: ""), message: nil, preferredStyle: .actionSheet) @@ -263,19 +211,35 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle } } +// MARK: - CoinJoin + +extension SettingsMenuViewController { + private func showCoinJoinController() { + let vc: UIViewController + + if CoinJoinViewModel.shared.infoShown { + vc = CoinJoinLevelsViewController.controller() + } else { + vc = CoinJoinInfoViewController.controller() + } + + navigationController?.pushViewController(vc, animated: true) + } +} + struct SettingsMenuContent: View { - var items: [MenuItemModel] + @StateObject var viewModel: SettingsViewModel var onLocalCurrencyChange: () -> Void var onNetworkChange: () -> Void var onRescanBlockchain: () -> Void var body: some View { - List(items) { item in + List(viewModel.items) { item in Group { if let cjItem = item as? CoinJoinMenuItemModel { MenuItem( title: cjItem.title, - subtitleView: AnyView(CoinJoinSubtitle(cjItem.mixingPercentage, cjItem.dashAmount)), + subtitleView: AnyView(CoinJoinSubtitle(cjItem)), icon: .custom("image.coinjoin.menu"), action: cjItem.action ) @@ -304,29 +268,15 @@ struct SettingsMenuContent: View { } @ViewBuilder - private func CoinJoinSubtitle(_ mixingPercentage: String, _ dashAmount: String) -> some View { - HStack(spacing: 0) { - SwiftUI.ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .dashBlue)) - .scaleEffect(0.5) - Text(NSLocalizedString("Mixing ·", comment: "CoinJoin")) - .font(.caption) - .foregroundColor(.tertiaryText) - .padding(.leading, 2) - Text(mixingPercentage) + private func CoinJoinSubtitle(_ cjItem: CoinJoinMenuItemModel) -> some View { + if cjItem.isOn { + CoinJoinProgressInfo(state: cjItem.state, progress: cjItem.progress, mixed: cjItem.mixed, total: cjItem.total, textColor: .tertiaryText, font: .caption) + } else { + Text(NSLocalizedString("Turned off", comment: "CoinJoin")) .font(.caption) .foregroundColor(.tertiaryText) .padding(.leading, 4) - Spacer() - Text(dashAmount) - .font(.caption) - .foregroundColor(.tertiaryText) - Image("icon_dash_currency") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12, height: 12) - .padding(.leading, 2) - .foregroundColor(.tertiaryText) + .padding(.top, 2) } } } diff --git a/DashWallet/Sources/UI/Menu/Settings/SettingsViewModel.swift b/DashWallet/Sources/UI/Menu/Settings/SettingsViewModel.swift new file mode 100644 index 000000000..40da83171 --- /dev/null +++ b/DashWallet/Sources/UI/Menu/Settings/SettingsViewModel.swift @@ -0,0 +1,134 @@ +// +// Created by Andrei Ashikhmin +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine + +enum SettingsNavDest { + case coinjoin + case currencySelector + case network + case rescan + case about + case none +} + +class SettingsViewModel: ObservableObject { + private var cancellableBag = Set() + private let coinJoinService = CoinJoinService.shared + private var model: DWSettingsMenuModel + @Published var items: [MenuItemModel] = [] + @Published private(set) var navigationDestination: SettingsNavDest = .none + + init(model: DWSettingsMenuModel) { + self.model = model + refreshMenuItems() + setupCoinJoinObservers() + } + + private func setupCoinJoinObservers() { + coinJoinService.$progress + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.refreshMenuItems() + } + .store(in: &cancellableBag) + + coinJoinService.$mode + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.refreshMenuItems() + } + .store(in: &cancellableBag) + + coinJoinService.$mixingState + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.refreshMenuItems() + } + .store(in: &cancellableBag) + } + + private func refreshMenuItems() { + self.items = [ + MenuItemModel( + title: NSLocalizedString("Local Currency", comment: ""), + subtitle: model.localCurrencyCode, + showChevron: true, + action: { [weak self] in + self?.navigationDestination = .currencySelector + } + ), + MenuItemModel( + title: NSLocalizedString("Enable Receive Notifications", comment: ""), + showToggle: true, + isToggled: model.notificationsEnabled, + action: { [weak self] in + self?.model.notificationsEnabled.toggle() + } + ), + MenuItemModel( + title: NSLocalizedString("Network", comment: ""), + subtitle: model.networkName, + showChevron: true, + action: { [weak self] in + self?.navigationDestination = .network + } + ), + MenuItemModel( + title: NSLocalizedString("Rescan Blockchain", comment: ""), + showChevron: true, + action: { [weak self] in + self?.navigationDestination = .rescan + } + ), + MenuItemModel( + title: NSLocalizedString("About", comment: ""), + showChevron: true, + action: { [weak self] in + self?.navigationDestination = .about + } + ) + ] + + #if DASHPAY + items.append(contentsOf: [ + CoinJoinMenuItemModel( + title: NSLocalizedString("CoinJoin", comment: "CoinJoin"), + isOn: coinJoinService.mode != .none, + state: coinJoinService.mixingState, + progress: coinJoinService.progress, + mixed: Double(coinJoinService.coinJoinBalance) / Double(DUFFS), + total: Double(coinJoinService.totalBalance) / Double(DUFFS), + action: { [weak self] in + self?.navigationDestination = .coinjoin + } + ), + MenuItemModel( + title: "Enable Voting", + showToggle: true, + isToggled: VotingPrefs.shared.votingEnabled, + action: { + VotingPrefs.shared.votingEnabled.toggle() + } + ) + ]) + #endif + } +} diff --git a/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift b/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift index 136d0b5f5..10bcdf6cb 100644 --- a/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift +++ b/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift @@ -21,7 +21,7 @@ typealias TransactionPreview = MenuItem struct MenuItem: View { var title: String - var subtitleView: AnyView? = nil + @State var subtitleView: AnyView? = nil var details: String? = nil var topText: String? = nil var icon: IconName? = nil @@ -89,7 +89,7 @@ struct MenuItem: View { action: (() -> Void)? = nil ) { self.title = title - self.subtitleView = subtitleView + self._subtitleView = State(initialValue: subtitleView) self.details = details self.topText = topText self.icon = icon diff --git a/DashWallet/dashwallet-Bridging-Header.h b/DashWallet/dashwallet-Bridging-Header.h index 95602335d..60966195a 100644 --- a/DashWallet/dashwallet-Bridging-Header.h +++ b/DashWallet/dashwallet-Bridging-Header.h @@ -113,7 +113,6 @@ static const bool _SNAPSHOT = 0; #import "UIView+DWEmbedding.h" #import "DWBasePressableControl.h" - #if DASHPAY #import "DWInvitationSetupState.h" #import "DPAlertViewController.h" @@ -168,3 +167,6 @@ static const bool _SNAPSHOT = 0; //MARK: Onboarding #import "DWTransactionStub.h" + +//MARK: CoinJoin +#import "DSCoinJoinManager.h"