From c8cd1cf37bf9030ad25c90472e07024536d71c14 Mon Sep 17 00:00:00 2001 From: tikhop Date: Wed, 10 May 2023 21:13:18 +0700 Subject: [PATCH 01/38] refactor: Move 'isJailbroken' method into 'UIApplication' extension --- DashWallet.xcodeproj/project.pbxproj | 4 ++ .../Categories/UIApplication+DashWallet.swift | 43 +++++++++++++++++++ .../DWHomeViewController+DWJailbreakCheck.m | 2 +- .../Sources/UI/Home/Models/DWHomeModel.m | 21 --------- .../UI/Home/Protocols/DWHomeProtocol.h | 1 - .../UI/Onboarding/Stubs/DWHomeModelStub.m | 4 -- 6 files changed, 48 insertions(+), 27 deletions(-) create mode 100644 DashWallet/Sources/Categories/UIApplication+DashWallet.swift diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index e8c54f050..b5960a520 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -722,6 +722,7 @@ C9F42FB429DD86FB001BC549 /* BackupInfoItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB329DD86FB001BC549 /* BackupInfoItemView.swift */; }; C9F42FB629DD8702001BC549 /* BackupInfoItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F42FB529DD8702001BC549 /* BackupInfoItemView.xib */; }; C9F42FB829DFC507001BC549 /* SpendableTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB729DFC506001BC549 /* SpendableTransaction.swift */; }; + C9F451E92A0BDAE700825057 /* UIApplication+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E82A0BDAE700825057 /* UIApplication+DashWallet.swift */; }; CC5F88E358330F8EE192D5BE /* libPods-DashWalletScreenshotsUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CDD4C961516ED20BC9F01FA /* libPods-DashWalletScreenshotsUITests.a */; }; DE3A167A235B79D705C0A962 /* libPods-dashwallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 982607F21196681DAC51A074 /* libPods-dashwallet.a */; }; FB248B5D1F73803100405AE0 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB248B5C1F73803100405AE0 /* UserNotifications.framework */; }; @@ -1995,6 +1996,7 @@ C9F42FB329DD86FB001BC549 /* BackupInfoItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupInfoItemView.swift; sourceTree = ""; }; C9F42FB529DD8702001BC549 /* BackupInfoItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BackupInfoItemView.xib; sourceTree = ""; }; C9F42FB729DFC506001BC549 /* SpendableTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpendableTransaction.swift; sourceTree = ""; }; + C9F451E82A0BDAE700825057 /* UIApplication+DashWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+DashWallet.swift"; sourceTree = ""; }; CE02413EF0C60B1D1EDE6457 /* Pods-WatchApp Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchApp Extension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WatchApp Extension/Pods-WatchApp Extension.release.xcconfig"; sourceTree = ""; }; D58B25CB4DC36975E05D3C0A /* Pods-dashwallet no watch.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashwallet no watch.debug.xcconfig"; path = "Pods/Target Support Files/Pods-dashwallet no watch/Pods-dashwallet no watch.debug.xcconfig"; sourceTree = ""; }; EA95ACF6CA2A73810B9BB451 /* Pods-DashWalletScreenshotsUITests.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashWalletScreenshotsUITests.testflight.xcconfig"; path = "Pods/Target Support Files/Pods-DashWalletScreenshotsUITests/Pods-DashWalletScreenshotsUITests.testflight.xcconfig"; sourceTree = ""; }; @@ -4288,6 +4290,7 @@ 47083B3129892D770010AF71 /* DSTransaction+DashWallet.swift */, 474C7210298A1A9500475CA6 /* UIView+Reuse.swift */, 472D13E9299E5396006903F1 /* NSAttributedString+Builder.swift */, + C9F451E82A0BDAE700825057 /* UIApplication+DashWallet.swift */, ); path = Categories; sourceTree = ""; @@ -6572,6 +6575,7 @@ 2A0C699C23104588001B8C90 /* DWPaymentInput.m in Sources */, 2AE9549D23D0C4F4003612B3 /* DWBaseContactsViewController.m in Sources */, 114D16B629812730009A124C /* OnlineAccountDetailsController.swift in Sources */, + C9F451E92A0BDAE700825057 /* UIApplication+DashWallet.swift in Sources */, 110D1784298E68A8005BEB30 /* OnlineAccountInfoController.swift in Sources */, 470AE1882926600A001A0514 /* PaymentController.swift in Sources */, 2ADB396C242615C200A6F898 /* CALayer+MBAnimationPersistence.m in Sources */, diff --git a/DashWallet/Sources/Categories/UIApplication+DashWallet.swift b/DashWallet/Sources/Categories/UIApplication+DashWallet.swift new file mode 100644 index 000000000..111ad6834 --- /dev/null +++ b/DashWallet/Sources/Categories/UIApplication+DashWallet.swift @@ -0,0 +1,43 @@ +// +// Created by PT +// Copyright © 2023 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 Darwin +import MachO.dyld + +@objc +extension UIApplication { + @objc + static var isJailbroken: Bool { + var s = stat() + let jailbroken = stat("/bin/sh", &s) == 0 // if we can see /bin/sh, the app isn't sandboxed + + // some anti-jailbreak detection tools re-sandbox apps, so do a secondary check for any MobileSubstrate dyld images + let count = _dyld_image_count() + for i in 0.. @property (nonatomic, strong) dispatch_queue_t queue; @@ -199,10 +182,6 @@ - (BOOL)isAllowedToShowReclassifyYourTransactions { return _allowedToShowReclassifyYourTransactions && shouldDisplayReclassifyYourTransactionsFlow; } -- (BOOL)isJailbroken { - return IsJailbroken(); -} - - (BOOL)isWalletEmpty { DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; const BOOL hasFunds = (wallet.totalReceived + wallet.totalSent > 0); diff --git a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h index 5e0442ce1..251d7b923 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h @@ -56,7 +56,6 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, nonatomic, assign) BOOL shouldShowWalletBackupReminder; -@property (readonly, nonatomic, assign, getter=isJailbroken) BOOL jailbroken; @property (readonly, nonatomic, assign, getter=isWalletEmpty) BOOL walletEmpty; @property (readonly, nonatomic, assign, getter=isAllowedToShowReclassifyYourTransactions) BOOL allowedToShowReclassifyYourTransactions; diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m index dc01ec941..45f090123 100644 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m +++ b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m @@ -108,10 +108,6 @@ - (BOOL)shouldShowWalletBackupReminder { return NO; } -- (BOOL)isJailbroken { - return NO; -} - - (BOOL)isWalletEmpty { return NO; } From d3c10bc8f2c7ed12b55959be2c44be33fced728a Mon Sep 17 00:00:00 2001 From: tikhop Date: Wed, 10 May 2023 22:24:01 +0700 Subject: [PATCH 02/38] refactor: Switching from 'DWSyncModel' to 'SyncingActivityMonitor' helper --- DashWallet.xcodeproj/project.pbxproj | 6 - .../Sources/UI/Home/DWHomeViewController.m | 56 ++++----- .../Sources/UI/Home/Models/DWHomeModel.m | 39 ++++-- .../Sources/UI/Home/Models/DWSyncModel.h | 38 ------ .../Sources/UI/Home/Models/DWSyncModel.m | 116 ------------------ DashWallet/Sources/UI/Home/Views/DWHomeView.m | 6 +- DashWallet/Sources/UI/Home/Views/DWSyncView.h | 13 +- DashWallet/Sources/UI/Home/Views/DWSyncView.m | 18 +-- .../UI/Home/Views/DWSyncingHeaderView.h | 4 +- .../UI/Home/Views/DWSyncingHeaderView.m | 2 +- .../Sources/UI/RootNavigation/DWRootModel.m | 1 - 11 files changed, 81 insertions(+), 218 deletions(-) delete mode 100644 DashWallet/Sources/UI/Home/Models/DWSyncModel.h delete mode 100644 DashWallet/Sources/UI/Home/Models/DWSyncModel.m diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index b5960a520..b77e76a27 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -189,7 +189,6 @@ 2A4E534422F02BC300E5168A /* UIView+DWReuseHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E534322F02BC300E5168A /* UIView+DWReuseHelper.m */; }; 2A4E534B22F03A9E00E5168A /* DWFilterHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E534A22F03A9E00E5168A /* DWFilterHeaderView.m */; }; 2A4E534D22F03AAC00E5168A /* DWFilterHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */; }; - 2A4E535022F19BE300E5168A /* DWSyncModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E534F22F19BE300E5168A /* DWSyncModel.m */; }; 2A4E535522F1D0D900E5168A /* DWTxListTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E535322F1D0D900E5168A /* DWTxListTableViewCell.xib */; }; 2A4E535C22F335C200E5168A /* DWTransactionListDataProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E535B22F335C200E5168A /* DWTransactionListDataProvider.m */; }; 2A5279BC23D994BC00F856D3 /* CoreNFC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A5279BB23D994BC00F856D3 /* CoreNFC.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; @@ -1091,8 +1090,6 @@ 2A4E534922F03A9E00E5168A /* DWFilterHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWFilterHeaderView.h; sourceTree = ""; }; 2A4E534A22F03A9E00E5168A /* DWFilterHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWFilterHeaderView.m; sourceTree = ""; }; 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWFilterHeaderView.xib; sourceTree = ""; }; - 2A4E534E22F19BE300E5168A /* DWSyncModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSyncModel.h; sourceTree = ""; }; - 2A4E534F22F19BE300E5168A /* DWSyncModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSyncModel.m; sourceTree = ""; }; 2A4E535322F1D0D900E5168A /* DWTxListTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWTxListTableViewCell.xib; sourceTree = ""; }; 2A4E535A22F335C200E5168A /* DWTransactionListDataProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTransactionListDataProvider.h; sourceTree = ""; }; 2A4E535B22F335C200E5168A /* DWTransactionListDataProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWTransactionListDataProvider.m; sourceTree = ""; }; @@ -2658,8 +2655,6 @@ 472D13E7299E4EE7006903F1 /* BalanceModel.swift */, 2A4E533622F023AB00E5168A /* DWHomeModel.h */, 2A4E533722F023AB00E5168A /* DWHomeModel.m */, - 2A4E534E22F19BE300E5168A /* DWSyncModel.h */, - 2A4E534F22F19BE300E5168A /* DWSyncModel.m */, 47081198298CF57D003FCA3D /* TransactionListDataSource.swift */, 2A6B8E522387056200A2E5FA /* DWBalanceDisplayOptions.h */, 2A6B8E532387056200A2E5FA /* DWBalanceDisplayOptions.m */, @@ -6468,7 +6463,6 @@ 11517C852949E6F0004FC7BF /* CrowdNodeEndpoint.swift in Sources */, 0F71317528F4365C0072F454 /* ServiceEntryPointModel.swift in Sources */, 47AE8B9528BFACA100490F5E /* ExploreDash.swift in Sources */, - 2A4E535022F19BE300E5168A /* DWSyncModel.m in Sources */, 2A7A7BE02348DC1900451078 /* DWToolsMenuViewController.m in Sources */, 2AE8B66B23CF0F390016F221 /* DWUsernamePendingViewController.m in Sources */, 4789D22F2981067C00BAFEFA /* UpholdAmountModel.swift in Sources */, diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController.m b/DashWallet/Sources/UI/Home/DWHomeViewController.m index 6e4a35713..9e7f543f2 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController.m @@ -27,7 +27,6 @@ #import "DWHomeViewController+DWShortcuts.h" #import "DWModalUserProfileViewController.h" #import "DWNotificationsViewController.h" -#import "DWSyncModel.h" #import "DWSyncingAlertViewController.h" #import "DWWindow.h" #import "UIViewController+DWTxFilter.h" @@ -224,33 +223,34 @@ - (void)presentTransactionDetails:(DSTransaction *)transaction { } - (void)checkCrowdNodeState { - if (self.model.syncModel.state == DWSyncModelState_SyncDone) { - [CrowdNodeObjcWrapper restoreState]; - - if ([CrowdNodeObjcWrapper isInterrupted]) { - // Continue signup - [CrowdNodeObjcWrapper continueInterrupted]; - } - } - else { - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - self.syncStateObserver = [notificationCenter addObserverForName:DWSyncStateChangedNotification - object:nil - queue:nil - usingBlock:^(NSNotification *note) { - BOOL isSynced = self.model.syncModel.state == DWSyncModelState_SyncDone; - - if (isSynced) { - if (self.syncStateObserver) { - // Only need to observe once - [[NSNotificationCenter defaultCenter] removeObserver:self.syncStateObserver]; - self.syncStateObserver = nil; - } - - [self checkCrowdNodeState]; - } - }]; - } + //TODO: Rewrite w/o DWSyncStateChangedNotification +// if (self.model.syncModel.state == DWSyncModelState_SyncDone) { +// [CrowdNodeObjcWrapper restoreState]; +// +// if ([CrowdNodeObjcWrapper isInterrupted]) { +// // Continue signup +// [CrowdNodeObjcWrapper continueInterrupted]; +// } +// } +// else { +// NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; +// self.syncStateObserver = [notificationCenter addObserverForName:DWSyncStateChangedNotification +// object:nil +// queue:nil +// usingBlock:^(NSNotification *note) { +// BOOL isSynced = self.model.syncModel.state == DWSyncModelState_SyncDone; +// +// if (isSynced) { +// if (self.syncStateObserver) { +// // Only need to observe once +// [[NSNotificationCenter defaultCenter] removeObserver:self.syncStateObserver]; +// self.syncStateObserver = nil; +// } +// +// [self checkCrowdNodeState]; +// } +// }]; +// } } @end diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index a31f81b76..e98a72aed 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -31,7 +31,6 @@ #import "DWGlobalOptions.h" #import "DWPayModel.h" #import "DWReceiveModel.h" -#import "DWSyncModel.h" #import "DWTransactionListDataProvider.h" #import "DWVersionManager.h" #import "UIDevice+DashWallet.h" @@ -48,6 +47,8 @@ @interface DWHomeModel () dashPayModel; +@property (nonatomic, strong) SyncingActivityMonitor *syncMonitor; + @property (readonly, nonatomic, strong) DWTransactionListDataSource *dataSource; @property (null_resettable, nonatomic, strong) DWTransactionListDataSource *receivedDataSource; @property (null_resettable, nonatomic, strong) DWTransactionListDataSource *sentDataSource; @@ -82,10 +83,13 @@ - (instancetype)init { [_reachability startMonitoring]; } - _dataProvider = [[DWTransactionListDataProvider alloc] init]; + _syncMonitor = SyncingActivityMonitor.shared; + [_syncMonitor addObserver:self]; - _syncModel = [[DWSyncModel alloc] init]; + + _dataProvider = [[DWTransactionListDataProvider alloc] init]; + #if DASHPAY_ENABLED _dashPayModel = [[DWDashPayModel alloc] init]; #endif /* DASHPAY_ENABLED */ @@ -121,10 +125,6 @@ - (instancetype)init { selector:@selector(transactionManagerTransactionStatusDidChangeNotification) name:DSTransactionManagerTransactionStatusDidChangeNotification object:nil]; - [notificationCenter addObserver:self - selector:@selector(syncStateChangedNotification) - name:DWSyncStateChangedNotification - object:nil]; [notificationCenter addObserver:self selector:@selector(chainWalletsDidChangeNotification:) name:DSChainWalletsDidChangeNotification @@ -152,6 +152,8 @@ - (instancetype)init { } - (void)dealloc { + [_syncMonitor removeObserver:self]; + DSLog(@"☠️ %@", NSStringFromClass(self.class)); } @@ -285,9 +287,7 @@ - (BOOL)performOnSetupUpgrades { } - (void)forceStartSyncingActivity { - DWSyncModel *syncModel = (DWSyncModel *)self.syncModel; - NSAssert([syncModel isKindOfClass:DWSyncModel.class], @"Internal inconsistency"); - [syncModel forceStartSyncingActivity]; + [[SyncingActivityMonitor shared] forceStartSyncingActivity]; } - (void)walletDidWipe { @@ -580,6 +580,25 @@ - (BOOL)isBalanceHidden { return self.balanceDisplayOptions.balanceHidden; } +#pragma mark SyncingActivityMonitorObserver + +- (void)syncingActivityMonitorProgressDidChange:(double)progress {} + +- (void)syncingActivityMonitorStateDidChangeWithPreviousState:(enum SyncingActivityMonitorState)previousState state:(enum SyncingActivityMonitorState)state { + BOOL isSynced = state == SyncingActivityMonitorStateSyncDone; + if (isSynced) { + [self.dashPayModel updateUsernameStatus]; + + if (self.dashPayModel.username != nil) { + [self.receiveModel updateReceivingInfo]; + [[DWDashPayContactsUpdater sharedInstance] beginUpdating]; + } + } + + [self updateBalance]; + [self reloadTxDataSource]; +} + @end NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Models/DWSyncModel.h b/DashWallet/Sources/UI/Home/Models/DWSyncModel.h deleted file mode 100644 index e18d2b4d6..000000000 --- a/DashWallet/Sources/UI/Home/Models/DWSyncModel.h +++ /dev/null @@ -1,38 +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 - -#import "DWSyncProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSReachabilityManager; - -extern NSString *const DWSyncStateChangedNotification; -// `NSNumber` of previous state in notification `userInfo` dictionary -extern NSString *const DWSyncStateChangedFromStateKey; - -@interface DWSyncModel : NSObject - -- (void)forceStartSyncingActivity; - -- (instancetype)init; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Models/DWSyncModel.m b/DashWallet/Sources/UI/Home/Models/DWSyncModel.m deleted file mode 100644 index 01f501770..000000000 --- a/DashWallet/Sources/UI/Home/Models/DWSyncModel.m +++ /dev/null @@ -1,116 +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 "DWSyncModel.h" - -#import - -#import "DWEnvironment.h" -#import "DWGlobalOptions.h" -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -#define LOG_SYNCING 0 - -#if LOG_SYNCING -#define DWSyncLog(frmt, ...) DSLog(frmt, ##__VA_ARGS__) -#else -#define DWSyncLog(frmt, ...) -#endif /* LOG_SYNCING */ - -__unused static NSString *SyncStateToString(DWSyncModelState state) { - switch (state) { - case DWSyncModelState_Syncing: - return @"Syncing"; - case DWSyncModelState_SyncDone: - return @"Done"; - case DWSyncModelState_SyncFailed: - return @"Failed"; - case DWSyncModelState_NoConnection: - return @"NoConnection"; - } -} - -NSString *const DWSyncStateChangedNotification = @"DWSyncStateChangedNotification"; -NSString *const DWSyncStateChangedFromStateKey = @"DWSyncStateChangedFromStateKey"; - -static NSTimeInterval const SYNC_LOOP_INTERVAL = 0.2; - -// Wait for 2.5 seconds to update progress to the new peak value. -// Peak is considered to be a difference between progress values more than 10%. -static NSTimeInterval const PROGRESS_PEAK_DELAY = 3.25; // 3.25 sec -static float const MAX_PROGRESS_DELTA = 0.1; // 10% - -@interface DWSyncModel () - -@property (nonatomic, strong) SyncingActivityMonitor *syncMonitor; - -@property (nonatomic, assign) DWSyncModelState state; -@property (nonatomic, assign) float progress; - -@end - -@implementation DWSyncModel - -- (instancetype)init { - self = [super init]; - if (self) { - _syncMonitor = SyncingActivityMonitor.shared; - [_syncMonitor addObserver:self]; - } - return self; -} - -- (void)dealloc { - [_syncMonitor removeObserver:self]; - - DSLog(@"☠️ %@", NSStringFromClass(self.class)); -} - -- (void)forceStartSyncingActivity { - DWSyncLog(@"[DW Sync] forceStartSyncingActivity"); - - [_syncMonitor forceStartSyncingActivity]; -} - -#pragma mark Private - -- (void)setState:(DWSyncModelState)state { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - if (_state == state) { - return; - } - - DWSyncLog(@"[DW Sync] Sync state: %@ -> %@", SyncStateToString(previousState), SyncStateToString(state)); - _state = state; -} - -#pragma mark SyncingActivityMonitorObserver - -- (void)syncingActivityMonitorProgressDidChange:(double)progress { - self.progress = progress; -} - -- (void)syncingActivityMonitorStateDidChangeWithPreviousState:(enum SyncingActivityMonitorState)previousState state:(enum SyncingActivityMonitorState)state { - self.state = state; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/DWHomeView.m b/DashWallet/Sources/UI/Home/Views/DWHomeView.m index fe3386375..a7401b377 100644 --- a/DashWallet/Sources/UI/Home/Views/DWHomeView.m +++ b/DashWallet/Sources/UI/Home/Views/DWHomeView.m @@ -108,7 +108,7 @@ - (instancetype)initWithFrame:(CGRect)frame { return; } - [self.syncingHeaderView setSyncState:self.model.syncModel.state]; + //TODO: [self.syncingHeaderView setSyncState:self.model.syncModel.state]; }]; [self mvvm_observe:DW_KEYPATH(self, model.syncModel.progress) @@ -117,7 +117,7 @@ - (instancetype)initWithFrame:(CGRect)frame { return; } - [self.syncingHeaderView setProgress:self.model.syncModel.progress]; + //TODO: [self.syncingHeaderView setProgress:self.model.syncModel.progress]; }]; } return self; @@ -191,7 +191,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N - (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { DWSyncingHeaderView *headerView = [[DWSyncingHeaderView alloc] initWithFrame:CGRectZero]; headerView.delegate = self; - [headerView setSyncState:self.model.syncModel.state]; + //TODO: [headerView setSyncState:self.model.syncModel.state]; [headerView setProgress:self.model.syncModel.progress]; self.syncingHeaderView = headerView; return headerView; diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncView.h b/DashWallet/Sources/UI/Home/Views/DWSyncView.h index f2106946f..90cdebc46 100644 --- a/DashWallet/Sources/UI/Home/Views/DWSyncView.h +++ b/DashWallet/Sources/UI/Home/Views/DWSyncView.h @@ -17,10 +17,17 @@ #import -#import "DWSyncModel.h" - NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, DWSyncViewState) { + DWSyncViewState_Syncing, + DWSyncViewState_SyncDone, + DWSyncViewState_SyncFailed, + DWSyncViewState_NoConnection, +}; + + @class DWSyncView; @protocol DWSyncViewDelegate @@ -33,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, weak) id delegate; -- (void)setSyncState:(DWSyncModelState)state; +- (void)setSyncState:(DWSyncViewState)state; - (void)setProgress:(float)progress animated:(BOOL)animated; @end diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncView.m b/DashWallet/Sources/UI/Home/Views/DWSyncView.m index ffd0581e5..ec2530fda 100644 --- a/DashWallet/Sources/UI/Home/Views/DWSyncView.m +++ b/DashWallet/Sources/UI/Home/Views/DWSyncView.m @@ -33,7 +33,7 @@ @interface DWSyncView () @property (strong, nonatomic) IBOutlet UIButton *retryButton; @property (strong, nonatomic) IBOutlet DWProgressView *progressView; @property (assign, nonatomic) BOOL viewStateSeeingBlocks; -@property (assign, nonatomic) DWSyncModelState syncState; +@property (assign, nonatomic) DWSyncViewState syncState; @end @@ -80,9 +80,9 @@ - (void)commonInit { [self.roundedView addGestureRecognizer:tapGestureRecognizer]; } -- (void)setSyncState:(DWSyncModelState)state { +- (void)setSyncState:(DWSyncViewState)state { _syncState = state; - if (state == DWSyncModelState_NoConnection) { + if (state == DWSyncViewState_NoConnection) { self.titleLabel.textColor = [UIColor dw_lightTitleColor]; self.descriptionLabel.textColor = [UIColor dw_lightTitleColor]; } @@ -92,8 +92,8 @@ - (void)setSyncState:(DWSyncModelState)state { } switch (state) { - case DWSyncModelState_Syncing: - case DWSyncModelState_SyncDone: { + case DWSyncViewState_Syncing: + case DWSyncViewState_SyncDone: { self.roundedView.backgroundColor = [UIColor dw_backgroundColor]; self.percentLabel.hidden = NO; self.retryButton.hidden = YES; @@ -102,7 +102,7 @@ - (void)setSyncState:(DWSyncModelState)state { [self updateUIForViewStateSeeingBlocks]; break; } - case DWSyncModelState_SyncFailed: { + case DWSyncViewState_SyncFailed: { self.roundedView.backgroundColor = [UIColor dw_backgroundColor]; self.percentLabel.hidden = YES; self.retryButton.tintColor = [UIColor dw_redColor]; @@ -113,7 +113,7 @@ - (void)setSyncState:(DWSyncModelState)state { break; } - case DWSyncModelState_NoConnection: { + case DWSyncViewState_NoConnection: { self.roundedView.backgroundColor = [UIColor dw_redColor]; self.percentLabel.hidden = YES; self.retryButton.tintColor = [UIColor dw_backgroundColor]; @@ -130,13 +130,13 @@ - (void)setSyncState:(DWSyncModelState)state { - (void)setProgress:(float)progress animated:(BOOL)animated { self.percentLabel.text = [NSString stringWithFormat:@"%0.1f%%", progress * 100.0]; [self.progressView setProgress:progress animated:animated]; - if (self.viewStateSeeingBlocks && self.syncState == DWSyncModelState_Syncing) { + if (self.viewStateSeeingBlocks && self.syncState == DWSyncViewState_Syncing) { [self updateUIForViewStateSeeingBlocks]; } } - (void)updateUIForViewStateSeeingBlocks { - if (self.syncState == DWSyncModelState_Syncing || self.syncState == DWSyncModelState_SyncDone) { + if (self.syncState == DWSyncViewState_Syncing || self.syncState == DWSyncViewState_SyncDone) { if (self.viewStateSeeingBlocks) { DWEnvironment *environment = [DWEnvironment sharedInstance]; DSChain *chain = environment.currentChain; diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.h b/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.h index 83653fc94..819fa05c9 100644 --- a/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.h +++ b/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.h @@ -17,8 +17,6 @@ #import -#import "DWSyncModel.h" - NS_ASSUME_NONNULL_BEGIN @class DWSyncingHeaderView; @@ -34,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, weak) id delegate; -@property (assign, nonatomic) DWSyncModelState syncState; +@property (assign, nonatomic) BOOL isSyncing; - (void)setProgress:(float)progress; diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.m b/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.m index d561cbb2c..d108cde40 100644 --- a/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.m +++ b/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.m @@ -117,7 +117,7 @@ - (void)setProgress:(float)progress { [result endEditing]; [self.syncingButton setAttributedTitle:result forState:UIControlStateNormal]; - self.syncingButton.hidden = self.syncState != DWSyncModelState_Syncing; + self.syncingButton.hidden = !self.isSyncing; } - (void)filterButtonAction:(UIButton *)sender { diff --git a/DashWallet/Sources/UI/RootNavigation/DWRootModel.m b/DashWallet/Sources/UI/RootNavigation/DWRootModel.m index 2cf774caa..f276075c6 100644 --- a/DashWallet/Sources/UI/RootNavigation/DWRootModel.m +++ b/DashWallet/Sources/UI/RootNavigation/DWRootModel.m @@ -21,7 +21,6 @@ #import "DWEnvironment.h" #import "DWGlobalOptions.h" #import "DWHomeModel.h" -#import "DWSyncModel.h" #import "dashwallet-Swift.h" #import From b41203fd751d08c4f7fe8f7bf08fb08e27f47fa7 Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 11 May 2023 00:00:00 +0700 Subject: [PATCH 03/38] refactor: Rewrite 'DWSyncingAlertViewController' in swift and avoid using 'DWHomeModel' inside the alert --- DashWallet.xcodeproj/project.pbxproj | 28 +-- .../Sources/UI/Home/DWHomeViewController.m | 2 - .../Sources/UI/Home/Models/DWHomeModel.m | 2 +- .../SyncingAlert/DWSyncingAlertContentView.h | 40 ---- .../SyncingAlert/DWSyncingAlertContentView.m | 199 ------------------ .../DWSyncingAlertViewController.h | 30 --- .../DWSyncingAlertViewController.m | 104 --------- .../SyncingAlertViewController.swift | 89 ++++++++ .../View/SyncingAlertContentView.swift | 156 ++++++++++++++ DashWallet/Sources/UI/Home/Views/DWHomeView.m | 4 + 10 files changed, 266 insertions(+), 388 deletions(-) delete mode 100644 DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertContentView.h delete mode 100644 DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertContentView.m delete mode 100644 DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertViewController.h delete mode 100644 DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertViewController.m create mode 100644 DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift create mode 100644 DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index b77e76a27..d5fc7981c 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -120,8 +120,6 @@ 2A10EB442358D2CA00C38B61 /* DWResetWalletInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A10EB432358D2CA00C38B61 /* DWResetWalletInfoViewController.m */; }; 2A11F59F2194BD6200E7B563 /* DWDataMigrationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A11F59E2194BD6200E7B563 /* DWDataMigrationManager.m */; }; 2A12E61923ABC7D3001CAF58 /* DWDemoMainTabbarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A12E61823ABC7D3001CAF58 /* DWDemoMainTabbarViewController.m */; }; - 2A1A60BC2674084F003DAC65 /* DWSyncingAlertContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1A60BB2674084F003DAC65 /* DWSyncingAlertContentView.m */; }; - 2A1A60BF26740E27003DAC65 /* DWSyncingAlertViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1A60BE26740E27003DAC65 /* DWSyncingAlertViewController.m */; }; 2A1AE78E23F4668B00179A6E /* DWUsernameHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1AE78D23F4668B00179A6E /* DWUsernameHeaderView.m */; }; 2A1AE79223F468CD00179A6E /* DWPlanetarySystemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1AE79123F468CD00179A6E /* DWPlanetarySystemView.m */; }; 2A1AF6DE23C7681B00442AF5 /* DWShortcutCollectionViewCell~ipad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A1AF6DC23C7681B00442AF5 /* DWShortcutCollectionViewCell~ipad.xib */; }; @@ -722,6 +720,8 @@ C9F42FB629DD8702001BC549 /* BackupInfoItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F42FB529DD8702001BC549 /* BackupInfoItemView.xib */; }; C9F42FB829DFC507001BC549 /* SpendableTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB729DFC506001BC549 /* SpendableTransaction.swift */; }; C9F451E92A0BDAE700825057 /* UIApplication+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E82A0BDAE700825057 /* UIApplication+DashWallet.swift */; }; + C9F451EB2A0BF10B00825057 /* SyncingAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451EA2A0BF10B00825057 /* SyncingAlertViewController.swift */; }; + C9F451EE2A0BF1F500825057 /* SyncingAlertContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451ED2A0BF1F500825057 /* SyncingAlertContentView.swift */; }; CC5F88E358330F8EE192D5BE /* libPods-DashWalletScreenshotsUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CDD4C961516ED20BC9F01FA /* libPods-DashWalletScreenshotsUITests.a */; }; DE3A167A235B79D705C0A962 /* libPods-dashwallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 982607F21196681DAC51A074 /* libPods-dashwallet.a */; }; FB248B5D1F73803100405AE0 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB248B5C1F73803100405AE0 /* UserNotifications.framework */; }; @@ -962,10 +962,6 @@ 2A12E61723ABC7D3001CAF58 /* DWDemoMainTabbarViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDemoMainTabbarViewController.h; sourceTree = ""; }; 2A12E61823ABC7D3001CAF58 /* DWDemoMainTabbarViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDemoMainTabbarViewController.m; sourceTree = ""; }; 2A12E61A23ABC8E1001CAF58 /* DWExtendedContainerViewController+DWProtected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWExtendedContainerViewController+DWProtected.h"; sourceTree = ""; }; - 2A1A60BA2674084F003DAC65 /* DWSyncingAlertContentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSyncingAlertContentView.h; sourceTree = ""; }; - 2A1A60BB2674084F003DAC65 /* DWSyncingAlertContentView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSyncingAlertContentView.m; sourceTree = ""; }; - 2A1A60BD26740E27003DAC65 /* DWSyncingAlertViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSyncingAlertViewController.h; sourceTree = ""; }; - 2A1A60BE26740E27003DAC65 /* DWSyncingAlertViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSyncingAlertViewController.m; sourceTree = ""; }; 2A1AE78C23F4668B00179A6E /* DWUsernameHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUsernameHeaderView.h; sourceTree = ""; }; 2A1AE78D23F4668B00179A6E /* DWUsernameHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUsernameHeaderView.m; sourceTree = ""; }; 2A1AE79023F468CD00179A6E /* DWPlanetarySystemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPlanetarySystemView.h; sourceTree = ""; }; @@ -1994,6 +1990,8 @@ C9F42FB529DD8702001BC549 /* BackupInfoItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BackupInfoItemView.xib; sourceTree = ""; }; C9F42FB729DFC506001BC549 /* SpendableTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpendableTransaction.swift; sourceTree = ""; }; C9F451E82A0BDAE700825057 /* UIApplication+DashWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+DashWallet.swift"; sourceTree = ""; }; + C9F451EA2A0BF10B00825057 /* SyncingAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncingAlertViewController.swift; sourceTree = ""; }; + C9F451ED2A0BF1F500825057 /* SyncingAlertContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncingAlertContentView.swift; sourceTree = ""; }; CE02413EF0C60B1D1EDE6457 /* Pods-WatchApp Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchApp Extension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WatchApp Extension/Pods-WatchApp Extension.release.xcconfig"; sourceTree = ""; }; D58B25CB4DC36975E05D3C0A /* Pods-dashwallet no watch.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashwallet no watch.debug.xcconfig"; path = "Pods/Target Support Files/Pods-dashwallet no watch/Pods-dashwallet no watch.debug.xcconfig"; sourceTree = ""; }; EA95ACF6CA2A73810B9BB451 /* Pods-DashWalletScreenshotsUITests.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashWalletScreenshotsUITests.testflight.xcconfig"; path = "Pods/Target Support Files/Pods-DashWalletScreenshotsUITests/Pods-DashWalletScreenshotsUITests.testflight.xcconfig"; sourceTree = ""; }; @@ -2560,10 +2558,8 @@ 2A1A60B92674083B003DAC65 /* SyncingAlert */ = { isa = PBXGroup; children = ( - 2A1A60BA2674084F003DAC65 /* DWSyncingAlertContentView.h */, - 2A1A60BB2674084F003DAC65 /* DWSyncingAlertContentView.m */, - 2A1A60BD26740E27003DAC65 /* DWSyncingAlertViewController.h */, - 2A1A60BE26740E27003DAC65 /* DWSyncingAlertViewController.m */, + C9F451EC2A0BF1EF00825057 /* View */, + C9F451EA2A0BF10B00825057 /* SyncingAlertViewController.swift */, ); path = SyncingAlert; sourceTree = ""; @@ -5717,6 +5713,14 @@ path = Style; sourceTree = ""; }; + C9F451EC2A0BF1EF00825057 /* View */ = { + isa = PBXGroup; + children = ( + C9F451ED2A0BF1F500825057 /* SyncingAlertContentView.swift */, + ); + path = View; + sourceTree = ""; + }; EBFC2EA47915CD4F5BA81564 /* Pods */ = { isa = PBXGroup; children = ( @@ -6394,6 +6398,7 @@ 2A1F640C238D5BBB00A9B505 /* DWSegmentSliderFormTableViewCell.m in Sources */, 11B8449428F27C470082770C /* ApiCode.swift in Sources */, 47838B92290701470003E8AB /* CoinbaseEntryPointModel.swift in Sources */, + C9F451EB2A0BF10B00825057 /* SyncingAlertViewController.swift in Sources */, 2A56EF002419310C002C32F3 /* DWDashPayConstants.m in Sources */, 11B8449628F5B9F80082770C /* CrowdNodeResponse.swift in Sources */, 47AE8BB228BFF61A00490F5E /* FileManager+DashWallet.swift in Sources */, @@ -6894,8 +6899,8 @@ 2A307CBF22E8A44200A18347 /* DWButton.m in Sources */, 0F6EDFD128C896BD000427E7 /* CoinbaseCreateAddressesRequest.swift in Sources */, 2A10EB352358996700C38B61 /* DWImportWalletInfoViewController.m in Sources */, + C9F451EE2A0BF1F500825057 /* SyncingAlertContentView.swift in Sources */, 471A2605289ACD5C0056B7B2 /* AddressUserInfo.swift in Sources */, - 2A1A60BF26740E27003DAC65 /* DWSyncingAlertViewController.m in Sources */, 47AE8C0C28C53E4A00490F5E /* MerchantInfoViewController.swift in Sources */, 11E47BAB28EB38510097CFA0 /* SendCoinsService.swift in Sources */, C94F5E8A29D3FCCF0034FD57 /* ShortcutAction.swift in Sources */, @@ -6958,7 +6963,6 @@ 2A4E1D5325056297008AC53F /* DWPaymentsButton.m in Sources */, 11BD738128E7356100A34022 /* CrowdNode.swift in Sources */, 47AE8B9F28BFAD8200490F5E /* SQLite+ExloreDash.swift in Sources */, - 2A1A60BC2674084F003DAC65 /* DWSyncingAlertContentView.m in Sources */, 2A4E534B22F03A9E00E5168A /* DWFilterHeaderView.m in Sources */, 2A951CE423D1B92C00602824 /* DWBaseContactsModel.m in Sources */, 2A1F6415238FEEA900A9B505 /* DWAdvancedSecurityModel.m in Sources */, diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController.m b/DashWallet/Sources/UI/Home/DWHomeViewController.m index 9e7f543f2..82989c259 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController.m @@ -27,7 +27,6 @@ #import "DWHomeViewController+DWShortcuts.h" #import "DWModalUserProfileViewController.h" #import "DWNotificationsViewController.h" -#import "DWSyncingAlertViewController.h" #import "DWWindow.h" #import "UIViewController+DWTxFilter.h" #import "UIWindow+DSUtils.h" @@ -110,7 +109,6 @@ - (void)homeView:(DWHomeView *)homeView showTxFilter:(UIView *)sender { - (void)homeView:(DWHomeView *)homeView showSyncingStatus:(UIView *)sender { DWSyncingAlertViewController *controller = [[DWSyncingAlertViewController alloc] init]; - controller.model = self.model; [self presentViewController:controller animated:YES completion:nil]; } diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index e98a72aed..e347bdf83 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWHomeModel () +@interface DWHomeModel () @property (nonatomic, strong) dispatch_queue_t queue; @property (strong, nonatomic) DSReachabilityManager *reachability; diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertContentView.h b/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertContentView.h deleted file mode 100644 index 2c08f2c83..000000000 --- a/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertContentView.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2021 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 - -#import "DWSyncContainerProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWSyncingAlertContentView; - -@protocol DWSyncingAlertContentViewDelegate - -- (void)syncingAlertContentView:(DWSyncingAlertContentView *)view okButtonAction:(UIButton *)sender; - -@end - -@interface DWSyncingAlertContentView : KVOUIView - -@property (nullable, nonatomic, weak) id delegate; - -@property (nonatomic, strong) id model; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertContentView.m b/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertContentView.m deleted file mode 100644 index 18d720057..000000000 --- a/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertContentView.m +++ /dev/null @@ -1,199 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2021 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 "DWSyncingAlertContentView.h" - -#import "DWActionButton.h" -#import "DWEnvironment.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWSyncingAlertContentView () - -@property (readonly, nonatomic, strong) UIImageView *syncingImageView; -@property (readonly, nonatomic, strong) UILabel *titleLabel; -@property (readonly, nonatomic, strong) UILabel *subtitleLabel; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWSyncingAlertContentView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_backgroundColor]; - - UIImageView *syncingImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_syncing_large"]]; - syncingImageView.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:syncingImageView]; - _syncingImageView = syncingImageView; - - UILabel *titleLabel = [[UILabel alloc] init]; - titleLabel.translatesAutoresizingMaskIntoConstraints = NO; - titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleTitle3]; - titleLabel.textColor = [UIColor dw_darkTitleColor]; - titleLabel.adjustsFontForContentSizeCategory = YES; - titleLabel.textAlignment = NSTextAlignmentCenter; - titleLabel.numberOfLines = 0; - [self addSubview:titleLabel]; - _titleLabel = titleLabel; - - UILabel *subtitleLabel = [[UILabel alloc] init]; - subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; - subtitleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; - subtitleLabel.textColor = [UIColor dw_secondaryTextColor]; - subtitleLabel.adjustsFontForContentSizeCategory = YES; - subtitleLabel.textAlignment = NSTextAlignmentCenter; - subtitleLabel.numberOfLines = 0; - [self addSubview:subtitleLabel]; - _subtitleLabel = subtitleLabel; - - DWActionButton *okButton = [[DWActionButton alloc] init]; - okButton.translatesAutoresizingMaskIntoConstraints = NO; - okButton.small = YES; - [okButton setTitle:NSLocalizedString(@"OK", nil) forState:UIControlStateNormal]; - [okButton addTarget:self action:@selector(okButtonAction:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:okButton]; - - [syncingImageView setContentCompressionResistancePriority:UILayoutPriorityRequired - forAxis:UILayoutConstraintAxisVertical]; - [syncingImageView setContentCompressionResistancePriority:UILayoutPriorityRequired - forAxis:UILayoutConstraintAxisHorizontal]; - - [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 - forAxis:UILayoutConstraintAxisVertical]; - [subtitleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 - forAxis:UILayoutConstraintAxisVertical]; - - [NSLayoutConstraint activateConstraints:@[ - [syncingImageView.topAnchor constraintEqualToAnchor:self.topAnchor], - [syncingImageView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], - - [titleLabel.topAnchor constraintEqualToAnchor:syncingImageView.bottomAnchor - constant:16.0], - [titleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], - - [subtitleLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor - constant:8.0], - [subtitleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [self.trailingAnchor constraintEqualToAnchor:subtitleLabel.trailingAnchor], - - [okButton.topAnchor constraintEqualToAnchor:subtitleLabel.bottomAnchor - constant:38.0], - [okButton.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], - [self.bottomAnchor constraintEqualToAnchor:okButton.bottomAnchor], - [okButton.heightAnchor constraintEqualToConstant:32.0], - [okButton.widthAnchor constraintEqualToConstant:110.0], - ]]; - - // KVO - - [self mvvm_observe:DW_KEYPATH(self, model.syncModel.state) - with:^(typeof(self) self, NSNumber *value) { - [self updateWithState:self.model.syncModel.state]; - }]; - - [self mvvm_observe:DW_KEYPATH(self, model.syncModel.progress) - with:^(typeof(self) self, NSNumber *value) { - const float progress = self.model.syncModel.progress; - self.titleLabel.text = [NSString stringWithFormat:@"%@ %0.1f%%", NSLocalizedString(@"Syncing", nil), - progress * 100.0]; - if (self.model.syncModel.state == DWSyncModelState_Syncing) { - [self updateWithState:self.model.syncModel.state]; - } - }]; - } - return self; -} - -- (void)okButtonAction:(UIButton *)sender { - [self.delegate syncingAlertContentView:self okButtonAction:sender]; -} - -- (void)updateWithState:(DWSyncModelState)syncState { - switch (syncState) { - case DWSyncModelState_Syncing: - case DWSyncModelState_SyncDone: { - DWEnvironment *environment = [DWEnvironment sharedInstance]; - DSChain *chain = environment.currentChain; - DSChainManager *chainManager = environment.currentChainManager; - // We give a 6 block window, just in case a new block comes in - BOOL atTheEndOfInitialTerminalBlocksAndSyncingMasternodeList = chain.lastTerminalBlockHeight >= chain.estimatedBlockHeight - 6 && chainManager.masternodeManager.masternodeListRetrievalQueueCount && - chainManager.syncPhase == DSChainSyncPhase_InitialTerminalBlocks; - BOOL atTheEndOfSyncBlocksAndSyncingMasternodeList = chain.lastSyncBlockHeight >= chain.estimatedBlockHeight - 6 && chainManager.masternodeManager.masternodeListRetrievalQueueCount && - chainManager.syncPhase == DSChainSyncPhase_Synced; - if (atTheEndOfInitialTerminalBlocksAndSyncingMasternodeList || atTheEndOfSyncBlocksAndSyncingMasternodeList) { - self.subtitleLabel.text = [NSString stringWithFormat:NSLocalizedString(@"masternode list #%d of %d", nil), - (int)(chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount - chainManager.masternodeManager.masternodeListRetrievalQueueCount), - (int)chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount]; - } - else { - if (chainManager.syncPhase == DSChainSyncPhase_InitialTerminalBlocks) { - - self.subtitleLabel.text = [NSString stringWithFormat:NSLocalizedString(@"header #%d of %d", nil), - chain.lastTerminalBlockHeight, - chain.estimatedBlockHeight]; - } - else { - self.subtitleLabel.text = [NSString stringWithFormat:NSLocalizedString(@"block #%d of %d", nil), - chain.lastSyncBlockHeight, - chain.estimatedBlockHeight]; - } - } - - break; - } - case DWSyncModelState_SyncFailed: - self.subtitleLabel.text = NSLocalizedString(@"Sync Failed", nil); - - break; - case DWSyncModelState_NoConnection: - self.subtitleLabel.text = NSLocalizedString(@"Unable to connect", nil); - - break; - } - - if (syncState == DWSyncModelState_Syncing) { - [self showAnimation]; - } - else { - [self hideAnimation]; - } -} - -- (void)showAnimation { - if ([self.syncingImageView.layer animationForKey:@"dw_rotationAnimation"]) { - return; - } - - CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; - rotationAnimation.fromValue = @0; - rotationAnimation.toValue = @(M_PI * 2.0); - rotationAnimation.duration = 1.75; - rotationAnimation.repeatCount = HUGE_VALF; - [self.syncingImageView.layer addAnimation:rotationAnimation forKey:@"dw_rotationAnimation"]; -} - -- (void)hideAnimation { - [self.syncingImageView.layer removeAllAnimations]; -} - -@end diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertViewController.h b/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertViewController.h deleted file mode 100644 index ef73ac9da..000000000 --- a/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertViewController.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2021 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 - -#import "DWSyncContainerProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWSyncingAlertViewController : UIViewController - -@property (nonatomic, strong) id model; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertViewController.m b/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertViewController.m deleted file mode 100644 index 727d60475..000000000 --- a/DashWallet/Sources/UI/Home/SyncingAlert/DWSyncingAlertViewController.m +++ /dev/null @@ -1,104 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2021 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 "DWSyncingAlertViewController.h" - -#import "DWModalPopupTransition.h" -#import "DWSyncingAlertContentView.h" -#import "DWUIKit.h" - - -NS_ASSUME_NONNULL_BEGIN - -@interface DWSyncingAlertViewController () - -@property (nonatomic, strong) DWModalPopupTransition *modalTransition; -@property (null_resettable, nonatomic, strong) DWSyncingAlertContentView *childView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWSyncingAlertViewController - -- (instancetype)init { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _modalTransition = [[DWModalPopupTransition alloc] init]; - - self.transitioningDelegate = self.modalTransition; - self.modalPresentationStyle = UIModalPresentationCustom; - } - return self; -} - -- (id)model { - return self.childView.model; -} - -- (void)setModel:(id)model { - self.childView.model = model; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor clearColor]; - - UIView *contentView = [[UIView alloc] init]; - contentView.translatesAutoresizingMaskIntoConstraints = NO; - contentView.backgroundColor = [UIColor dw_backgroundColor]; - contentView.layer.cornerRadius = 8.0; - contentView.layer.masksToBounds = YES; - [self.view addSubview:contentView]; - - [contentView addSubview:self.childView]; - - [NSLayoutConstraint activateConstraints:@[ - [contentView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], - - [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], - - [self.childView.topAnchor constraintEqualToAnchor:contentView.topAnchor - constant:32.0], - [self.childView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], - [self.childView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], - [contentView.bottomAnchor constraintEqualToAnchor:self.childView.bottomAnchor - constant:32.0], - ]]; -} - -#pragma mark - DWSyncingAlertContentViewDelegate - -- (void)syncingAlertContentView:(DWSyncingAlertContentView *)view okButtonAction:(UIButton *)sender { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -#pragma mark - Private - -- (DWSyncingAlertContentView *)childView { - if (_childView == nil) { - DWSyncingAlertContentView *childView = [[DWSyncingAlertContentView alloc] init]; - childView.translatesAutoresizingMaskIntoConstraints = NO; - childView.delegate = self; - _childView = childView; - } - return _childView; -} - -@end diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift b/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift new file mode 100644 index 000000000..9d48ce611 --- /dev/null +++ b/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift @@ -0,0 +1,89 @@ +// +// Created by PT +// Copyright © 2023 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(DWSyncingAlertViewController) +final class SyncingAlertViewController: BaseViewController, SyncingAlertContentViewDelegate { + + var modalTransition = DWModalPopupTransition() + private lazy var childView: SyncingAlertContentView = { + let childView = SyncingAlertContentView() + childView.translatesAutoresizingMaskIntoConstraints = false + childView.delegate = self + childView.update(with: SyncingActivityMonitor.shared.progress) + childView.update(with: SyncingActivityMonitor.shared.state) + return childView + }() + + init() { + super.init(nibName: nil, bundle: nil) + self.transitioningDelegate = modalTransition + self.modalPresentationStyle = .custom + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + SyncingActivityMonitor.shared.add(observer: self) + + view.backgroundColor = .clear + + let contentView = UIView() + contentView.translatesAutoresizingMaskIntoConstraints = false + contentView.backgroundColor = UIColor.dw_background() + contentView.layer.cornerRadius = 8.0 + contentView.layer.masksToBounds = true + view.addSubview(contentView) + + contentView.addSubview(childView) + + NSLayoutConstraint.activate([ + contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor), + contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + childView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 32.0), + childView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + childView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: childView.bottomAnchor, constant: 32.0), + ]) + } + + // MARK: - DWSyncingAlertContentViewDelegate + + func syncingAlertContentView(_ view: SyncingAlertContentView, okButtonAction sender: UIButton) { + dismiss(animated: true, completion: nil) + } + + deinit { + SyncingActivityMonitor.shared.remove(observer: self) + } +} + +extension SyncingAlertViewController: SyncingActivityMonitorObserver { + func syncingActivityMonitorProgressDidChange(_ progress: Double) { + childView.update(with: progress) + } + + func syncingActivityMonitorStateDidChange(previousState: SyncingActivityMonitor.State, state: SyncingActivityMonitor.State) { + childView.update(with: state) + } +} diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift b/DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift new file mode 100644 index 000000000..9c89ec932 --- /dev/null +++ b/DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift @@ -0,0 +1,156 @@ +// +// Created by PT +// Copyright © 2023 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 + +protocol SyncingAlertContentViewDelegate: AnyObject { + func syncingAlertContentView(_ view: SyncingAlertContentView, okButtonAction sender: UIButton) +} + +final class SyncingAlertContentView: UIView { + + private(set) var syncingImageView: UIImageView! + private(set) var titleLabel: UILabel! + private(set) var subtitleLabel: UILabel! + + weak var delegate: SyncingAlertContentViewDelegate? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.backgroundColor = UIColor.dw_background() + + syncingImageView = UIImageView(image: UIImage(named: "icon_syncing_large")) + syncingImageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(syncingImageView) + + titleLabel = UILabel() + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.font = UIFont.dw_font(forTextStyle: .title3) + titleLabel.textColor = UIColor.dw_darkTitle() + titleLabel.adjustsFontForContentSizeCategory = true + titleLabel.textAlignment = .center + titleLabel.numberOfLines = 0 + self.addSubview(titleLabel) + + subtitleLabel = UILabel() + subtitleLabel.translatesAutoresizingMaskIntoConstraints = false + subtitleLabel.font = UIFont.dw_font(forTextStyle: .callout) + subtitleLabel.textColor = UIColor.dw_secondaryText() + subtitleLabel.adjustsFontForContentSizeCategory = true + subtitleLabel.textAlignment = .center + subtitleLabel.numberOfLines = 0 + self.addSubview(subtitleLabel) + + let okButton = DWActionButton() + okButton.translatesAutoresizingMaskIntoConstraints = false + okButton.small = true + okButton.setTitle(NSLocalizedString("OK", comment: ""), for: .normal) + okButton.addTarget(self, action: #selector(okButtonAction(sender:)), for: .touchUpInside) + self.addSubview(okButton) + + syncingImageView.setContentCompressionResistancePriority(.required, for: .vertical) + syncingImageView.setContentCompressionResistancePriority(.required, for: .horizontal) + + titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) + subtitleLabel.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + + NSLayoutConstraint.activate([ + syncingImageView.topAnchor.constraint(equalTo: self.topAnchor), + syncingImageView.centerXAnchor.constraint(equalTo: self.centerXAnchor), + + titleLabel.topAnchor.constraint(equalTo: syncingImageView.bottomAnchor, constant: 16.0), + titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor), + + subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8.0), + subtitleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.trailingAnchor.constraint(equalTo: subtitleLabel.trailingAnchor), + + okButton.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 38.0), + okButton.centerXAnchor.constraint(equalTo: self.centerXAnchor), + self.bottomAnchor.constraint(equalTo: okButton.bottomAnchor), + okButton.heightAnchor.constraint(equalToConstant: 32.0), + okButton.widthAnchor.constraint(equalToConstant: 110.0), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func okButtonAction(sender: UIButton) { + delegate?.syncingAlertContentView(self, okButtonAction: sender) + } + + func update(with syncState: SyncingActivityMonitor.State) { + switch syncState { + case .syncing, .syncDone: + let environment = DWEnvironment.sharedInstance() + let chain = environment.currentChain + let chainManager = environment.currentChainManager + // We give a 6 block window, just in case a new block comes in + let atTheEndOfInitialTerminalBlocksAndSyncingMasternodeList = chain.lastTerminalBlockHeight >= chain.estimatedBlockHeight - 6 && chainManager.masternodeManager.masternodeListRetrievalQueueCount > 0 && chainManager.syncPhase == .initialTerminalBlocks + let atTheEndOfSyncBlocksAndSyncingMasternodeList = chain.lastSyncBlockHeight >= chain.estimatedBlockHeight - 6 && chainManager.masternodeManager.masternodeListRetrievalQueueCount > 0 && chainManager.syncPhase == .synced + if atTheEndOfInitialTerminalBlocksAndSyncingMasternodeList || atTheEndOfSyncBlocksAndSyncingMasternodeList { + subtitleLabel.text = String(format: NSLocalizedString("masternode list #%d of %d", comment: ""), (chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount - chainManager.masternodeManager.masternodeListRetrievalQueueCount), chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount) + } else { + if chainManager.syncPhase == .initialTerminalBlocks { + subtitleLabel.text = String(format: NSLocalizedString("header #%d of %d", comment: ""), chain.lastTerminalBlockHeight, chain.estimatedBlockHeight) + } else { + subtitleLabel.text = String(format: NSLocalizedString("block #%d of %d", comment: ""), chain.lastSyncBlockHeight, chain.estimatedBlockHeight) + } + } + + case .syncFailed: + subtitleLabel.text = NSLocalizedString("Sync Failed", comment: "") + + case .noConnection: + subtitleLabel.text = NSLocalizedString("Unable to connect", comment: "") + case .unknown: + break + } + + if syncState == .syncing { + showAnimation() + } else { + hideAnimation() + } + } + + func update(with progress: Double) { + titleLabel.text = String(format: "%@ %.1f%%", NSLocalizedString("Syncing", comment: ""), progress * 100.0) + } + + func showAnimation() { + if syncingImageView.layer.animation(forKey: "dw_rotationAnimation") != nil { + return + } + + let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + rotationAnimation.fromValue = 0 + rotationAnimation.toValue = Double.pi * 2.0 + rotationAnimation.duration = 1.75 + rotationAnimation.repeatCount = .infinity + syncingImageView.layer.add(rotationAnimation, forKey: "dw_rotationAnimation") + } + + func hideAnimation() { + syncingImageView.layer.removeAllAnimations() + } +} + diff --git a/DashWallet/Sources/UI/Home/Views/DWHomeView.m b/DashWallet/Sources/UI/Home/Views/DWHomeView.m index a7401b377..c48ceaedd 100644 --- a/DashWallet/Sources/UI/Home/Views/DWHomeView.m +++ b/DashWallet/Sources/UI/Home/Views/DWHomeView.m @@ -194,6 +194,10 @@ - (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:( //TODO: [headerView setSyncState:self.model.syncModel.state]; [headerView setProgress:self.model.syncModel.progress]; self.syncingHeaderView = headerView; + + self.syncingHeaderView.isSyncing = YES; + [self.syncingHeaderView setProgress:0.5]; + return headerView; } From c99e8fd4ec047563405836c425a2fe6308caec22 Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 11 May 2023 11:45:21 +0700 Subject: [PATCH 04/38] refactor: Rewrite 'DWSyncingHeaderView' in Swift with fewer external dependencies --- .../UI/Home/Views/DWSyncingHeaderView.h | 41 ------ .../UI/Home/Views/DWSyncingHeaderView.m | 131 ----------------- .../UI/Home/Views/SyncingHeaderView.swift | 133 ++++++++++++++++++ 3 files changed, 133 insertions(+), 172 deletions(-) delete mode 100644 DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.h delete mode 100644 DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.m create mode 100644 DashWallet/Sources/UI/Home/Views/SyncingHeaderView.swift diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.h b/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.h deleted file mode 100644 index 819fa05c9..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2021 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 DWSyncingHeaderView; - -@protocol DWSyncingHeaderViewDelegate - -- (void)syncingHeaderView:(DWSyncingHeaderView *)view filterButtonAction:(UIButton *)sender; -- (void)syncingHeaderView:(DWSyncingHeaderView *)view syncingButtonAction:(UIButton *)sender; - -@end - -@interface DWSyncingHeaderView : UIView - -@property (nullable, nonatomic, weak) id delegate; - -@property (assign, nonatomic) BOOL isSyncing; - -- (void)setProgress:(float)progress; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.m b/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.m deleted file mode 100644 index d108cde40..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWSyncingHeaderView.m +++ /dev/null @@ -1,131 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2021 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 "DWSyncingHeaderView.h" - -#import "DWButton.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWSyncingHeaderView () - -@property (strong, nonatomic) UIButton *syncingButton; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWSyncingHeaderView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - UILabel *titleLabel = [[UILabel alloc] init]; - titleLabel.translatesAutoresizingMaskIntoConstraints = NO; - titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; - titleLabel.text = NSLocalizedString(@"History", nil); - titleLabel.textColor = [UIColor dw_darkTitleColor]; - [titleLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh + 1 - forAxis:UILayoutConstraintAxisHorizontal]; - [self addSubview:titleLabel]; - - DWButton *syncingButton = [[DWButton alloc] init]; - syncingButton.translatesAutoresizingMaskIntoConstraints = NO; - syncingButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; - [syncingButton setTitleColor:[UIColor dw_darkTitleColor] forState:UIControlStateNormal]; - [syncingButton setContentHuggingPriority:UILayoutPriorityDefaultHigh - 1 - forAxis:UILayoutConstraintAxisHorizontal]; - [syncingButton setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 - forAxis:UILayoutConstraintAxisHorizontal]; - [syncingButton addTarget:self action:@selector(syncingButtonAction:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:syncingButton]; - _syncingButton = syncingButton; - - UIButton *filterButton = [UIButton buttonWithType:UIButtonTypeCustom]; - filterButton.translatesAutoresizingMaskIntoConstraints = NO; - [filterButton setImage:[UIImage imageNamed:@"icon_filter_button"] forState:UIControlStateNormal]; - [filterButton addTarget:self action:@selector(filterButtonAction:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:filterButton]; - - - const CGFloat padding = 16.0; - [NSLayoutConstraint activateConstraints:@[ - [titleLabel.topAnchor constraintEqualToAnchor:self.topAnchor - constant:padding], - [self.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor - constant:padding], - [titleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor - constant:padding], - - [syncingButton.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], - [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:syncingButton.bottomAnchor], - [syncingButton.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], - [syncingButton.leadingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor - constant:8.0], - [syncingButton.heightAnchor constraintEqualToConstant:44.0], - - [filterButton.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], - [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:filterButton.bottomAnchor], - [filterButton.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], - [filterButton.leadingAnchor constraintEqualToAnchor:syncingButton.trailingAnchor], - [self.trailingAnchor constraintEqualToAnchor:filterButton.trailingAnchor - constant:10.0], - [filterButton.heightAnchor constraintEqualToConstant:44.0], - [filterButton.widthAnchor constraintEqualToConstant:44.0], - ]]; - } - return self; -} - -- (void)setProgress:(float)progress { - NSString *percentString = [NSString stringWithFormat:@"%0.1f%%", progress * 100.0]; - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - [result beginEditing]; - - NSAttributedString *str1 = [[NSAttributedString alloc] - initWithString:[NSString stringWithFormat:@"%@ ", NSLocalizedString(@"Syncing", nil)] - attributes:@{ - NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody], - }]; - [result appendAttributedString:str1]; - - NSAttributedString *str2 = [[NSAttributedString alloc] - initWithString:percentString - attributes:@{ - NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline], - }]; - [result appendAttributedString:str2]; - - [result endEditing]; - - [self.syncingButton setAttributedTitle:result forState:UIControlStateNormal]; - self.syncingButton.hidden = !self.isSyncing; -} - -- (void)filterButtonAction:(UIButton *)sender { - [self.delegate syncingHeaderView:self filterButtonAction:sender]; -} - -- (void)syncingButtonAction:(UIButton *)sender { - [self.delegate syncingHeaderView:self syncingButtonAction:sender]; -} - -@end diff --git a/DashWallet/Sources/UI/Home/Views/SyncingHeaderView.swift b/DashWallet/Sources/UI/Home/Views/SyncingHeaderView.swift new file mode 100644 index 000000000..66c8fa58e --- /dev/null +++ b/DashWallet/Sources/UI/Home/Views/SyncingHeaderView.swift @@ -0,0 +1,133 @@ +// +// Created by PT +// Copyright © 2023 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: - SyncingHeaderViewDelegate + +@objc(DWSyncingHeaderViewDelegate) +protocol SyncingHeaderViewDelegate: AnyObject { + func syncingHeaderView(_ view: SyncingHeaderView, filterButtonAction sender: UIButton) + func syncingHeaderView(_ view: SyncingHeaderView, syncingButtonAction sender: UIButton) +} + +// MARK: - SyncingHeaderView + +@objc(DWSyncingHeaderView) +final class SyncingHeaderView: UITableViewHeaderFooterView { + weak var delegate: SyncingHeaderViewDelegate? + + var syncingButton: UIButton! + + var progress = 0.0 { + didSet { + refreshView() + } + } + + var isSyncing = false { + didSet { + refreshView() + } + } + + override init(reuseIdentifier: String?) { + super.init(reuseIdentifier: reuseIdentifier) + + backgroundColor = UIColor.dw_secondaryBackground() + + let titleLabel = UILabel() + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.font = UIFont.dw_font(forTextStyle: .headline) + titleLabel.text = NSLocalizedString("History", comment: "") + titleLabel.textColor = UIColor.dw_darkTitle() + titleLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) + addSubview(titleLabel) + + syncingButton = DWButton() + syncingButton.translatesAutoresizingMaskIntoConstraints = false + syncingButton.contentHorizontalAlignment = .right + syncingButton.setTitleColor(UIColor.dw_darkTitle(), for: .normal) + syncingButton.setContentHuggingPriority(.defaultHigh - 1, for: .horizontal) + syncingButton.setContentCompressionResistancePriority(.required - 1, for: .horizontal) + syncingButton.addTarget(self, action: #selector(syncingButtonAction(_:)), for: .touchUpInside) + addSubview(syncingButton) + + let filterButton = UIButton(type: .custom) + filterButton.translatesAutoresizingMaskIntoConstraints = false + filterButton.setImage(UIImage(named: "icon_filter_button"), for: .normal) + filterButton.addTarget(self, action: #selector(filterButtonAction(_:)), for: .touchUpInside) + addSubview(filterButton) + + let padding: CGFloat = 16.0 + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: padding), + bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: padding), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding), + + syncingButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + bottomAnchor.constraint(greaterThanOrEqualTo: syncingButton.bottomAnchor), + syncingButton.centerYAnchor.constraint(equalTo: centerYAnchor), + syncingButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 8.0), + syncingButton.heightAnchor.constraint(equalToConstant: 44.0), + + filterButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + bottomAnchor.constraint(greaterThanOrEqualTo: filterButton.bottomAnchor), + filterButton.centerYAnchor.constraint(equalTo: centerYAnchor), + filterButton.leadingAnchor.constraint(equalTo: syncingButton.trailingAnchor), + trailingAnchor.constraint(equalTo: filterButton.trailingAnchor, constant: 10.0), + filterButton.heightAnchor.constraint(equalToConstant: 44.0), + filterButton.widthAnchor.constraint(equalToConstant: 44.0), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc + func filterButtonAction(_ sender: UIButton) { + delegate?.syncingHeaderView(self, filterButtonAction: sender) + } + + @objc + func syncingButtonAction(_ sender: UIButton) { + delegate?.syncingHeaderView(self, syncingButtonAction: sender) + } +} + +extension SyncingHeaderView { + private func refreshView() { + syncingButton.isHidden = !isSyncing + + guard isSyncing else { + return + } + let percentString = String(format: "%0.1f%%", progress * 100.0) + + let result = NSMutableAttributedString() + + let str1 = NSAttributedString(string: String(format: "%@ ", NSLocalizedString("Syncing", comment: "")), + attributes: [NSAttributedString.Key.font: UIFont.dw_font(forTextStyle: .body)]) + result.append(str1) + + let str2 = NSAttributedString(string: percentString, attributes: [NSAttributedString.Key.font: UIFont.dw_font(forTextStyle: .headline)]) + result.append(str2) + + syncingButton.setAttributedTitle(result, for: .normal) + } +} From ac970d9e503563b5a61c38f8432dc458dd84ce1f Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 11 May 2023 12:18:25 +0700 Subject: [PATCH 05/38] refactor: Rewrite 'DWProgressView' in swift --- .../Sources/UI/Views/ProgressView.swift | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 DashWallet/Sources/UI/Views/ProgressView.swift diff --git a/DashWallet/Sources/UI/Views/ProgressView.swift b/DashWallet/Sources/UI/Views/ProgressView.swift new file mode 100644 index 000000000..db8733591 --- /dev/null +++ b/DashWallet/Sources/UI/Views/ProgressView.swift @@ -0,0 +1,127 @@ +// +// Created by PT +// Copyright © 2023 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 + +final class ProgressView: UIView { + private let kProgressAnimationKey = "DW_PROGRESS_ANIMATION_KEY" + private let kDelayBetweenPulse: CFTimeInterval = 4.0 + + private var greenLayer: CALayer! + private var blueLayer: CALayer! + private var animating = false + + var progress: Float = 0.0 { + didSet { + setProgress(progress, animated: false) + } + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + private func commonInit() { + backgroundColor = UIColor.dw_progressBackground() + layer.masksToBounds = true + + let greenLayer = CALayer() + greenLayer.backgroundColor = UIColor.dw_green().cgColor + layer.addSublayer(greenLayer) + self.greenLayer = greenLayer + + let blueLayer = CALayer() + blueLayer.backgroundColor = UIColor.dw_dashNavigationBlue().cgColor + layer.addSublayer(blueLayer) + self.blueLayer = blueLayer + } + + override func layoutSublayers(of layer: CALayer) { + super.layoutSublayers(of: layer) + if layer == self.layer { + greenLayer.frame = layer.bounds + blueLayer.frame = layer.bounds + let x = layer.bounds.width / 2.0 + let y = layer.bounds.height / 2.0 + // set 0 progress position + greenLayer.position = CGPoint(x: -x, y: y) + blueLayer.position = CGPoint(x: -x, y: y) + } + } + + func setProgress(_ progress: Float, animated: Bool) { + assert(progress >= 0.0 && progress <= 1.0, "Invalid progress") + self.progress = max(0.0, min(1.0, progress)) + + // use implicit animation + greenLayer.anchorPoint = CGPoint(x: 0.5 - Double(progress), y: 0.5) + + if progress == 1.0 { + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(progressAnimationIteration), object: nil) + animating = false + } else if progress > 0.0 && !animating { + animating = true + perform(#selector(progressAnimationIteration), with: nil, afterDelay: kDelayBetweenPulse) + } + } + + @objc + private func progressAnimationIteration() { + if !animating { + blueLayer.removeAnimation(forKey: kProgressAnimationKey) + return + } + + let anchorAnimationDuration: CFTimeInterval = 0.4 + let delayBeforeFadingOut: CFTimeInterval = 0.4 + let colorAnimationDuration: CFTimeInterval = 1.0 + + let anchorAnimation = CABasicAnimation(keyPath: "anchorPoint") + anchorAnimation.fromValue = NSValue(cgPoint: CGPoint(x: 0.5, y: 0.5)) + anchorAnimation.toValue = NSValue(cgPoint: CGPoint(x: 0.5 - Double(progress), y: 0.5)) + anchorAnimation.duration = anchorAnimationDuration + anchorAnimation.beginTime = 0.0 + anchorAnimation.isRemovedOnCompletion = false + anchorAnimation.fillMode = .forwards + anchorAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) + + let colorAnimation = CABasicAnimation(keyPath: "backgroundColor") + colorAnimation.fromValue = UIColor.dw_dashNavigationBlue().cgColor + colorAnimation.toValue = UIColor.dw_green().cgColor + colorAnimation.duration = colorAnimationDuration + colorAnimation.beginTime = anchorAnimationDuration + delayBeforeFadingOut + colorAnimation.fillMode = .forwards + + let groupAnimation = CAAnimationGroup() + groupAnimation.animations = [anchorAnimation, colorAnimation] + groupAnimation.duration = anchorAnimationDuration + + delayBeforeFadingOut + + colorAnimationDuration + + Double(kDelayBetweenPulse) + + blueLayer.add(groupAnimation, forKey: kProgressAnimationKey) + + perform(#selector(progressAnimationIteration), with: nil, afterDelay: kDelayBetweenPulse) + } +} + From 826c26790e5576773b2cf2bffd8a4942069f4aea Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 11 May 2023 13:44:00 +0700 Subject: [PATCH 06/38] refactor: Rewrite 'DWBadgeView' in swift --- DashWallet/Sources/UI/Views/BadgeView.swift | 87 +++++++++++++ .../UI/Views/SharedViews/DWBadgeView.h | 30 ----- .../UI/Views/SharedViews/DWBadgeView.m | 121 ------------------ 3 files changed, 87 insertions(+), 151 deletions(-) create mode 100644 DashWallet/Sources/UI/Views/BadgeView.swift delete mode 100644 DashWallet/Sources/UI/Views/SharedViews/DWBadgeView.h delete mode 100644 DashWallet/Sources/UI/Views/SharedViews/DWBadgeView.m diff --git a/DashWallet/Sources/UI/Views/BadgeView.swift b/DashWallet/Sources/UI/Views/BadgeView.swift new file mode 100644 index 000000000..787d1db5c --- /dev/null +++ b/DashWallet/Sources/UI/Views/BadgeView.swift @@ -0,0 +1,87 @@ +// +// Created by PT +// Copyright © 2023 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 + +final class BadgeView: UIView { + + private(set) var label: UILabel! + var text: String? { + get { label.text } + set { + label.text = newValue + invalidateIntrinsicContentSize() + } + } + + var font: UIFont? { + get { label.font } + set { label.font = newValue } + } + + var textColor: UIColor? { + get { self.label.textColor } + set { self.label.textColor = newValue } + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupBadgeView() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupBadgeView() + } + + override func layoutSubviews() { + super.layoutSubviews() + layer.cornerRadius = bounds.height / 2.0 + } + + override var backgroundColor: UIColor? { + didSet { + label.backgroundColor = backgroundColor + } + } + + private func setupBadgeView() { + backgroundColor = .dw_tint() + layer.masksToBounds = true + isUserInteractionEnabled = false + + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.backgroundColor = backgroundColor + label.font = UIFont.dw_font(forTextStyle: UIFont.TextStyle.caption1) + label.adjustsFontForContentSizeCategory = true + label.textColor = UIColor.dw_dashBlue() + label.textAlignment = .center + addSubview(label) + self.label = label + + let verticalPadding: CGFloat = 5.0 + let horizontalPadding: CGFloat = 14.0 + + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: topAnchor, constant: verticalPadding), + label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalPadding), + bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: verticalPadding), + trailingAnchor.constraint(equalTo: label.trailingAnchor, constant: horizontalPadding), + ]) + } +} diff --git a/DashWallet/Sources/UI/Views/SharedViews/DWBadgeView.h b/DashWallet/Sources/UI/Views/SharedViews/DWBadgeView.h deleted file mode 100644 index c6249fd8e..000000000 --- a/DashWallet/Sources/UI/Views/SharedViews/DWBadgeView.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2020 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 - -@interface DWBadgeView : UIView - -@property (nullable, nonatomic, copy) NSString *text; -@property (nullable, nonatomic, strong) UIFont *font; -@property (nullable, nonatomic, strong) UIColor *textColor; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Views/SharedViews/DWBadgeView.m b/DashWallet/Sources/UI/Views/SharedViews/DWBadgeView.m deleted file mode 100644 index 19b412b31..000000000 --- a/DashWallet/Sources/UI/Views/SharedViews/DWBadgeView.m +++ /dev/null @@ -1,121 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2020 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 "DWBadgeView.h" - -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWBadgeView () - -@property (readonly, nonatomic, strong) UILabel *label; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWBadgeView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setup_badgeView]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setup_badgeView]; - } - return self; -} - -- (void)layoutSubviews { - [super layoutSubviews]; - - self.layer.cornerRadius = floor(CGRectGetHeight(self.bounds) / 2.0); -} - -- (void)setBackgroundColor:(UIColor *)backgroundColor { - [super setBackgroundColor:backgroundColor]; - - self.label.backgroundColor = backgroundColor; -} - -- (NSString *)text { - return self.label.text; -} - -- (void)setText:(NSString *)text { - self.label.text = text; - [self invalidateIntrinsicContentSize]; -} - -- (UIFont *)font { - return self.label.font; -} - -- (void)setFont:(UIFont *)font { - self.label.font = font; -} - -- (UIColor *)textColor { - return self.label.textColor; -} - -- (void)setTextColor:(UIColor *)textColor { - self.label.textColor = textColor; -} - -#pragma mark - Private - -- (void)setup_badgeView { - self.backgroundColor = [UIColor dw_tintColor]; - - self.layer.masksToBounds = YES; - - self.userInteractionEnabled = NO; - - UILabel *label = [[UILabel alloc] init]; - label.translatesAutoresizingMaskIntoConstraints = NO; - label.backgroundColor = self.backgroundColor; - label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCaption1]; - label.adjustsFontForContentSizeCategory = YES; - label.textColor = [UIColor dw_dashBlueColor]; - label.textAlignment = NSTextAlignmentCenter; - [self addSubview:label]; - _label = label; - - const CGFloat verticalPadding = 5.0; - const CGFloat horizontalPadding = 14.0; - - [NSLayoutConstraint activateConstraints:@[ - [label.topAnchor constraintEqualToAnchor:self.topAnchor - constant:verticalPadding], - [label.leadingAnchor constraintEqualToAnchor:self.leadingAnchor - constant:horizontalPadding], - [self.bottomAnchor constraintEqualToAnchor:label.bottomAnchor - constant:verticalPadding], - [self.trailingAnchor constraintEqualToAnchor:label.trailingAnchor - constant:horizontalPadding], - ]]; -} - -@end From 2f4a390c2b12ae75fb9c196fbffb2c200d13b51f Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 11 May 2023 13:49:44 +0700 Subject: [PATCH 07/38] refactor: Rewrite 'DashPayProfileView' in swift --- .../UI/Home/Views/DWDashPayProfileView.h | 31 ----- .../UI/Home/Views/DWDashPayProfileView.m | 116 ------------------ .../UI/Home/Views/DashPayProfileView.swift | 106 ++++++++++++++++ 3 files changed, 106 insertions(+), 147 deletions(-) delete mode 100644 DashWallet/Sources/UI/Home/Views/DWDashPayProfileView.h delete mode 100644 DashWallet/Sources/UI/Home/Views/DWDashPayProfileView.m create mode 100644 DashWallet/Sources/UI/Home/Views/DashPayProfileView.swift diff --git a/DashWallet/Sources/UI/Home/Views/DWDashPayProfileView.h b/DashWallet/Sources/UI/Home/Views/DWDashPayProfileView.h deleted file mode 100644 index 0f2533501..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWDashPayProfileView.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2020 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 - -@interface DWDashPayProfileView : UIControl - -@property (nullable, nonatomic, copy) NSString *username; -@property (nonatomic, assign) NSUInteger unreadCount; - -- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/DWDashPayProfileView.m b/DashWallet/Sources/UI/Home/Views/DWDashPayProfileView.m deleted file mode 100644 index d2e4824d9..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWDashPayProfileView.m +++ /dev/null @@ -1,116 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2020 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 "DWDashPayProfileView.h" - -#import "DWBadgeView.h" -#import "DWDPAvatarView.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -static CGSize const AVATAR_SIZE = {72.0, 72.0}; - -@interface DWDashPayProfileView () - -@property (readonly, nonatomic, strong) UIView *contentView; -@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; -@property (readonly, nonatomic, strong) UIImageView *bellImageView; -@property (readonly, nonatomic, strong) DWBadgeView *badgeView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDashPayProfileView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_dashBlueColor]; - - UIView *contentView = [[UIView alloc] init]; - contentView.translatesAutoresizingMaskIntoConstraints = NO; - contentView.backgroundColor = self.backgroundColor; - contentView.userInteractionEnabled = NO; - [self addSubview:contentView]; - _contentView = contentView; - - DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] init]; - avatarView.translatesAutoresizingMaskIntoConstraints = NO; - avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; - avatarView.userInteractionEnabled = NO; - [contentView addSubview:avatarView]; - _avatarView = avatarView; - - UIImageView *bellImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_bell"]]; - bellImageView.translatesAutoresizingMaskIntoConstraints = NO; - bellImageView.userInteractionEnabled = NO; - [contentView addSubview:bellImageView]; - _bellImageView = bellImageView; - - DWBadgeView *badgeView = [[DWBadgeView alloc] initWithFrame:CGRectZero]; - badgeView.translatesAutoresizingMaskIntoConstraints = NO; - badgeView.hidden = YES; - [contentView addSubview:badgeView]; - _badgeView = badgeView; - - [NSLayoutConstraint activateConstraints:@[ - [contentView.topAnchor constraintEqualToAnchor:self.topAnchor], - [contentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [contentView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], - [contentView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], - - [avatarView.topAnchor constraintEqualToAnchor:contentView.topAnchor], - [avatarView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], - [avatarView.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], - [avatarView.widthAnchor constraintEqualToConstant:AVATAR_SIZE.width], - [avatarView.heightAnchor constraintEqualToConstant:AVATAR_SIZE.height], - - [bellImageView.trailingAnchor constraintEqualToAnchor:avatarView.trailingAnchor], - [bellImageView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], - - [badgeView.centerXAnchor constraintEqualToAnchor:avatarView.trailingAnchor], - [badgeView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], - ]]; - } - return self; -} - - -- (void)setHighlighted:(BOOL)highlighted { - [super setHighlighted:highlighted]; - - [self.contentView dw_pressedAnimation:DWPressedAnimationStrength_Medium pressed:highlighted]; -} - -- (void)setUsername:(NSString *)username { - _username = username; - - self.avatarView.username = username; -} - -- (void)setUnreadCount:(NSUInteger)unreadCount { - _unreadCount = unreadCount; - - self.badgeView.text = [NSString stringWithFormat:@"%ld", unreadCount]; - - self.bellImageView.hidden = unreadCount > 0; - self.badgeView.hidden = unreadCount == 0; -} - -@end diff --git a/DashWallet/Sources/UI/Home/Views/DashPayProfileView.swift b/DashWallet/Sources/UI/Home/Views/DashPayProfileView.swift new file mode 100644 index 000000000..5978fab68 --- /dev/null +++ b/DashWallet/Sources/UI/Home/Views/DashPayProfileView.swift @@ -0,0 +1,106 @@ +// +// Created by PT +// Copyright © 2023 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 + +import UIKit + +let AVATAR_SIZE = CGSize(width: 72.0, height: 72.0) + +// MARK: - DashPayProfileView + +class DashPayProfileView: UIView { + private(set) var contentView: UIView + private(set) var avatarView: DWDPAvatarView + private(set) var bellImageView: UIImageView + private(set) var badgeView: BadgeView + + var username: String? { + didSet { + avatarView.username = username + } + } + + var unreadCount = 0 { + didSet { + badgeView.text = "\(unreadCount)" + bellImageView.isHidden = unreadCount > 0 + badgeView.isHidden = unreadCount == 0 + } + } + + var isHighlighted = false { + didSet { + self.contentView.dw_pressedAnimation(.medium, pressed: isHighlighted) + } + } + + override init(frame: CGRect) { + contentView = UIView() + avatarView = DWDPAvatarView() + bellImageView = UIImageView(image: UIImage(named: "icon_bell")) + + badgeView = BadgeView() + + super.init(frame: frame) + + backgroundColor = UIColor.dw_dashBlue() + + contentView.translatesAutoresizingMaskIntoConstraints = false + contentView.backgroundColor = backgroundColor + contentView.isUserInteractionEnabled = false + addSubview(contentView) + + avatarView.translatesAutoresizingMaskIntoConstraints = false + avatarView.backgroundMode = .random + avatarView.isUserInteractionEnabled = false + contentView.addSubview(avatarView) + + bellImageView.translatesAutoresizingMaskIntoConstraints = false + bellImageView.isUserInteractionEnabled = false + contentView.addSubview(bellImageView) + + badgeView.translatesAutoresizingMaskIntoConstraints = false + badgeView.isHidden = true + contentView.addSubview(badgeView) + + NSLayoutConstraint.activate([ + contentView.topAnchor.constraint(equalTo: topAnchor), + contentView.leadingAnchor.constraint(equalTo: leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: bottomAnchor), + + avatarView.topAnchor.constraint(equalTo: contentView.topAnchor), + avatarView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + avatarView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + avatarView.widthAnchor.constraint(equalToConstant: AVATAR_SIZE.width), + avatarView.heightAnchor.constraint(equalToConstant: AVATAR_SIZE.height), + + bellImageView.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), + bellImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + + badgeView.centerXAnchor.constraint(equalTo: avatarView.trailingAnchor), + badgeView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + ]) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} From 7c45e7b0430ecd7c2639bbdbf1c8fbf6d102a56b Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 11 May 2023 15:20:26 +0700 Subject: [PATCH 08/38] refactor: Rewrite 'DWSyncView' in swift --- DashWallet/Sources/UI/Home/Views/DWSyncView.h | 48 ----- DashWallet/Sources/UI/Home/Views/DWSyncView.m | 181 ------------------ .../Sources/UI/Home/Views/DWSyncView.xib | 34 ++-- .../Sources/UI/Home/Views/SyncView.swift | 167 ++++++++++++++++ 4 files changed, 183 insertions(+), 247 deletions(-) delete mode 100644 DashWallet/Sources/UI/Home/Views/DWSyncView.h delete mode 100644 DashWallet/Sources/UI/Home/Views/DWSyncView.m create mode 100644 DashWallet/Sources/UI/Home/Views/SyncView.swift diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncView.h b/DashWallet/Sources/UI/Home/Views/DWSyncView.h deleted file mode 100644 index 90cdebc46..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWSyncView.h +++ /dev/null @@ -1,48 +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 - - -typedef NS_ENUM(NSUInteger, DWSyncViewState) { - DWSyncViewState_Syncing, - DWSyncViewState_SyncDone, - DWSyncViewState_SyncFailed, - DWSyncViewState_NoConnection, -}; - - -@class DWSyncView; - -@protocol DWSyncViewDelegate - -- (void)syncViewRetryButtonAction:(DWSyncView *)view; - -@end - -@interface DWSyncView : UIView - -@property (nullable, nonatomic, weak) id delegate; - -- (void)setSyncState:(DWSyncViewState)state; -- (void)setProgress:(float)progress animated:(BOOL)animated; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncView.m b/DashWallet/Sources/UI/Home/Views/DWSyncView.m deleted file mode 100644 index ec2530fda..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWSyncView.m +++ /dev/null @@ -1,181 +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 "DWSyncView.h" - -#import "DWEnvironment.h" -#import "DWProgressView.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWSyncView () - -@property (weak, nonatomic) IBOutlet UIView *contentView; -@property (strong, nonatomic) IBOutlet UIView *roundedView; -@property (strong, nonatomic) IBOutlet UILabel *titleLabel; -@property (strong, nonatomic) IBOutlet UILabel *descriptionLabel; -@property (strong, nonatomic) IBOutlet UILabel *percentLabel; -@property (strong, nonatomic) IBOutlet UIButton *retryButton; -@property (strong, nonatomic) IBOutlet DWProgressView *progressView; -@property (assign, nonatomic) BOOL viewStateSeeingBlocks; -@property (assign, nonatomic) DWSyncViewState syncState; - -@end - -@implementation DWSyncView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self commonInit]; - } - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super initWithCoder:aDecoder]; - if (self) { - [self commonInit]; - } - return self; -} - -- (void)commonInit { - [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil]; - [self addSubview:self.contentView]; - self.contentView.translatesAutoresizingMaskIntoConstraints = NO; - [NSLayoutConstraint activateConstraints:@[ - [self.contentView.topAnchor constraintEqualToAnchor:self.topAnchor], - [self.contentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [self.contentView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], - [self.contentView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], - [self.contentView.widthAnchor constraintEqualToAnchor:self.widthAnchor], - ]]; - - self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - self.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; - self.descriptionLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; - self.percentLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleTitle1]; - self.viewStateSeeingBlocks = NO; - - UITapGestureRecognizer *tapGestureRecognizer = - [[UITapGestureRecognizer alloc] initWithTarget:self - action:@selector(changeSeeBlocksStateAction:)]; - [self.roundedView addGestureRecognizer:tapGestureRecognizer]; -} - -- (void)setSyncState:(DWSyncViewState)state { - _syncState = state; - if (state == DWSyncViewState_NoConnection) { - self.titleLabel.textColor = [UIColor dw_lightTitleColor]; - self.descriptionLabel.textColor = [UIColor dw_lightTitleColor]; - } - else { - self.titleLabel.textColor = [UIColor dw_secondaryTextColor]; - self.descriptionLabel.textColor = [UIColor dw_quaternaryTextColor]; - } - - switch (state) { - case DWSyncViewState_Syncing: - case DWSyncViewState_SyncDone: { - self.roundedView.backgroundColor = [UIColor dw_backgroundColor]; - self.percentLabel.hidden = NO; - self.retryButton.hidden = YES; - self.progressView.hidden = NO; - self.titleLabel.text = NSLocalizedString(@"Syncing", nil); - [self updateUIForViewStateSeeingBlocks]; - break; - } - case DWSyncViewState_SyncFailed: { - self.roundedView.backgroundColor = [UIColor dw_backgroundColor]; - self.percentLabel.hidden = YES; - self.retryButton.tintColor = [UIColor dw_redColor]; - self.retryButton.hidden = NO; - self.progressView.hidden = NO; - self.titleLabel.text = NSLocalizedString(@"Sync Failed", nil); - self.descriptionLabel.text = NSLocalizedString(@"Please try again", nil); - - break; - } - case DWSyncViewState_NoConnection: { - self.roundedView.backgroundColor = [UIColor dw_redColor]; - self.percentLabel.hidden = YES; - self.retryButton.tintColor = [UIColor dw_backgroundColor]; - self.retryButton.hidden = NO; - self.progressView.hidden = YES; - self.titleLabel.text = NSLocalizedString(@"Unable to connect", nil); - self.descriptionLabel.text = NSLocalizedString(@"Check your connection", nil); - - break; - } - } -} - -- (void)setProgress:(float)progress animated:(BOOL)animated { - self.percentLabel.text = [NSString stringWithFormat:@"%0.1f%%", progress * 100.0]; - [self.progressView setProgress:progress animated:animated]; - if (self.viewStateSeeingBlocks && self.syncState == DWSyncViewState_Syncing) { - [self updateUIForViewStateSeeingBlocks]; - } -} - -- (void)updateUIForViewStateSeeingBlocks { - if (self.syncState == DWSyncViewState_Syncing || self.syncState == DWSyncViewState_SyncDone) { - if (self.viewStateSeeingBlocks) { - DWEnvironment *environment = [DWEnvironment sharedInstance]; - DSChain *chain = environment.currentChain; - DSChainManager *chainManager = environment.currentChainManager; - if (chainManager.syncPhase == DSChainSyncPhase_InitialTerminalBlocks) { - if (chain.lastTerminalBlockHeight >= chain.estimatedBlockHeight && chainManager.masternodeManager.masternodeListRetrievalQueueCount) { - self.descriptionLabel.text = [NSString stringWithFormat:NSLocalizedString(@"masternode list #%d of %d", nil), - (int)(chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount - chainManager.masternodeManager.masternodeListRetrievalQueueCount), - (int)chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount]; - } - else { - self.descriptionLabel.text = [NSString stringWithFormat:NSLocalizedString(@"header #%d of %d", nil), - chain.lastTerminalBlockHeight, - chain.estimatedBlockHeight]; - } - } - else { - self.descriptionLabel.text = [NSString stringWithFormat:NSLocalizedString(@"block #%d of %d", nil), - chain.lastSyncBlockHeight, - chain.estimatedBlockHeight]; - } - } - else { - self.descriptionLabel.text = NSLocalizedString(@"with Dash blockchain", nil); - } - } -} - -#pragma mark - Actions - -- (void)changeSeeBlocksStateAction:(id)sender { - self.viewStateSeeingBlocks = !self.viewStateSeeingBlocks; - [self updateUIForViewStateSeeingBlocks]; -} - -- (IBAction)retryButtonAction:(id)sender { - [self.delegate syncViewRetryButtonAction:self]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncView.xib b/DashWallet/Sources/UI/Home/Views/DWSyncView.xib index 6c9c7111d..8939f06e9 100644 --- a/DashWallet/Sources/UI/Home/Views/DWSyncView.xib +++ b/DashWallet/Sources/UI/Home/Views/DWSyncView.xib @@ -1,26 +1,16 @@ - + - + - - - - - - - - - - - + - + @@ -65,14 +55,14 @@ - + - @@ -133,13 +123,21 @@ + + + + + + + + - + diff --git a/DashWallet/Sources/UI/Home/Views/SyncView.swift b/DashWallet/Sources/UI/Home/Views/SyncView.swift new file mode 100644 index 000000000..57e5a34ff --- /dev/null +++ b/DashWallet/Sources/UI/Home/Views/SyncView.swift @@ -0,0 +1,167 @@ +// +// Created by PT +// Copyright © 2023 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: - SyncViewDelegate + +protocol SyncViewDelegate: AnyObject { + func syncViewRetryButtonAction(_ view: SyncView) +} + +// MARK: - SyncView + +final class SyncView: UIView { + weak var delegate: SyncViewDelegate? + + var hasNetwork = true { + didSet { + updateView() + } + } + + var syncState: SyncingActivityMonitor.State = .unknown { + didSet { + updateView() + } + } + + var viewStateSeeingBlocks = false + + @IBOutlet var roundedView: UIView! + @IBOutlet var titleLabel: UILabel! + @IBOutlet var descriptionLabel: UILabel! + @IBOutlet var percentLabel: UILabel! + @IBOutlet var retryButton: UIButton! + @IBOutlet var progressView: ProgressView! + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + private func commonInit() { + backgroundColor = UIColor.dw_secondaryBackground() + + titleLabel.font = UIFont.dw_font(forTextStyle: .subheadline) + descriptionLabel.font = UIFont.dw_font(forTextStyle: .footnote) + percentLabel.font = UIFont.dw_font(forTextStyle: .title1) + viewStateSeeingBlocks = false + + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(changeSeeBlocksStateAction(_:))) + roundedView.addGestureRecognizer(tapGestureRecognizer) + } + + func set(progress: Float, animated: Bool) { + percentLabel.text = String(format: "%.1f%%", progress * 100.0) + progressView.setProgress(progress, animated: animated) + + if viewStateSeeingBlocks && syncState == .syncing { + updateUIForViewStateSeeingBlocks() + } + } + + func updateUIForViewStateSeeingBlocks() { + // TODO: Don't access DashSync directly + if syncState == .syncing || syncState == .syncDone { + if viewStateSeeingBlocks { + let environment = DWEnvironment.sharedInstance() + let chain = environment.currentChain + let chainManager = environment.currentChainManager + if chainManager.syncPhase == .initialTerminalBlocks { + if chain.lastTerminalBlockHeight >= chain.estimatedBlockHeight && chainManager.masternodeManager.masternodeListRetrievalQueueCount != 0 { + descriptionLabel.text = String(format: NSLocalizedString("masternode list #%d of %d", comment: ""), + chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount - chainManager.masternodeManager + .masternodeListRetrievalQueueCount, + chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount) + } + else { + descriptionLabel.text = String(format: NSLocalizedString("header #%d of %d", comment: ""), + chain.lastTerminalBlockHeight, + chain.estimatedBlockHeight) + } + } + else { + descriptionLabel.text = String(format: NSLocalizedString("block #%d of %d", comment: ""), + chain.lastSyncBlockHeight, + chain.estimatedBlockHeight) + } + } + else { + descriptionLabel.text = NSLocalizedString("with Dash blockchain", comment: "") + } + } + } + + @objc + func changeSeeBlocksStateAction(_ sender: Any) { + viewStateSeeingBlocks.toggle() + updateUIForViewStateSeeingBlocks() + } + + @IBAction + func retryButtonAction(_ sender: Any) { + delegate?.syncViewRetryButtonAction(self) + } + +} + +extension SyncView { + private func updateView() { + switch syncState { + case .syncing, .syncDone: + roundedView.backgroundColor = UIColor.dw_background() + percentLabel.isHidden = false + retryButton.isHidden = true + progressView.isHidden = false + titleLabel.text = NSLocalizedString("Syncing", comment: "") + updateUIForViewStateSeeingBlocks() + + case .syncFailed: + roundedView.backgroundColor = UIColor.dw_background() + percentLabel.isHidden = true + retryButton.tintColor = UIColor.dw_red() + retryButton.isHidden = false + progressView.isHidden = false + titleLabel.text = NSLocalizedString("Sync Failed", comment: "") + descriptionLabel.text = NSLocalizedString("Please try again", comment: "") + default: + break + } + + if hasNetwork { + titleLabel.textColor = UIColor.dw_secondaryText() + descriptionLabel.textColor = UIColor.dw_quaternaryText() + } else { + titleLabel.textColor = UIColor.dw_lightTitle() + descriptionLabel.textColor = UIColor.dw_lightTitle() + + roundedView.backgroundColor = UIColor.dw_red() + percentLabel.isHidden = true + retryButton.tintColor = UIColor.dw_background() + retryButton.isHidden = false + progressView.isHidden = true + titleLabel.text = NSLocalizedString("Unable to connect", comment: "") + descriptionLabel.text = NSLocalizedString("Check your connection", comment: "") + } + } +} From 8a702a176e617c1b3f2ee92b3e21771fc81c8eac Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 11 May 2023 16:29:21 +0700 Subject: [PATCH 09/38] chore: Swift code formatting --- .../OrderPreviewViewController.swift | 2 +- .../TransferAmountViewController.swift | 2 +- .../Sources/UI/CrowdNode/CrowdNodeModel.swift | 2 +- .../GettingStartedViewController.swift | 2 +- .../SyncingAlertViewController.swift | 30 +++--- .../View/SyncingAlertContentView.swift | 77 ++++++++------- .../Shortcuts/Models/ShortcutAction.swift | 30 +++--- .../Shortcuts/Models/ShortcutsModel.swift | 32 +++--- .../Home/Views/Shortcuts/ShortcutCell.swift | 12 +-- .../Home/Views/Shortcuts/ShortcutsView.swift | 98 +++++++++++-------- .../ExtendedPublicKeysModel.swift | 12 ++- .../DerivationPathKeysViewController.swift | 48 +++++---- .../Models/DerivationPathKeysModel.swift | 62 ++++++------ .../Views/DerivationPathKeysCell.swift | 26 ++--- .../Views/DerivationPathKeysHeaderView.swift | 18 ++-- .../Overview/Cell/KeysOverviewCell.swift | 24 ++--- .../Overview/KeysOverviewViewController.swift | 36 ++++--- .../Model/WalletKeysOverviewModel.swift | 20 ++-- .../FailedOperationStatusViewController.swift | 6 +- .../ProvideAmountViewController.swift | 4 +- .../Amount/SendAmountViewController.swift | 15 +-- .../Payments/Pay/Cells/PayTableViewCell.swift | 8 +- .../UI/Payments/Pay/PayViewController.swift | 58 ++++++----- .../Payments/Pay/PayableViewController.swift | 20 ++-- .../UI/Payments/PaymentsViewController.swift | 82 +++++++++------- .../Receive/ReceiveViewController.swift | 66 +++++++------ .../Receive/Views/ReceiveContentView.swift | 62 +++++++----- .../Payments/SendReceivePageController.swift | 55 ++++++----- .../BackupInfo/BackupInfoViewController.swift | 98 +++++++++++-------- .../BackupInfo/Views/BackupInfoItemView.swift | 6 +- .../VerifiedSuccessfullyViewController.swift | 12 ++- DashWallet/Sources/UI/Views/BadgeView.swift | 3 + DashWallet/Sources/UI/Views/EmptyView.swift | 8 +- .../Navigation/BaseNavigationController.swift | 19 ++-- .../Keyboard/NumberKeyboardButton.swift | 10 +- 35 files changed, 600 insertions(+), 465 deletions(-) diff --git a/DashWallet/Sources/UI/Coinbase/Order Preview/OrderPreviewViewController.swift b/DashWallet/Sources/UI/Coinbase/Order Preview/OrderPreviewViewController.swift index d5542da60..52fd1ed87 100644 --- a/DashWallet/Sources/UI/Coinbase/Order Preview/OrderPreviewViewController.swift +++ b/DashWallet/Sources/UI/Coinbase/Order Preview/OrderPreviewViewController.swift @@ -79,7 +79,7 @@ class OrderPreviewViewController: BaseViewController, NetworkReachabilityHandlin Cryptocurrency markets are volatile, and this allows us to temporarily lock in a price for trade execution. """, comment: "Coinbase/Buy Dash/Fee Info") vc.actionButtonText = NSLocalizedString("Learn More...", comment: "Coinbase") - + let nvc = BaseNavigationController(rootViewController: vc) present(nvc, animated: true) } diff --git a/DashWallet/Sources/UI/Coinbase/Transfer Amount/TransferAmountViewController.swift b/DashWallet/Sources/UI/Coinbase/Transfer Amount/TransferAmountViewController.swift index 0fd34399b..4b0933ea5 100644 --- a/DashWallet/Sources/UI/Coinbase/Transfer Amount/TransferAmountViewController.swift +++ b/DashWallet/Sources/UI/Coinbase/Transfer Amount/TransferAmountViewController.swift @@ -43,7 +43,7 @@ final class TransferAmountViewController: CoinbaseAmountViewController, Converte override func actionButtonAction(sender: UIView) { showActivityIndicator() - + if transferModel.direction == .toCoinbase { checkLeftoverBalance { [weak self] canContinue in guard canContinue, let wSelf = self else { self?.hideActivityIndicator(); return } diff --git a/DashWallet/Sources/UI/CrowdNode/CrowdNodeModel.swift b/DashWallet/Sources/UI/CrowdNode/CrowdNodeModel.swift index 656e67324..89dd55df8 100644 --- a/DashWallet/Sources/UI/CrowdNode/CrowdNodeModel.swift +++ b/DashWallet/Sources/UI/CrowdNode/CrowdNodeModel.swift @@ -129,7 +129,7 @@ final class CrowdNodeModel { onlineAccountState = crowdNode.onlineAccountState observeState() observeBalances() - + crowdNode.restoreState() getAccountAddress() } diff --git a/DashWallet/Sources/UI/CrowdNode/Getting Started/GettingStartedViewController.swift b/DashWallet/Sources/UI/CrowdNode/Getting Started/GettingStartedViewController.swift index cdac8d49a..618586337 100644 --- a/DashWallet/Sources/UI/CrowdNode/Getting Started/GettingStartedViewController.swift +++ b/DashWallet/Sources/UI/CrowdNode/Getting Started/GettingStartedViewController.swift @@ -140,7 +140,7 @@ extension GettingStartedViewController { } } -// MARK: DWSecureWalletDelegate +// MARK: BackupInfoViewControllerDelegate extension GettingStartedViewController: BackupInfoViewControllerDelegate { private func backupPassphrase() { diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift b/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift index 9d48ce611..4ecd3a575 100644 --- a/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift +++ b/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,9 +17,11 @@ import UIKit +// MARK: - SyncingAlertViewController + @objc(DWSyncingAlertViewController) final class SyncingAlertViewController: BaseViewController, SyncingAlertContentViewDelegate { - + var modalTransition = DWModalPopupTransition() private lazy var childView: SyncingAlertContentView = { let childView = SyncingAlertContentView() @@ -32,8 +34,8 @@ final class SyncingAlertViewController: BaseViewController, SyncingAlertContentV init() { super.init(nibName: nil, bundle: nil) - self.transitioningDelegate = modalTransition - self.modalPresentationStyle = .custom + transitioningDelegate = modalTransition + modalPresentationStyle = .custom } required init?(coder: NSCoder) { @@ -42,20 +44,20 @@ final class SyncingAlertViewController: BaseViewController, SyncingAlertContentV override func viewDidLoad() { super.viewDidLoad() - + SyncingActivityMonitor.shared.add(observer: self) - + view.backgroundColor = .clear - + let contentView = UIView() contentView.translatesAutoresizingMaskIntoConstraints = false contentView.backgroundColor = UIColor.dw_background() contentView.layer.cornerRadius = 8.0 contentView.layer.masksToBounds = true view.addSubview(contentView) - + contentView.addSubview(childView) - + NSLayoutConstraint.activate([ contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor), contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor), @@ -66,23 +68,25 @@ final class SyncingAlertViewController: BaseViewController, SyncingAlertContentV contentView.bottomAnchor.constraint(equalTo: childView.bottomAnchor, constant: 32.0), ]) } - + // MARK: - DWSyncingAlertContentViewDelegate - + func syncingAlertContentView(_ view: SyncingAlertContentView, okButtonAction sender: UIButton) { dismiss(animated: true, completion: nil) } - + deinit { SyncingActivityMonitor.shared.remove(observer: self) } } +// MARK: SyncingActivityMonitorObserver + extension SyncingAlertViewController: SyncingActivityMonitorObserver { func syncingActivityMonitorProgressDidChange(_ progress: Double) { childView.update(with: progress) } - + func syncingActivityMonitorStateDidChange(previousState: SyncingActivityMonitor.State, state: SyncingActivityMonitor.State) { childView.update(with: state) } diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift b/DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift index 9c89ec932..655d74ce8 100644 --- a/DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift +++ b/DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,27 +17,31 @@ import UIKit +// MARK: - SyncingAlertContentViewDelegate + protocol SyncingAlertContentViewDelegate: AnyObject { func syncingAlertContentView(_ view: SyncingAlertContentView, okButtonAction sender: UIButton) } +// MARK: - SyncingAlertContentView + final class SyncingAlertContentView: UIView { private(set) var syncingImageView: UIImageView! private(set) var titleLabel: UILabel! private(set) var subtitleLabel: UILabel! - + weak var delegate: SyncingAlertContentViewDelegate? override init(frame: CGRect) { super.init(frame: frame) - - self.backgroundColor = UIColor.dw_background() - + + backgroundColor = UIColor.dw_background() + syncingImageView = UIImageView(image: UIImage(named: "icon_syncing_large")) syncingImageView.translatesAutoresizingMaskIntoConstraints = false - self.addSubview(syncingImageView) - + addSubview(syncingImageView) + titleLabel = UILabel() titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.font = UIFont.dw_font(forTextStyle: .title3) @@ -45,8 +49,8 @@ final class SyncingAlertContentView: UIView { titleLabel.adjustsFontForContentSizeCategory = true titleLabel.textAlignment = .center titleLabel.numberOfLines = 0 - self.addSubview(titleLabel) - + addSubview(titleLabel) + subtitleLabel = UILabel() subtitleLabel.translatesAutoresizingMaskIntoConstraints = false subtitleLabel.font = UIFont.dw_font(forTextStyle: .callout) @@ -54,15 +58,15 @@ final class SyncingAlertContentView: UIView { subtitleLabel.adjustsFontForContentSizeCategory = true subtitleLabel.textAlignment = .center subtitleLabel.numberOfLines = 0 - self.addSubview(subtitleLabel) - + addSubview(subtitleLabel) + let okButton = DWActionButton() okButton.translatesAutoresizingMaskIntoConstraints = false okButton.small = true okButton.setTitle(NSLocalizedString("OK", comment: ""), for: .normal) okButton.addTarget(self, action: #selector(okButtonAction(sender:)), for: .touchUpInside) - self.addSubview(okButton) - + addSubview(okButton) + syncingImageView.setContentCompressionResistancePriority(.required, for: .vertical) syncingImageView.setContentCompressionResistancePriority(.required, for: .horizontal) @@ -70,30 +74,31 @@ final class SyncingAlertContentView: UIView { subtitleLabel.setContentCompressionResistancePriority(.defaultLow, for: .vertical) NSLayoutConstraint.activate([ - syncingImageView.topAnchor.constraint(equalTo: self.topAnchor), - syncingImageView.centerXAnchor.constraint(equalTo: self.centerXAnchor), + syncingImageView.topAnchor.constraint(equalTo: topAnchor), + syncingImageView.centerXAnchor.constraint(equalTo: centerXAnchor), titleLabel.topAnchor.constraint(equalTo: syncingImageView.bottomAnchor, constant: 16.0), - titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor), - self.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor), subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8.0), - subtitleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor), - self.trailingAnchor.constraint(equalTo: subtitleLabel.trailingAnchor), + subtitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: subtitleLabel.trailingAnchor), okButton.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 38.0), - okButton.centerXAnchor.constraint(equalTo: self.centerXAnchor), - self.bottomAnchor.constraint(equalTo: okButton.bottomAnchor), + okButton.centerXAnchor.constraint(equalTo: centerXAnchor), + bottomAnchor.constraint(equalTo: okButton.bottomAnchor), okButton.heightAnchor.constraint(equalToConstant: 32.0), okButton.widthAnchor.constraint(equalToConstant: 110.0), ]) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - @objc func okButtonAction(sender: UIButton) { + + @objc + func okButtonAction(sender: UIButton) { delegate?.syncingAlertContentView(self, okButtonAction: sender) } @@ -104,10 +109,14 @@ final class SyncingAlertContentView: UIView { let chain = environment.currentChain let chainManager = environment.currentChainManager // We give a 6 block window, just in case a new block comes in - let atTheEndOfInitialTerminalBlocksAndSyncingMasternodeList = chain.lastTerminalBlockHeight >= chain.estimatedBlockHeight - 6 && chainManager.masternodeManager.masternodeListRetrievalQueueCount > 0 && chainManager.syncPhase == .initialTerminalBlocks - let atTheEndOfSyncBlocksAndSyncingMasternodeList = chain.lastSyncBlockHeight >= chain.estimatedBlockHeight - 6 && chainManager.masternodeManager.masternodeListRetrievalQueueCount > 0 && chainManager.syncPhase == .synced + let atTheEndOfInitialTerminalBlocksAndSyncingMasternodeList = chain.lastTerminalBlockHeight >= chain.estimatedBlockHeight - 6 && chainManager.masternodeManager + .masternodeListRetrievalQueueCount > 0 && chainManager.syncPhase == .initialTerminalBlocks + let atTheEndOfSyncBlocksAndSyncingMasternodeList = chain.lastSyncBlockHeight >= chain.estimatedBlockHeight - 6 && chainManager.masternodeManager + .masternodeListRetrievalQueueCount > 0 && chainManager.syncPhase == .synced if atTheEndOfInitialTerminalBlocksAndSyncingMasternodeList || atTheEndOfSyncBlocksAndSyncingMasternodeList { - subtitleLabel.text = String(format: NSLocalizedString("masternode list #%d of %d", comment: ""), (chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount - chainManager.masternodeManager.masternodeListRetrievalQueueCount), chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount) + subtitleLabel.text = String(format: NSLocalizedString("masternode list #%d of %d", comment: ""), + chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount - chainManager.masternodeManager.masternodeListRetrievalQueueCount, + chainManager.masternodeManager.masternodeListRetrievalQueueMaxAmount) } else { if chainManager.syncPhase == .initialTerminalBlocks { subtitleLabel.text = String(format: NSLocalizedString("header #%d of %d", comment: ""), chain.lastTerminalBlockHeight, chain.estimatedBlockHeight) @@ -115,32 +124,32 @@ final class SyncingAlertContentView: UIView { subtitleLabel.text = String(format: NSLocalizedString("block #%d of %d", comment: ""), chain.lastSyncBlockHeight, chain.estimatedBlockHeight) } } - + case .syncFailed: subtitleLabel.text = NSLocalizedString("Sync Failed", comment: "") - + case .noConnection: subtitleLabel.text = NSLocalizedString("Unable to connect", comment: "") case .unknown: break } - + if syncState == .syncing { showAnimation() } else { hideAnimation() } } - + func update(with progress: Double) { titleLabel.text = String(format: "%@ %.1f%%", NSLocalizedString("Syncing", comment: ""), progress * 100.0) } - + func showAnimation() { if syncingImageView.layer.animation(forKey: "dw_rotationAnimation") != nil { return } - + let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") rotationAnimation.fromValue = 0 rotationAnimation.toValue = Double.pi * 2.0 @@ -148,7 +157,7 @@ final class SyncingAlertContentView: UIView { rotationAnimation.repeatCount = .infinity syncingImageView.layer.add(rotationAnimation, forKey: "dw_rotationAnimation") } - + func hideAnimation() { syncingImageView.layer.removeAllAnimations() } diff --git a/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutAction.swift b/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutAction.swift index 9a3f0b1dc..bb146356d 100644 --- a/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutAction.swift +++ b/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutAction.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,6 +17,8 @@ import Foundation +// MARK: - ShortcutActionType + @objc(DWShortcutActionType) enum ShortcutActionType: Int { case secureWallet = 1 @@ -103,9 +105,8 @@ extension ShortcutActionType { default: fatalError("Image not found for shortcut type: \(self)") } - } - + var title: String { switch self { case .secureWallet: @@ -148,31 +149,32 @@ extension ShortcutActionType { case .explore: return NSLocalizedString("Explore", comment: "Translate it as short as possible! (24 symbols max)") } - } } +// MARK: - ShortcutAction + @objc(DWShortcutAction) class ShortcutAction: NSObject { @objc let type: ShortcutActionType - + @objc let enabled: Bool - + init(type: ShortcutActionType, enabled: Bool = true) { self.type = type self.enabled = enabled } - + @objc static func action(type: ShortcutActionType) -> ShortcutAction { - return ShortcutAction(type: type, enabled: true) + ShortcutAction(type: type, enabled: true) } - + @objc static func action(type: ShortcutActionType, enabled: Bool) -> ShortcutAction { - return ShortcutAction(type: type, enabled: enabled) + ShortcutAction(type: type, enabled: enabled) } } @@ -180,19 +182,19 @@ extension ShortcutAction { var title: String { type.title } - + var icon: UIImage { type.icon } - + var alpha: CGFloat { enabled ? 1 : 0.4 } - + var showsGradientLayer: Bool { false } - + var textColor: UIColor { .dw_darkTitle() } diff --git a/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutsModel.swift b/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutsModel.swift index 7241fc881..886a020e2 100644 --- a/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutsModel.swift +++ b/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutsModel.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,11 +17,15 @@ import Foundation +// MARK: - ShortcutsModelDataSource + @objc(DWShortcutsModelDataSource) protocol ShortcutsModelDataSource: AnyObject { func shouldShowCreateUserNameButton() -> Bool } +// MARK: - ShortcutsModelDelegate + @objc(DWShortcutsModelDelegate) protocol ShortcutsModelDelegate: AnyObject { func shortcutItemsDidChange() @@ -29,24 +33,26 @@ protocol ShortcutsModelDelegate: AnyObject { let MAX_SHORTCUTS_COUNT = 4 +// MARK: - ShortcutsModel + @objc(DWShortcutsModel) class ShortcutsModel: NSObject { private var mutableItems: [ShortcutAction] = [] - + weak var dataSource: ShortcutsModelDataSource? weak var delegate: ShortcutsModelDelegate? - + @objc init(dataSource: ShortcutsModelDataSource) { super.init() - + self.dataSource = dataSource - + reloadShortcuts() } var items: [ShortcutAction] { - return mutableItems + mutableItems } @objc @@ -59,13 +65,13 @@ class ShortcutsModel: NSObject { let options = DWGlobalOptions.sharedInstance() let walletNeedsBackup = options.walletNeedsBackup let userHasBalance = options.userHasBalance - + var mutableItems = [ShortcutAction]() - mutableItems.reserveCapacity(3) - + mutableItems.reserveCapacity(2) + if walletNeedsBackup { mutableItems.append(ShortcutAction(type: .secureWallet)) - + if userHasBalance { mutableItems.append(ShortcutAction(type: .receive)) mutableItems.append(ShortcutAction(type: .payToAddress)) @@ -73,7 +79,7 @@ class ShortcutsModel: NSObject { } else { mutableItems.append(ShortcutAction(type: .explore)) mutableItems.append(ShortcutAction(type: .receive)) - + if DWEnvironment.sharedInstance().currentChain.isMainnet() { mutableItems.append(ShortcutAction(type: .buySellDash)) } @@ -87,13 +93,13 @@ class ShortcutsModel: NSObject { } else { mutableItems.append(ShortcutAction(type: .explore)) mutableItems.append(ShortcutAction(type: .receive)) - + if DWEnvironment.sharedInstance().currentChain.isMainnet() { mutableItems.append(ShortcutAction(type: .buySellDash)) } } } - + return mutableItems } } diff --git a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutCell.swift b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutCell.swift index ea034df97..a26a82ecb 100644 --- a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutCell.swift +++ b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutCell.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -18,19 +18,19 @@ import UIKit class ShortcutCell: UICollectionViewCell { - + @IBOutlet private weak var centeredView: UIView! @IBOutlet private weak var iconImageView: UIImageView! @IBOutlet private weak var titleLabel: UILabel! - + private var gradientLayer: CAGradientLayer? - + override func awakeFromNib() { super.awakeFromNib() titleLabel.font = UIFont.dw_font(forTextStyle: .caption2) centeredView.backgroundColor = .clear } - + var model: ShortcutAction! { didSet { titleLabel.text = model.title @@ -42,7 +42,7 @@ class ShortcutCell: UICollectionViewCell { iconImageView.alpha = alpha } } - + override var isHighlighted: Bool { didSet { guard model.enabled == true else { return } diff --git a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift index 043e16e76..2eb49713d 100644 --- a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift +++ b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -19,7 +19,7 @@ import UIKit func cellSize(for contentSizeCategory: UIContentSizeCategory) -> CGSize { var size = CGSize.zero - + if contentSizeCategory == .extraSmall || contentSizeCategory == .small || contentSizeCategory == .medium || @@ -32,7 +32,7 @@ func cellSize(for contentSizeCategory: UIContentSizeCategory) -> CGSize { } else { size = CGSize(width: 116.0, height: 116.0) } - + if UIDevice.isIphone6Plus || UIDevice.hasHomeIndicator { let width = UIScreen.main.bounds.size.width let margin: CGFloat = 16.0 @@ -43,42 +43,48 @@ func cellSize(for contentSizeCategory: UIContentSizeCategory) -> CGSize { return CGSize(width: cellWidth, height: cellWidth) } } - + if UIDevice.isIpad { return CGSize(width: size.width * 2.0, height: size.height) } - + return size } +// MARK: - ShortcutsActionDelegate + @objc(DWShortcutsActionDelegate) protocol ShortcutsActionDelegate: AnyObject { func shortcutsView(_ view: UIView, didSelectAction action: ShortcutAction, sender: UIView) } +// MARK: - ShortcutsViewDelegate + @objc(DWShortcutsViewDelegate) protocol ShortcutsViewDelegate: AnyObject { func shortcutsViewDidUpdateContentSize(_ shortcutsView: ShortcutsView) } +// MARK: - ShortcutsView + @objc class ShortcutsView: UIView { @objc weak var actionDelegate: ShortcutsActionDelegate? - + @objc weak var delegate: ShortcutsViewDelegate? - + @IBOutlet weak var contentView: UIView! - + @IBOutlet weak var collectionView: UICollectionView! - + @IBOutlet var collectionViewHeightConstraint: NSLayoutConstraint! - + @objc var model: ShortcutsModel! { didSet { @@ -86,60 +92,62 @@ class ShortcutsView: UIView { collectionView.reloadData() } } - + override init(frame: CGRect) { super.init(frame: frame) commonInit() } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } - + private func commonInit() { Bundle.main.loadNibNamed(String(describing: type(of: self)), owner: self, options: nil) - + backgroundColor = .dw_secondaryBackground() - + contentView.translatesAutoresizingMaskIntoConstraints = false addSubview(contentView) - + NSLayoutConstraint.activate([ contentView.topAnchor.constraint(equalTo: topAnchor), contentView.leadingAnchor.constraint(equalTo: leadingAnchor), contentView.bottomAnchor.constraint(equalTo: bottomAnchor), contentView.trailingAnchor.constraint(equalTo: trailingAnchor), - contentView.widthAnchor.constraint(equalTo: widthAnchor) + contentView.widthAnchor.constraint(equalTo: widthAnchor), ]) - - + + collectionView.layer.cornerRadius = 8 collectionView.layer.masksToBounds = true - + if UIDevice.current.userInterfaceIdiom == .pad { collectionView.contentInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0) } - + collectionView.register(UINib(nibName: "DWShortcutCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: ShortcutCell.reuseIdentifier) - - NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChangeNotification(notification:)), name: UIContentSizeCategory.didChangeNotification, object: nil) - + + NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChangeNotification(notification:)), + name: UIContentSizeCategory.didChangeNotification, object: nil) + updateCellSizeForContentSizeCategory(UIApplication.shared.preferredContentSizeCategory, initialSetup: true) } - - @objc func contentSizeCategoryDidChangeNotification(notification: Notification) { + + @objc + func contentSizeCategoryDidChangeNotification(notification: Notification) { guard let category = notification.userInfo?[UIContentSizeCategory.newValueUserInfoKey] as? UIContentSizeCategory else { return } updateCellSizeForContentSizeCategory(category, initialSetup: false) } - + private func updateCellSizeForContentSizeCategory(_ contentSizeCategory: UIContentSizeCategory, initialSetup: Bool) { let cellSize = cellSize(for: contentSizeCategory) collectionViewHeightConstraint.constant = cellSize.height setNeedsUpdateConstraints() - + if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout { layout.itemSize = cellSize if !initialSetup { @@ -147,49 +155,51 @@ class ShortcutsView: UIView { } } collectionView.reloadData() - + if !initialSetup { delegate?.shortcutsViewDidUpdateContentSize(self) } } } +// MARK: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout + extension ShortcutsView: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let action = self.model.items[indexPath.item] - + let action = model.items[indexPath.item] + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ShortcutCell.reuseIdentifier, for: indexPath) as! ShortcutCell cell.model = action - #if SNAPSHOT - if (action.type == .secureWallet) { + #if SNAPSHOT + if action.type == .secureWallet { cell.accessibilityIdentifier = "shortcut_secure_wallet" } - #endif /* SNAPSHOT */ + #endif // SNAPSHOT return cell } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { model.items.count } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: true) - - let action = self.model.items[indexPath.item] + + let action = model.items[indexPath.item] guard action.enabled else { return } guard let cell = collectionView.cellForItem(at: indexPath) else { return } - + actionDelegate?.shortcutsView(self, didSelectAction: action, sender: cell) } - + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { guard let collectionViewLayout = collectionViewLayout as? UICollectionViewFlowLayout else { return UIEdgeInsets.zero } - + let cellSpacing = collectionViewLayout.minimumLineSpacing let cellWidth = collectionViewLayout.itemSize.width - + let cellCount = CGFloat(collectionView.numberOfItems(inSection: section)) var inset = (collectionView.bounds.size.width - (cellCount * cellWidth) - ((cellCount - 1) * cellSpacing)) * 0.5 inset = max(inset, 0.0) @@ -197,11 +207,13 @@ extension ShortcutsView: UICollectionViewDataSource, UICollectionViewDelegate, U } } +// MARK: ShortcutsModelDataSource, ShortcutsModelDelegate + extension ShortcutsView: ShortcutsModelDataSource, ShortcutsModelDelegate { func shouldShowCreateUserNameButton() -> Bool { false } - + func shortcutItemsDidChange() { collectionView.reloadData() } diff --git a/DashWallet/Sources/UI/Menu/Tools/ExtendedKeys/ExtendedPublicKeysModel.swift b/DashWallet/Sources/UI/Menu/Tools/ExtendedKeys/ExtendedPublicKeysModel.swift index 64c6433fe..ae61993e2 100644 --- a/DashWallet/Sources/UI/Menu/Tools/ExtendedKeys/ExtendedPublicKeysModel.swift +++ b/DashWallet/Sources/UI/Menu/Tools/ExtendedKeys/ExtendedPublicKeysModel.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,9 +17,11 @@ import Foundation +// MARK: - ExtendedPublicKeysModel + final class ExtendedPublicKeysModel { let derivationPaths: [DSDerivationPath] - + init() { let currentAccount = DWEnvironment.sharedInstance().currentAccount derivationPaths = currentAccount.fundDerivationPaths ?? [] @@ -29,11 +31,11 @@ final class ExtendedPublicKeysModel { extension DSDerivationPath { var item: DerivationPathKeysItem { let title: String - + if let dp = self as? DSIncomingFundsDerivationPath, - let username = dp.contactDestinationBlockchainIdentity.currentDashpayUsername { + let username = dp.contactDestinationBlockchainIdentity.currentDashpayUsername { title = username - }else{ + } else { title = referenceName } diff --git a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/DerivationPathKeysViewController.swift b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/DerivationPathKeysViewController.swift index 6950ffaa4..72e89feb9 100644 --- a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/DerivationPathKeysViewController.swift +++ b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/DerivationPathKeysViewController.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,76 +17,80 @@ import UIKit +// MARK: - DerivationPathKeysViewController + final class DerivationPathKeysViewController: BaseViewController, NavigationStackControllable { private let model: DerivationPathKeysModel - + private var tableView: UITableView! - + convenience init(with key: MNKey, derivationPath: DSAuthenticationKeysDerivationPath) { self.init(with: DerivationPathKeysModel(key: key, derivationPath: derivationPath)) } - + init(with model: DerivationPathKeysModel) { self.model = model - + super.init(nibName: nil, bundle: nil) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + @objc private func addNewAction() { model.showNextKey() - + tableView.insertSections([model.visibleIndexes], with: .automatic) } - + override func viewDidLoad() { super.viewDidLoad() - + configureHierarchy() } } +// MARK: UITableViewDelegate, UITableViewDataSource + extension DerivationPathKeysViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let usageInfo = model.usageInfoForKey(at: section) - + let view = tableView.dequeueReusableHeaderFooterView(type: DerivationPathKeysHeaderView.self) view.titleLabel.text = NSLocalizedString("Keypair", comment: "") + " \(section)" view.extraInfoLabel.text = usageInfo return view } - + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 40 } - + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - + guard let cell = tableView.cellForRow(at: indexPath) as? DerivationPathKeysCell, !cell.item.value.isEmpty else { return } - + UIPasteboard.general.string = cell.item.value view.dw_showInfoHUD(withText: NSLocalizedString("Copied", comment: "")) } - + func numberOfSections(in tableView: UITableView) -> Int { model.numberOfSections } - + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { model.numberIfItems } - + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let info = model.infoItems[indexPath.row] let item = model.itemForInfo(info, atIndex: indexPath.section) - + let cell = tableView.dequeueReusableCell(type: DerivationPathKeysCell.self, for: indexPath) cell.update(with: item) cell.separatorInset = .init(top: 0, left: 15, bottom: 0, right: 0) @@ -98,7 +102,7 @@ extension DerivationPathKeysViewController { private func configureHierarchy() { title = model.title view.backgroundColor = .dw_secondaryBackground() - + navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .plain, target: self, action: #selector(addNewAction)) tableView = UITableView(frame: .zero, style: .insetGrouped) tableView.registerClass(for: DerivationPathKeysCell.self) @@ -111,7 +115,7 @@ extension DerivationPathKeysViewController { tableView.delegate = self tableView.dataSource = self view.addSubview(tableView) - + NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0), @@ -121,6 +125,8 @@ extension DerivationPathKeysViewController { } } +// MARK: NavigationBarStyleable + extension DerivationPathKeysViewController: NavigationBarStyleable { var prefersLargeTitles: Bool { true } var largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode { .always } diff --git a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift index a09d470ac..1e12bb8fa 100644 --- a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift +++ b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,21 +17,25 @@ import Foundation +// MARK: - DerivationPathKeysItem + struct DerivationPathKeysItem { let title: String let value: String - + init(title: String, value: String) { self.title = title self.value = value } - + init(info: DerivationPathInfo, value: String) { - self.title = info.title + title = info.title self.value = value } } +// MARK: - DerivationPathInfo + enum DerivationPathInfo: CaseIterable { case address case publicKey @@ -54,54 +58,56 @@ extension DerivationPathInfo { } } +// MARK: - DerivationPathKeysModel + final class DerivationPathKeysModel { let key: MNKey let derivationPath: DSAuthenticationKeysDerivationPath - + let infoItems = DerivationPathInfo.allCases - + var visibleIndexes: Int - + init(key: MNKey, derivationPath: DSAuthenticationKeysDerivationPath) { self.key = key self.derivationPath = derivationPath - self.visibleIndexes = Int(derivationPath.firstUnusedIndex()) + visibleIndexes = Int(derivationPath.firstUnusedIndex()) } - + func showNextKey() { visibleIndexes += 1 } } -//MARK: UI Helper +// MARK: UI Helper extension DerivationPathKeysModel { var title: String { key.title } - + var numberOfSections: Int { visibleIndexes + 1 } - + var numberIfItems: Int { infoItems.count } - + func usageInfoForKey(at index: Int) -> String { let used = derivationPath.addressIsUsed(at: UInt32(index)) - - if (used) { + + if used { if let localMasternode = derivationPath.chain.chainManager!.masternodeManager.localMasternode(using: UInt32(index), at: derivationPath) { return NSLocalizedString("Used at: ", comment: "") + localMasternode.ipAddressAndIfNonstandardPortString - }else { - guard let localMasternodesArray = self.derivationPath.chain.chainManager!.masternodeManager.localMasternodesPreviously(using: UInt32(index), at: derivationPath) else { + } else { + guard let localMasternodesArray = derivationPath.chain.chainManager!.masternodeManager.localMasternodesPreviously(using: UInt32(index), at: derivationPath) else { return NSLocalizedString("Not yet used", comment: "") } - + if localMasternodesArray.count == 1 { let localMasternode = localMasternodesArray.first! return NSLocalizedString("Previously used at: ", comment: "") + localMasternode.ipAddressAndIfNonstandardPortString - } else if localMasternodesArray.count == 0 { + } else if localMasternodesArray.isEmpty { return NSLocalizedString("Used", comment: "") } else { let localMasternode = localMasternodesArray.last! @@ -112,16 +118,16 @@ extension DerivationPathKeysModel { return NSLocalizedString("Not yet used", comment: "") } } - + func itemForInfo(_ info: DerivationPathInfo, atIndex index: Int) -> DerivationPathKeysItem { let wallet = DWEnvironment.sharedInstance().currentWallet - + switch info { case .address: - let address = self.derivationPath.address(at: UInt32(index)) + let address = derivationPath.address(at: UInt32(index)) return DerivationPathKeysItem(info: info, value: address) case .publicKey: - let publicKeyData = self.derivationPath.publicKeyData(at: UInt32(index)) + let publicKeyData = derivationPath.publicKeyData(at: UInt32(index)) return DerivationPathKeysItem(info: info, value: publicKeyData.hexEncodedString()) case .privateKey: return autoreleasepool { @@ -129,9 +135,9 @@ extension DerivationPathKeysModel { return DerivationPathKeysItem(info: info, value: NSLocalizedString("Not available", comment: "")) } let seed = DSBIP39Mnemonic.sharedInstance()!.deriveKey(fromPhrase: phrase, withPassphrase: nil) - + let key = self.derivationPath.privateKey(at: UInt32(index), fromSeed: seed)! - + return DerivationPathKeysItem(info: info, value: key.secretKeyString) } case .wifPrivateKey: @@ -140,12 +146,12 @@ extension DerivationPathKeysModel { return DerivationPathKeysItem(info: info, value: NSLocalizedString("Not available", comment: "")) } let seed = DSBIP39Mnemonic.sharedInstance()!.deriveKey(fromPhrase: phrase, withPassphrase: nil) - + let key = self.derivationPath.privateKey(at: UInt32(index), fromSeed: seed)! - + return DerivationPathKeysItem(info: info, value: key.serializedPrivateKey(for: wallet.chain)!) } - + // case .MasternodeInfo: // let used = self.derivationPath.addressIsUsed(at: index) // if used { diff --git a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Views/DerivationPathKeysCell.swift b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Views/DerivationPathKeysCell.swift index f7d024e99..de60d5824 100644 --- a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Views/DerivationPathKeysCell.swift +++ b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Views/DerivationPathKeysCell.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -21,40 +21,40 @@ final class DerivationPathKeysCell: UITableViewCell { private var nameLabel: UILabel! private var valueLabel: UILabel! private var copyButton: UIButton! - + private(set) var item: DerivationPathKeysItem! - + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - + configureHierarchy() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func update(with item: DerivationPathKeysItem) { self.item = item - + nameLabel.text = item.title valueLabel.text = item.value } - + private func configureHierarchy() { let mainStackView = UIStackView() mainStackView.translatesAutoresizingMaskIntoConstraints = false mainStackView.axis = .vertical mainStackView.spacing = 2 contentView.addSubview(mainStackView) - + nameLabel = UILabel() nameLabel.translatesAutoresizingMaskIntoConstraints = false nameLabel.font = .dw_font(forTextStyle: .footnote) nameLabel.textColor = UIColor.dw_secondaryText() mainStackView.addArrangedSubview(nameLabel) - + valueLabel = UILabel() valueLabel.translatesAutoresizingMaskIntoConstraints = false valueLabel.font = .dw_font(forTextStyle: .subheadline) @@ -62,19 +62,19 @@ final class DerivationPathKeysCell: UITableViewCell { valueLabel.lineBreakMode = .byWordWrapping valueLabel.textColor = .dw_label() mainStackView.addArrangedSubview(valueLabel) - + copyButton = UIButton(type: .custom) copyButton.setImage(UIImage(named: "icon_copy_outline"), for: .normal) copyButton.translatesAutoresizingMaskIntoConstraints = false copyButton.tintColor = .dw_label() copyButton.isUserInteractionEnabled = false contentView.addSubview(copyButton) - + NSLayoutConstraint.activate([ mainStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15), mainStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 9), mainStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -9), - + copyButton.widthAnchor.constraint(equalToConstant: 30), copyButton.heightAnchor.constraint(equalToConstant: 30), copyButton.leadingAnchor.constraint(equalTo: mainStackView.trailingAnchor, constant: 5), diff --git a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Views/DerivationPathKeysHeaderView.swift b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Views/DerivationPathKeysHeaderView.swift index 948bef30e..506b02332 100644 --- a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Views/DerivationPathKeysHeaderView.swift +++ b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Views/DerivationPathKeysHeaderView.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -20,40 +20,40 @@ import UIKit final class DerivationPathKeysHeaderView: UITableViewHeaderFooterView { var titleLabel: UILabel! var extraInfoLabel: UILabel! - + override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) - + configureHierarchy() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override var intrinsicContentSize: CGSize { .init(width: DerivationPathKeysHeaderView.noIntrinsicMetric, height: 30) } - + private func configureHierarchy() { titleLabel = UILabel() titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.font = .dw_font(forTextStyle: .callout).withWeight(UIFont.Weight.semibold.rawValue) titleLabel.textColor = UIColor.dw_label() contentView.addSubview(titleLabel) - + extraInfoLabel = UILabel() extraInfoLabel.translatesAutoresizingMaskIntoConstraints = false extraInfoLabel.font = .dw_font(forTextStyle: .footnote) extraInfoLabel.textColor = .dw_secondaryText() extraInfoLabel.textAlignment = .right contentView.addSubview(extraInfoLabel) - + NSLayoutConstraint.activate([ titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0), titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0), titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0), - + extraInfoLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0), extraInfoLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0), extraInfoLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0), diff --git a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/Cell/KeysOverviewCell.swift b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/Cell/KeysOverviewCell.swift index 61a67d84d..39c89bf6a 100644 --- a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/Cell/KeysOverviewCell.swift +++ b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/Cell/KeysOverviewCell.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -21,53 +21,53 @@ final class KeysOverviewCell: UITableViewCell { private var keyNameLabel: UILabel! private var keyCountLabel: UILabel! private var usedLabel: UILabel! - + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - + configureHierarchy() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func update(with keyItem: MNKey, count: Int, used: Int) { let keyCountText = String(format: NSLocalizedString("%d key(s)", comment: "#bc-ignore!"), count) let usedCountText = String(format: NSLocalizedString("%ld used(s)", comment: "#bc-ignore!"), used) - + keyNameLabel.text = keyItem.title keyCountLabel.text = keyCountText usedLabel.text = usedCountText } - + private func configureHierarchy() { let mainStackView = UIStackView() mainStackView.translatesAutoresizingMaskIntoConstraints = false mainStackView.axis = .vertical mainStackView.spacing = 2 contentView.addSubview(mainStackView) - + keyNameLabel = UILabel() keyNameLabel.translatesAutoresizingMaskIntoConstraints = false keyNameLabel.font = .dw_font(forTextStyle: .subheadline) mainStackView.addArrangedSubview(keyNameLabel) - + keyCountLabel = UILabel() keyCountLabel.translatesAutoresizingMaskIntoConstraints = false keyCountLabel.font = .dw_font(forTextStyle: .caption1) mainStackView.addArrangedSubview(keyCountLabel) - + usedLabel = UILabel() usedLabel.translatesAutoresizingMaskIntoConstraints = false usedLabel.textAlignment = .right usedLabel.font = .dw_font(forTextStyle: .footnote) contentView.addSubview(usedLabel) - + NSLayoutConstraint.activate([ mainStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15), mainStackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0), - + usedLabel.leadingAnchor.constraint(equalTo: mainStackView.trailingAnchor, constant: 5), usedLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5), usedLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0), diff --git a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/KeysOverviewViewController.swift b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/KeysOverviewViewController.swift index 7f8780b22..f2063810c 100644 --- a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/KeysOverviewViewController.swift +++ b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/KeysOverviewViewController.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,18 +17,20 @@ import UIKit +// MARK: - KeysOverviewViewController + @objc(DWKeysOverviewViewController) final class KeysOverviewViewController: BaseViewController { private var tableView: UITableView! private var model: WalletKeysOverviewModel! - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } - + override func viewDidLoad() { super.viewDidLoad() - + configureModel() configureHierarchy() } @@ -38,10 +40,10 @@ extension KeysOverviewViewController { private func configureModel() { model = WalletKeysOverviewModel() } - + private func configureHierarchy() { title = NSLocalizedString("Masternode Keys", comment: "") - + tableView = UITableView(frame: .zero, style: .insetGrouped) tableView.preservesSuperviewLayoutMargins = true tableView.rowHeight = 62 @@ -52,7 +54,7 @@ extension KeysOverviewViewController { tableView.delegate = self tableView.dataSource = self view.addSubview(tableView) - + NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0), @@ -62,41 +64,43 @@ extension KeysOverviewViewController { } } +// MARK: UITableViewDataSource, UITableViewDelegate + extension KeysOverviewViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { model.items.count } - + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let item = model.items[indexPath.row] let count = model.keyCount(for: item) let used = model.usedCount(for: item) - + let cell = tableView.dequeueReusableCell(type: KeysOverviewCell.self, for: indexPath) cell.accessoryType = .disclosureIndicator cell.update(with: item, count: count, used: used) return cell } - + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - + let showVcBlock = { [weak self] in guard let self else { return } - + let item = model.items[indexPath.row] let derivationPath = model.derivationPath(for: item) let vc = DerivationPathKeysViewController(with: item, derivationPath: derivationPath) vc.hidesBottomBarWhenPushed = true navigationController?.pushViewController(vc, animated: true) } - - if (DSAuthenticationManager.sharedInstance().didAuthenticate) { + + if DSAuthenticationManager.sharedInstance().didAuthenticate { showVcBlock() } else { DSAuthenticationManager.sharedInstance().authenticate(withPrompt: nil, usingBiometricAuthentication: false, alertIfLockout: true) { authenticatedOrSuccess, _, _ in - + guard authenticatedOrSuccess else { return } showVcBlock() } @@ -104,6 +108,8 @@ extension KeysOverviewViewController: UITableViewDataSource, UITableViewDelegate } } +// MARK: NavigationBarStyleable + extension KeysOverviewViewController: NavigationBarStyleable { var prefersLargeTitles: Bool { true } var largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode { .always } diff --git a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/Model/WalletKeysOverviewModel.swift b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/Model/WalletKeysOverviewModel.swift index 08f1342a8..362aeb228 100644 --- a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/Model/WalletKeysOverviewModel.swift +++ b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/Overview/Model/WalletKeysOverviewModel.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,6 +17,8 @@ import Foundation +// MARK: - MNKey + enum MNKey: CaseIterable { case owner case voting @@ -39,25 +41,27 @@ extension MNKey { } } +// MARK: - WalletKeysOverviewModel + final class WalletKeysOverviewModel { var items: [MNKey] = MNKey.allCases - + let ownerDerivationPath: DSAuthenticationKeysDerivationPath let votingDerivationPath: DSAuthenticationKeysDerivationPath let operatorDerivationPath: DSAuthenticationKeysDerivationPath let hpmnOperatorDerivationPath: DSAuthenticationKeysDerivationPath - + init() { let wallet = DWEnvironment.sharedInstance().currentWallet let factory = DSDerivationPathFactory.sharedInstance()! - + ownerDerivationPath = factory.providerOwnerKeysDerivationPath(for: wallet) votingDerivationPath = factory.providerOwnerKeysDerivationPath(for: wallet) operatorDerivationPath = factory.providerOwnerKeysDerivationPath(for: wallet) hpmnOperatorDerivationPath = operatorDerivationPath - //hpmnOperatorDerivationPath = factory.platformNodeKeysDerivationPath(for: wallet) //We will use it in another branch + // hpmnOperatorDerivationPath = factory.platformNodeKeysDerivationPath(for: wallet) //We will use it in another branch } - + func derivationPath(for type: MNKey) -> DSAuthenticationKeysDerivationPath { switch type { case .owner: @@ -70,12 +74,12 @@ final class WalletKeysOverviewModel { return hpmnOperatorDerivationPath } } - + func keyCount(for type: MNKey) -> Int { let derivationPath = derivationPath(for: type) return derivationPath.allAddresses.count } - + func usedCount(for type: MNKey) -> Int { let derivationPath = derivationPath(for: type) return derivationPath.usedAddresses.count diff --git a/DashWallet/Sources/UI/Operation Status/FailedOperationStatusViewController.swift b/DashWallet/Sources/UI/Operation Status/FailedOperationStatusViewController.swift index 77f971be3..75d49a515 100644 --- a/DashWallet/Sources/UI/Operation Status/FailedOperationStatusViewController.swift +++ b/DashWallet/Sources/UI/Operation Status/FailedOperationStatusViewController.swift @@ -29,7 +29,7 @@ final class FailedOperationStatusViewController: BaseViewController, NavigationB var cancelHandler: (() -> ())? var retryHandler: (() -> ())? var supportHandler: (() -> ())? - + var headerText: String! { didSet { titleLabel?.text = headerText @@ -41,7 +41,7 @@ final class FailedOperationStatusViewController: BaseViewController, NavigationB descriptionLabel?.text = descriptionText } } - + var supportButtonText: String! { didSet { contactSupportButton?.setTitle(supportButtonText, for: .normal) @@ -53,7 +53,7 @@ final class FailedOperationStatusViewController: BaseViewController, NavigationB retryHandler?() } - @IBAction + @IBAction func supportAction() { supportHandler?() } diff --git a/DashWallet/Sources/UI/Payment Controller/Enter Amount/ProvideAmountViewController.swift b/DashWallet/Sources/UI/Payment Controller/Enter Amount/ProvideAmountViewController.swift index bcf68620d..a0eac121a 100644 --- a/DashWallet/Sources/UI/Payment Controller/Enter Amount/ProvideAmountViewController.swift +++ b/DashWallet/Sources/UI/Payment Controller/Enter Amount/ProvideAmountViewController.swift @@ -45,10 +45,10 @@ final class ProvideAmountViewController: SendAmountViewController { override func actionButtonAction(sender: UIView) { guard validateInputAmount() else { return } - + checkLeftoverBalance { [weak self] canContinue in guard canContinue, let wSelf = self else { return } - + wSelf.showActivityIndicator() let paymentCurrency: DWPaymentCurrency = wSelf.sendAmountModel.activeAmountType == .main ? .dash : .fiat DWGlobalOptions.sharedInstance().selectedPaymentCurrency = paymentCurrency diff --git a/DashWallet/Sources/UI/Payments/Amount/SendAmountViewController.swift b/DashWallet/Sources/UI/Payments/Amount/SendAmountViewController.swift index 5934a794a..f3edffe76 100644 --- a/DashWallet/Sources/UI/Payments/Amount/SendAmountViewController.swift +++ b/DashWallet/Sources/UI/Payments/Amount/SendAmountViewController.swift @@ -44,23 +44,26 @@ class SendAmountViewController: BaseAmountViewController { override func maxButtonAction() { sendAmountModel.selectAllFunds() } - + internal func checkLeftoverBalance(isCrowdNodeTransfer: Bool = false, completion: @escaping ((Bool) -> Void)) { if CrowdNodeDefaults.shared.lastKnownBalance <= 0 && !isCrowdNodeTransfer { // If CrowdNode balance is 0, then there is no need to check the leftover balance completion(true) return } - + // If CrowdNode balance isn't empty and the user sends DASH somewhere, // or if the user is making a CrowdNode deposit, then we need to check the leftover balance - + let account = DWEnvironment.sharedInstance().currentAccount let allAvailableFunds = account.maxOutputAmount - + if model.amount.plainAmount + CrowdNode.minimumLeftoverBalance > allAvailableFunds { let title = NSLocalizedString("Looks like you are emptying your Dash Wallet", comment: "Leftover balance warning") - let message = String.localizedStringWithFormat(NSLocalizedString("Please note, you will not be able to withdraw your funds from CowdNode to this wallet until you increase your balance to %@ Dash.", comment: "Leftover balance warning"), CrowdNode.minimumLeftoverBalance.formattedDashAmountWithoutCurrencySymbol) + let message = String + .localizedStringWithFormat(NSLocalizedString("Please note, you will not be able to withdraw your funds from CowdNode to this wallet until you increase your balance to %@ Dash.", + comment: "Leftover balance warning"), + CrowdNode.minimumLeftoverBalance.formattedDashAmountWithoutCurrencySymbol) let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: "Leftover balance warning"), style: .default, handler: { _ in completion(true) @@ -68,7 +71,7 @@ class SendAmountViewController: BaseAmountViewController { alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "Leftover balance warning"), style: .cancel, handler: { _ in completion(false) })) - self.present(alert, animated: true, completion: nil) + present(alert, animated: true, completion: nil) } else { completion(true) } diff --git a/DashWallet/Sources/UI/Payments/Pay/Cells/PayTableViewCell.swift b/DashWallet/Sources/UI/Payments/Pay/Cells/PayTableViewCell.swift index 3d1adb385..013046d19 100644 --- a/DashWallet/Sources/UI/Payments/Pay/Cells/PayTableViewCell.swift +++ b/DashWallet/Sources/UI/Payments/Pay/Cells/PayTableViewCell.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -19,10 +19,12 @@ import UIKit let MAX_ALLOWED_BUTTON_WIDTH: CGFloat = 108.0 +// MARK: - PayTableViewCell + class PayTableViewCell: UITableViewCell { @IBOutlet private weak var iconImageView: UIImageView! @IBOutlet private weak var titleLabel: UILabel! - + override func awakeFromNib() { super.awakeFromNib() titleLabel.font = UIFont.dw_font(forTextStyle: .subheadline) @@ -30,7 +32,7 @@ class PayTableViewCell: UITableViewCell { var model: DWPayOptionModel? { didSet { - guard let model = model else { return } + guard let model else { return } let type = model.type titleLabel.text = model.title diff --git a/DashWallet/Sources/UI/Payments/Pay/PayViewController.swift b/DashWallet/Sources/UI/Payments/Pay/PayViewController.swift index d8566a69e..710eed97f 100644 --- a/DashWallet/Sources/UI/Payments/Pay/PayViewController.swift +++ b/DashWallet/Sources/UI/Payments/Pay/PayViewController.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,26 +17,30 @@ import UIKit +// MARK: - PayViewControllerDelegate + @objc(DWPayViewControllerDelegate) protocol PayViewControllerDelegate: AnyObject { func payViewControllerDidFinishPayment(_ controller: PayViewController, contact: DWDPBasicUserItem?) } +// MARK: - PayViewController + @objc(DWPayViewController) class PayViewController: BaseViewController, PayableViewController { @IBOutlet weak var tableView: UITableView! - + @objc var paymentController: PaymentController! - + @objc var payModel: DWPayModelProtocol! var maxActionButtonWidth: CGFloat = 0 - + @objc - var demoMode: Bool = false - + var demoMode = false + @objc var delegate: PayViewControllerDelegate? @@ -51,7 +55,7 @@ class PayViewController: BaseViewController, PayableViewController { override func viewDidLoad() { super.viewDidLoad() - + configurePaymentController() configureHierarchy() } @@ -67,14 +71,14 @@ class PayViewController: BaseViewController, PayableViewController { } } -private extension PayViewController { - func configurePaymentController() { +extension PayViewController { + private func configurePaymentController() { paymentController = PaymentController() paymentController.delegate = self paymentController.presentationContextProvider = self } - - func configureHierarchy() { + + private func configureHierarchy() { let cellId = PayTableViewCell.reuseIdentifier let nib = UINib(nibName: cellId, bundle: nil) tableView.register(nib, forCellReuseIdentifier: cellId) @@ -89,10 +93,12 @@ private extension PayViewController { } } +// MARK: UITableViewDataSource, UITableViewDelegate + extension PayViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return payModel.options.count + payModel.options.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -105,7 +111,7 @@ extension PayViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let payOption = payModel.options[indexPath.row] - + switch payOption.type { case .scanQR: performScanQRCodeAction(delegate: self) @@ -121,43 +127,45 @@ extension PayViewController: UITableViewDataSource, UITableViewDelegate { +// MARK: DWQRScanModelDelegate + extension PayViewController: DWQRScanModelDelegate { func qrScanModel(_ viewModel: DWQRScanModel, didScanPaymentInput paymentInput: DWPaymentInput) { dismiss(animated: true) { [weak self] in self?.paymentController.performPayment(with: paymentInput) } } - + func qrScanModelDidCancel(_ viewModel: DWQRScanModel) { dismiss(animated: true) } } +// MARK: PaymentControllerDelegate, PaymentControllerPresentationContextProviding + extension PayViewController: PaymentControllerDelegate, PaymentControllerPresentationContextProviding { func presentationAnchorForPaymentController(_ controller: PaymentController) -> PaymentControllerPresentationAnchor { self } - + func paymentControllerDidFinishTransaction(_ controller: PaymentController, transaction: DSTransaction) { let model = TxDetailModel(transaction: transaction) let vc = SuccessTxDetailViewController(model: model) vc.modalPresentationStyle = .fullScreen vc.contactItem = paymentController.contactItem vc.delegate = self - self.present(vc, animated: true) - } - - func paymentControllerDidCancelTransaction(_ controller: PaymentController) { - - } - - func paymentControllerDidFailTransaction(_ controller: PaymentController) { - + present(vc, animated: true) } + + func paymentControllerDidCancelTransaction(_ controller: PaymentController) { } + + func paymentControllerDidFailTransaction(_ controller: PaymentController) { } } +// MARK: SuccessTxDetailViewControllerDelegate + extension PayViewController: SuccessTxDetailViewControllerDelegate { func txDetailViewControllerDidFinish(controller: SuccessTxDetailViewController) { - self.delegate?.payViewControllerDidFinishPayment(self, contact: paymentController.contactItem) + delegate?.payViewControllerDidFinishPayment(self, contact: paymentController.contactItem) } } diff --git a/DashWallet/Sources/UI/Payments/Pay/PayableViewController.swift b/DashWallet/Sources/UI/Payments/Pay/PayableViewController.swift index 67c8cd92a..6ae3f0fc4 100644 --- a/DashWallet/Sources/UI/Payments/Pay/PayableViewController.swift +++ b/DashWallet/Sources/UI/Payments/Pay/PayableViewController.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,6 +17,8 @@ import Foundation +// MARK: - PayableViewController + protocol PayableViewController: DWQRScanModelDelegate { var payModel: DWPayModelProtocol! { get } var paymentController: PaymentController! { get } @@ -24,7 +26,7 @@ protocol PayableViewController: DWQRScanModelDelegate { extension PayableViewController where Self: UIViewController { func payToAddressAction() { - guard let payModel = payModel else { return } + guard let payModel else { return } payModel.payToAddress { [weak self] success in guard let strongSelf = self else { return } @@ -42,34 +44,32 @@ extension PayableViewController where Self: UIViewController { } } } - + func performScanQRCodeAction(delegate: DWQRScanModelDelegate) { if let vc = presentedViewController, vc is DWQRScanViewController { return; } - + let controller = DWQRScanViewController() controller.model.delegate = delegate present(controller, animated: true, completion: nil) } - + func performNFCReadingAction() { payModel?.performNFCReading(completion: { [weak self] paymentInput in guard let strongSelf = self else { return } strongSelf.processPaymentInput(paymentInput) }) } - + func performPayToPasteboardAction() { guard let paymentInput = payModel?.pasteboardPaymentInput else { return } processPaymentInput(paymentInput) } - + func processPaymentInput(_ input: DWPaymentInput) { paymentController.performPayment(with: input) } } -extension PayableViewController where Self: UIViewController { - -} +extension PayableViewController where Self: UIViewController { } diff --git a/DashWallet/Sources/UI/Payments/PaymentsViewController.swift b/DashWallet/Sources/UI/Payments/PaymentsViewController.swift index 5b0ea3658..6ca8e1651 100644 --- a/DashWallet/Sources/UI/Payments/PaymentsViewController.swift +++ b/DashWallet/Sources/UI/Payments/PaymentsViewController.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,18 +17,22 @@ import UIKit +// MARK: - PaymentsViewControllerState + @objc(DWPaymentsViewControllerIndex) enum PaymentsViewControllerState: Int { @objc(DWPaymentsViewControllerIndex_None) case none = -1 - + @objc(DWPaymentsViewControllerIndex_Receive) case receive = 0 - + @objc(DWPaymentsViewControllerIndex_Pay) case pay = 1 } +// MARK: - PaymentsViewControllerDelegate + @objc(DWPaymentsViewControllerDelegate) protocol PaymentsViewControllerDelegate: AnyObject { func paymentsViewControllerWantsToImportPrivateKey(_ controller: PaymentsViewController) @@ -36,69 +40,73 @@ protocol PaymentsViewControllerDelegate: AnyObject { func paymentsViewControllerDidFinishPayment(_ controller: PaymentsViewController, contact: DWDPBasicUserItem?) } +// MARK: - PaymentsViewController + @objc(DWPaymentsViewController) class PaymentsViewController: BaseViewController { @IBOutlet var segmentedControl: UISegmentedControl! @IBOutlet var containerView: UIView! @IBOutlet var closeButton: UIButton! - + @objc weak var delegate: PaymentsViewControllerDelegate? - + @objc - var demoMode: Bool = false - + var demoMode = false + @objc weak var demoDelegate: DWDemoDelegate? - + @objc var currentState: PaymentsViewControllerState = .pay { didSet { if currentState == .none { currentState = PaymentsViewControllerState(rawValue: DWGlobalOptions.sharedInstance().paymentsScreenCurrentTab)! } - + let idx = currentState.rawValue - + segmentedControl?.selectedSegmentIndex = idx pageController?.selectedIndex = idx } } - + private var receiveModel: DWReceiveModelProtocol! private var payModel: DWPayModelProtocol! private var dataProvider: DWTransactionListDataProviderProtocol? - + private var payViewController: PayViewController! private var receiveViewController: ReceiveViewController! - + private var pageController: SendReceivePageController! - - @IBAction func segmentedControlAction() { + + @IBAction + func segmentedControlAction() { let idx = segmentedControl.selectedSegmentIndex pageController.setSelectedIndex(idx, animated: true) - + DWGlobalOptions.sharedInstance().paymentsScreenCurrentTab = idx } - - @IBAction func closeButtonAction() { + + @IBAction + func closeButtonAction() { delegate?.paymentsViewControllerDidCancel(self) } - + override func viewDidLoad() { super.viewDidLoad() - + configureHierarchy() } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } - + @objc class func controller(withReceiveModel receiveModel: DWReceiveModelProtocol?, - payModel: DWPayModelProtocol?, - dataProvider: DWTransactionListDataProviderProtocol?) -> PaymentsViewController { + payModel: DWPayModelProtocol?, + dataProvider: DWTransactionListDataProviderProtocol?) -> PaymentsViewController { let controller = sb("Payments").vc(PaymentsViewController.self) controller.receiveModel = receiveModel controller.payModel = payModel @@ -110,17 +118,17 @@ class PaymentsViewController: BaseViewController { extension PaymentsViewController { func configureHierarchy() { view.backgroundColor = .dw_secondaryBackground() - + segmentedControl.setTitle(NSLocalizedString("Receive", comment: "Receive/Send"), forSegmentAt: 0) segmentedControl.setTitle(NSLocalizedString("Send", comment: "Receive/Send"), forSegmentAt: 1) segmentedControl.selectedSegmentIndex = currentState.rawValue - + payViewController = PayViewController.controller(with: payModel) payViewController.delegate = self - + receiveViewController = ReceiveViewController(model: receiveModel) receiveViewController.delegate = self - + pageController = SendReceivePageController() pageController.helperDelegate = self addChild(pageController) @@ -129,9 +137,9 @@ extension PaymentsViewController { pageController.didMove(toParent: self) pageController.controllers = [receiveViewController, payViewController] pageController.selectedIndex = currentState.rawValue - + closeButton.layer.cornerRadius = 24 - + NSLayoutConstraint.activate([ pageController.view.topAnchor.constraint(equalTo: containerView.topAnchor), pageController.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), @@ -141,29 +149,37 @@ extension PaymentsViewController { } } +// MARK: NavigationBarDisplayable + extension PaymentsViewController: NavigationBarDisplayable { var isNavigationBarHidden: Bool { true } } +// MARK: SendReceivePageControllerDelegate + extension PaymentsViewController: SendReceivePageControllerDelegate { func sendReceivePageControllerWillChangeSelectedIndex(to index: Int) { segmentedControl.selectedSegmentIndex = index - + DWGlobalOptions.sharedInstance().paymentsScreenCurrentTab = index } } +// MARK: PayViewControllerDelegate + extension PaymentsViewController: PayViewControllerDelegate { func payViewControllerDidFinishPayment(_ controller: PayViewController, contact: DWDPBasicUserItem?) { delegate?.paymentsViewControllerDidFinishPayment(self, contact: contact) } } +// MARK: ReceiveViewControllerDelegate + extension PaymentsViewController: ReceiveViewControllerDelegate { func receiveViewControllerExitButtonAction(_ controller: ReceiveViewController) { - //NOP + // NOP } - + func importPrivateKeyButtonAction(_ controller: ReceiveViewController) { delegate?.paymentsViewControllerWantsToImportPrivateKey(self) } diff --git a/DashWallet/Sources/UI/Payments/Receive/ReceiveViewController.swift b/DashWallet/Sources/UI/Payments/Receive/ReceiveViewController.swift index c0add69ae..075d43f94 100644 --- a/DashWallet/Sources/UI/Payments/Receive/ReceiveViewController.swift +++ b/DashWallet/Sources/UI/Payments/Receive/ReceiveViewController.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,85 +17,91 @@ import Foundation +// MARK: - ReceiveViewType + @objc(DWReceiveViewType) enum ReceiveViewType: Int { @objc(DWReceiveViewType_Default) case `default` - + @objc(DWReceiveViewType_QuickReceive) case quick } - + extension ReceiveViewType { var secondButtonTitle: String { if self == .default { return NSLocalizedString("Share address", comment: "Receive screen") - }else{ + } else { return NSLocalizedString("Exit", comment: "Receive screen") } } } +// MARK: - ReceiveViewControllerDelegate + @objc(DWReceiveViewControllerDelegate) protocol ReceiveViewControllerDelegate: AnyObject { func receiveViewControllerExitButtonAction(_ controller: ReceiveViewController) func importPrivateKeyButtonAction(_ controller: ReceiveViewController) } +// MARK: - ReceiveViewController + @objc(DWReceiveViewController) class ReceiveViewController: BaseViewController { var model: DWReceiveModelProtocol! - + @objc var viewType: ReceiveViewType = .default - + @objc weak var delegate: ReceiveViewControllerDelegate? - + @objc - var allowedToImportPrivateKey: Bool = true - + var allowedToImportPrivateKey = true + @objc init(model: DWReceiveModelProtocol) { self.model = model super.init(nibName: nil, bundle: nil) } - + @objc func importPrivateKeyButtonAction() { let controller = sb("ImportWalletInfo").instantiateInitialViewController() as! DWImportWalletInfoViewController controller.delegate = self - + let nvc = BaseNavigationController(rootViewController: controller) present(nvc, animated: true) - + nvc.setCancelButtonHidden(false) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { super.viewDidLoad() - + configureHierarchy() } } -private extension ReceiveViewController { +extension ReceiveViewController { private func configureHierarchy() { let mainStackView = UIStackView() mainStackView.translatesAutoresizingMaskIntoConstraints = false mainStackView.axis = .vertical mainStackView.spacing = stackSpacing view.addSubview(mainStackView) - + let receiveContentView = ReceiveContentView.view(with: model) receiveContentView.viewType = viewType receiveContentView.specifyAmountHandler = { [weak self] in guard let self else { return } - + let vc = SpecifyAmountViewController.controller() vc.delegate = self self.navigationController?.pushViewController(vc, animated: true) @@ -103,17 +109,16 @@ private extension ReceiveViewController { receiveContentView.shareHandler = { [weak self] sender in guard let self else { return } self.dw_shareReceiveInfo(self.model, sender: sender) - } receiveContentView.exitHandler = { [weak self] in guard let self else { return } self.delegate?.receiveViewControllerExitButtonAction(self) } - + receiveContentView.backgroundColor = .dw_background() receiveContentView.layer.cornerRadius = radius mainStackView.addArrangedSubview(receiveContentView) - + let importPrivateKeyButton = UIButton(type: .custom) importPrivateKeyButton.addTarget(self, action: #selector(importPrivateKeyButtonAction), for: .touchUpInside) importPrivateKeyButton.backgroundColor = .dw_background() @@ -127,36 +132,40 @@ private extension ReceiveViewController { importPrivateKeyButton.setTitle(NSLocalizedString("Import Private Key", comment: "Import Private Key"), for: .normal) importPrivateKeyButton.isHidden = !allowedToImportPrivateKey mainStackView.addArrangedSubview(importPrivateKeyButton) - + mainStackView.addArrangedSubview(EmptyView()) - + NSLayoutConstraint.activate([ mainStackView.topAnchor.constraint(equalTo: view.topAnchor), mainStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor), mainStackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), mainStackView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), - + receiveContentView.heightAnchor.constraint(equalToConstant: 373), importPrivateKeyButton.heightAnchor.constraint(equalToConstant: 64), ]) } } +// MARK: SpecifyAmountViewControllerDelegate + extension ReceiveViewController: SpecifyAmountViewControllerDelegate { func specifyAmountViewController(_ vc: SpecifyAmountViewController, didInput amount: UInt64) { let model = DWReceiveModel(amount: amount) - + let requestController = DWRequestAmountViewController(model: model) requestController.delegate = self - self.present(requestController, animated: true) + present(requestController, animated: true) } } +// MARK: DWRequestAmountViewControllerDelegate + extension ReceiveViewController: DWRequestAmountViewControllerDelegate { func requestAmountViewController(_ controller: DWRequestAmountViewController, didReceiveAmountWithInfo info: String) { controller.dismiss(animated: true) { self.navigationController?.popViewController(animated: true) - + let popAnimationDuration = 300 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(popAnimationDuration)) { self.navigationController?.view.dw_showInfoHUD(withText: info) @@ -165,11 +174,12 @@ extension ReceiveViewController: DWRequestAmountViewControllerDelegate { } } +// MARK: DWImportWalletInfoViewControllerDelegate + extension ReceiveViewController: DWImportWalletInfoViewControllerDelegate { func importWalletInfoViewControllerScanPrivateKeyAction(_ controller: DWImportWalletInfoViewController) { controller.dismiss(animated: true) { self.delegate?.importPrivateKeyButtonAction(self) } - } } diff --git a/DashWallet/Sources/UI/Payments/Receive/Views/ReceiveContentView.swift b/DashWallet/Sources/UI/Payments/Receive/Views/ReceiveContentView.swift index 2b7671d50..2748be387 100644 --- a/DashWallet/Sources/UI/Payments/Receive/Views/ReceiveContentView.swift +++ b/DashWallet/Sources/UI/Payments/Receive/Views/ReceiveContentView.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,6 +17,8 @@ import UIKit +// MARK: - ReceiveContentView + @objc(DWReceiveContentView) final class ReceiveContentView: UIView { @IBOutlet var qrCodeButton: UIButton! @@ -24,39 +26,43 @@ final class ReceiveContentView: UIView { @IBOutlet var addressButton: UIButton! @IBOutlet var specifyAmountButton: UIButton! @IBOutlet var secondButton: UIButton! - + private var model: DWReceiveModelProtocol! - private var feedbackGenerator: UINotificationFeedbackGenerator = UINotificationFeedbackGenerator() - + private var feedbackGenerator = UINotificationFeedbackGenerator() + @objc public var viewType: ReceiveViewType = .default - + public var specifyAmountHandler: (() -> Void)? - + @objc public var shareHandler: ((UIButton) -> Void)? - + @objc public var exitHandler: (() -> Void)? - - @IBAction func addressButtonAction() { + + @IBAction + func addressButtonAction() { feedbackGenerator.notificationOccurred(.success) model.copyAddressToPasteboard() } - - @IBAction func qrButtonAction() { + + @IBAction + func qrButtonAction() { feedbackGenerator.notificationOccurred(.success) model.copyQRImageToPasteboard() } - - @IBAction func specifyAmountButtonAction() { + + @IBAction + func specifyAmountButtonAction() { specifyAmountHandler?() } - - @IBAction func secondButtonAction() { + + @IBAction + func secondButtonAction() { if viewType == .default { shareHandler?(secondButton) - }else{ + } else { exitHandler?() } } @@ -65,49 +71,51 @@ final class ReceiveContentView: UIView { func setSpecifyAmountButtonHidden(_ hidden: Bool) { specifyAmountButton.isHidden = hidden } - + @objc func setSecondButtonHidden(_ hidden: Bool) { secondButton.isHidden = hidden } - + @objc static func view(with model: DWReceiveModelProtocol) -> ReceiveContentView { let view = UINib.view(Self.self) view.model = model - + model.delegate = view - + view.configureHierarchy() view.reloadView() - + return view } } -private extension ReceiveContentView { +extension ReceiveContentView { private func configureHierarchy() { specifyAmountButton.setTitle(NSLocalizedString("Specify Amount", comment: "Receive screen"), for: .normal) secondButton.setTitle(viewType.secondButtonTitle, for: .normal) } - + private func reloadView() { let hasValue = model.paymentAddress != nil - + addressButton.setTitle(model.paymentAddress, for: .normal) addressButton.isHidden = !hasValue - + qrCodeButton.setImage(model.qrCodeImage, for: .normal) qrCodeButton.isHidden = model.qrCodeImage == nil - + specifyAmountButton.isEnabled = hasValue - + if viewType == .default { secondButton.isEnabled = hasValue } } } +// MARK: DWReceiveModelDelegate + extension ReceiveContentView: DWReceiveModelDelegate { func receivingInfoDidUpdate() { reloadView() diff --git a/DashWallet/Sources/UI/Payments/SendReceivePageController.swift b/DashWallet/Sources/UI/Payments/SendReceivePageController.swift index 78da21246..27a473cc8 100644 --- a/DashWallet/Sources/UI/Payments/SendReceivePageController.swift +++ b/DashWallet/Sources/UI/Payments/SendReceivePageController.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,72 +17,79 @@ import UIKit +// MARK: - SendReceivePageControllerDelegate + protocol SendReceivePageControllerDelegate: AnyObject { func sendReceivePageControllerWillChangeSelectedIndex(to index: Int) } +// MARK: - SendReceivePageController + class SendReceivePageController: UIPageViewController { weak var helperDelegate: SendReceivePageControllerDelegate? - - private var isControllerReady: Bool = false - + + private var isControllerReady = false + init() { super.init(transitionStyle: .scroll, navigationOrientation: .horizontal) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + var controllers: [UIViewController]! { didSet { guard isControllerReady else { return } - + let vc = controllers.first! setViewControllers([vc], direction: .forward, animated: false) } } - - var selectedIndex: Int = 0 - + + var selectedIndex = 0 + func setSelectedIndex(_ idx: Int, animated: Bool) { selectedIndex = idx let vc = controllers[selectedIndex] let direction = direction(for: vc) setViewControllers([vc], direction: direction, animated: animated) } - + override func viewDidLoad() { super.viewDidLoad() - + delegate = self dataSource = self } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - + guard selectedIndex < controllers.count else { return } let vc = controllers[selectedIndex] setViewControllers([vc], direction: .forward, animated: false) - + isControllerReady = true } } +// MARK: UIPageViewControllerDelegate + extension SendReceivePageController: UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { guard let vc = pendingViewControllers.first else { return } - + let idx = index(of: vc) selectedIndex = idx helperDelegate?.sendReceivePageControllerWillChangeSelectedIndex(to: idx) } - - func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { + + func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], + transitionCompleted completed: Bool) { print("pageViewController", finished, completed, previousViewControllers) guard finished else { return } - + if let vc = previousViewControllers.first, !completed { let idx = index(of: vc) selectedIndex = idx @@ -91,20 +98,22 @@ extension SendReceivePageController: UIPageViewControllerDelegate { } } +// MARK: UIPageViewControllerDataSource + extension SendReceivePageController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { if controllers.last == viewController { return controllers.first } - + return nil } - + func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { if controllers.first == viewController { return controllers.last } - + return nil } } @@ -114,7 +123,7 @@ extension SendReceivePageController { func index(of controller: UIViewController) -> Int { controllers.first == controller ? 0 : 1 } - + func direction(for controller: UIViewController) -> UIPageViewController.NavigationDirection { let idx = index(of: controller) return idx == 0 ? .reverse : .forward diff --git a/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift b/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift index 5ef4c8583..735c25679 100644 --- a/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift +++ b/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -17,6 +17,8 @@ import Foundation +// MARK: - BackupInfoItem + private enum BackupInfoItem { case notStoredByDash case unableToRestore @@ -29,7 +31,7 @@ extension BackupInfoItem { case .unableToRestore: return "You will NOT be able to restore the wallet without a recovery phrase" } } - + var description: String { switch self { case .notStoredByDash: return "Anyone that has your recovery phrase can access your funds." @@ -45,98 +47,106 @@ extension BackupInfoItem { } } +// MARK: - SecureWalletInfoType + @objc(DWSecureWalletInfoType) enum SecureWalletInfoType: Int { @objc(DWSecureWalletInfoType_Setup) case setup - + @objc(DWSecureWalletInfoType_Reminder) case reminder } +// MARK: - BackupInfoViewControllerDelegate + @objc(DWBackupInfoViewControllerDelegate) -protocol BackupInfoViewControllerDelegate: DWSecureWalletDelegate { -} +protocol BackupInfoViewControllerDelegate: DWSecureWalletDelegate { } + +// MARK: - BackupInfoViewController @objc(DWBackupInfoViewController) final class BackupInfoViewController: BaseViewController { @IBOutlet private var titleLabel: UILabel! @IBOutlet private var subtitleLabel: UILabel! - + @IBOutlet private var contentView: UIStackView! - + @IBOutlet private var bottomButtonStack: UIStackView! @IBOutlet private var showRecoveryPhraseButton: UIButton! @IBOutlet private var skipButton: UIButton! - + private var closeButton: UIBarButtonItem! private var seedPhraseModel: DWPreviewSeedPhraseModel! - + @objc public weak var delegate: BackupInfoViewControllerDelegate? - + public var type: SecureWalletInfoType = .setup - + @objc - var isSkipButtonHidden: Bool = true { + var isSkipButtonHidden = true { didSet { if isViewLoaded { skipButton?.isHidden = isSkipButtonHidden } } } - + @objc - var isCloseButtonHidden: Bool = false { + var isCloseButtonHidden = false { didSet { if isViewLoaded { reloadCloseButton() } } } - + @objc - var isAllActionHidden: Bool = false { + var isAllActionHidden = false { didSet { if isViewLoaded { reloadView() } } } - + @objc private func closeAction() { delegate?.secureWalletRoutineDidCanceled(self) } - - @IBAction func skipButtonAction() { + + @IBAction + func skipButtonAction() { delegate?.secureWalletRoutineDidCanceled(self) } - - @IBAction func backupButtonAction() { + + @IBAction + func backupButtonAction() { let controller = DWBackupSeedPhraseViewController(model: seedPhraseModel) controller.shouldCreateNewWalletOnScreenshot = shouldCreateNewWalletOnScreenshot controller.delegate = delegate navigationController?.pushViewController(controller, animated: true) } - - @IBAction func closeButtonAction() { + + @IBAction + func closeButtonAction() { delegate?.secureWalletRoutineDidCanceled(self) } - + override func viewDidLoad() { super.viewDidLoad() - + if type == .setup { // Create wallet entry point - self.seedPhraseModel = DWPreviewSeedPhraseModel() + seedPhraseModel = DWPreviewSeedPhraseModel() seedPhraseModel.getOrCreateNewWallet() } - + configureHierarchy() } - + @objc static func controller(with type: SecureWalletInfoType) -> BackupInfoViewController { let controller = vc(BackupInfoViewController.self, from: sb("BackupInfo")) @@ -148,41 +158,43 @@ final class BackupInfoViewController: BaseViewController { extension BackupInfoViewController { private func configureHierarchy() { titleLabel.text = NSLocalizedString("Backup your recovery phrase", comment: "Back up wallet") - subtitleLabel.text = NSLocalizedString("You will need this recovery phrase to access your funds if this device is lost, damaged or if Dash Wallet is uninstalled from this device.", comment: "Back up wallet") + subtitleLabel + .text = NSLocalizedString("You will need this recovery phrase to access your funds if this device is lost, damaged or if Dash Wallet is uninstalled from this device.", + comment: "Back up wallet") showRecoveryPhraseButton.setTitle(NSLocalizedString("Show Recovery Phrase", comment: "Back up wallet"), for: .normal) skipButton.setTitle(NSLocalizedString("Skip", comment: "Back up wallet"), for: .normal) - + show(item: .notStoredByDash) show(item: .unableToRestore) - + skipButton.isHidden = isSkipButtonHidden reloadCloseButton() reloadView() } - + private func reloadView() { if isAllActionHidden { hideCloseButton() bottomButtonStack.isHidden = true - }else{ + } else { showCloseButtonIfNeeded() bottomButtonStack.isHidden = false } } - + private func reloadCloseButton() { if isCloseButtonHidden { hideCloseButton() - }else{ + } else { showCloseButton() } } - + private func show(item: BackupInfoItem) { let view = itemView(from: item) contentView.addArrangedSubview(view) } - + private func itemView(from item: BackupInfoItem) -> BackupInfoItemView { let view = BackupInfoItemView.view() view.titleLabel.text = item.title @@ -190,20 +202,20 @@ extension BackupInfoViewController { view.iconView.image = item.icon return view } - + private func showCloseButtonIfNeeded() { if !isCloseButtonHidden { showCloseButton() } } + private func showCloseButton() { let item = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(closeAction)) navigationItem.rightBarButtonItem = item - + closeButton = item - } - + private func hideCloseButton() { navigationItem.rightBarButtonItem = nil closeButton = nil @@ -212,10 +224,12 @@ extension BackupInfoViewController { extension BackupInfoViewController { var shouldCreateNewWalletOnScreenshot: Bool { - return type == .reminder + type == .reminder } } +// MARK: NavigationBarDisplayable + extension BackupInfoViewController: NavigationBarDisplayable { var isBackButtonHidden: Bool { isAllActionHidden == false } } diff --git a/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/Views/BackupInfoItemView.swift b/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/Views/BackupInfoItemView.swift index 6dae281a1..4a386030f 100644 --- a/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/Views/BackupInfoItemView.swift +++ b/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/Views/BackupInfoItemView.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -21,10 +21,10 @@ final class BackupInfoItemView: UIView { @IBOutlet var iconView: UIImageView! @IBOutlet var titleLabel: UILabel! @IBOutlet var descriptionLabel: UILabel! - + override func awakeFromNib() { super.awakeFromNib() - + titleLabel.font = .dw_font(forTextStyle: .subheadline).withWeight(UIFont.Weight.semibold.rawValue) } } diff --git a/DashWallet/Sources/UI/Setup/SecureWallet/VerifiedSuccessfully/VerifiedSuccessfullyViewController.swift b/DashWallet/Sources/UI/Setup/SecureWallet/VerifiedSuccessfully/VerifiedSuccessfullyViewController.swift index 2dc65d674..51c7ce2bb 100644 --- a/DashWallet/Sources/UI/Setup/SecureWallet/VerifiedSuccessfully/VerifiedSuccessfullyViewController.swift +++ b/DashWallet/Sources/UI/Setup/SecureWallet/VerifiedSuccessfully/VerifiedSuccessfullyViewController.swift @@ -17,12 +17,14 @@ import Foundation +// MARK: - VerifiedSuccessfullyViewController + @objc(DWVerifiedSuccessfullyViewController) final class VerifiedSuccessfullyViewController : UIViewController, NavigationFullscreenable { let requiresNoNavigationBar = true override var preferredStatusBarStyle: UIStatusBarStyle { .default } @objc public weak var delegate: DWSecureWalletDelegate? = nil - + @IBOutlet var scrollView: UIScrollView! @IBOutlet var securityImageView: UIImageView! @IBOutlet var titleLabel: UILabel! @@ -39,12 +41,14 @@ final class VerifiedSuccessfullyViewController : UIViewController, NavigationFul scrollView.flashScrollIndicators() } - @objc static func controller() -> VerifiedSuccessfullyViewController { + @objc + static func controller() -> VerifiedSuccessfullyViewController { let storyboard = UIStoryboard(name: "VerifiedSuccessfully", bundle: nil) return storyboard.instantiateInitialViewController() as! VerifiedSuccessfullyViewController } - - @IBAction func continueButtonAction() { + + @IBAction + func continueButtonAction() { delegate?.secureWalletRoutineDidFinish(self) } } diff --git a/DashWallet/Sources/UI/Views/BadgeView.swift b/DashWallet/Sources/UI/Views/BadgeView.swift index 787d1db5c..e8f93ac41 100644 --- a/DashWallet/Sources/UI/Views/BadgeView.swift +++ b/DashWallet/Sources/UI/Views/BadgeView.swift @@ -17,6 +17,8 @@ import UIKit +// MARK: - BadgeView + final class BadgeView: UIView { private(set) var label: UILabel! @@ -85,3 +87,4 @@ final class BadgeView: UIView { ]) } } + diff --git a/DashWallet/Sources/UI/Views/EmptyView.swift b/DashWallet/Sources/UI/Views/EmptyView.swift index cd9702d39..b9ac2c6ea 100644 --- a/DashWallet/Sources/UI/Views/EmptyView.swift +++ b/DashWallet/Sources/UI/Views/EmptyView.swift @@ -1,4 +1,4 @@ -// +// // Created by PT // Copyright © 2023 Dash Core Group. All rights reserved. // @@ -20,13 +20,13 @@ import UIKit final class EmptyView: UIView { override init(frame: CGRect) { super.init(frame: frame) - + backgroundColor = .clear } - + required init?(coder: NSCoder) { super.init(coder: coder) - + backgroundColor = .clear } } diff --git a/DashWallet/Sources/UI/Views/Navigation/BaseNavigationController.swift b/DashWallet/Sources/UI/Views/Navigation/BaseNavigationController.swift index 4fe711b9e..99ed643b0 100644 --- a/DashWallet/Sources/UI/Views/Navigation/BaseNavigationController.swift +++ b/DashWallet/Sources/UI/Views/Navigation/BaseNavigationController.swift @@ -27,7 +27,7 @@ extension NavigationStackControllable { func shouldPopViewController() -> Bool { true } } -// MARK: - NavigationBarDisplayable +// MARK: - NavigationBarStyleable protocol NavigationBarStyleable: UIViewController { var backButtonTintColor: UIColor { get } @@ -88,16 +88,17 @@ class BaseNavigationController: UINavigationController { fatalError("init(coder:) has not been implemented") } - @IBAction func cancelButtonAction() { + @IBAction + func cancelButtonAction() { dismiss(animated: true) } - + @objc public func setCancelButtonHidden(_ hidden: Bool) { let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonAction)) topViewController?.navigationItem.rightBarButtonItem = cancelButton } - + override func responds(to aSelector: Selector!) -> Bool { super.responds(to: aSelector) || (_delegate?.responds(to: aSelector!) ?? false) } @@ -153,11 +154,11 @@ extension BaseNavigationController: UINavigationControllerDelegate { var backButtonTintColor = UIColor.dw_label() var prefersLargeTitles = false var largeTitleDisplayMode = UINavigationItem.LargeTitleDisplayMode.automatic; - + if let viewController = viewController as? NavigationBarDisplayable { hideBackButton = viewController.isBackButtonHidden hideNavigationBar = viewController.isNavigationBarHidden - + } else if let vc = viewController as? NavigationFullscreenable { hideNavigationBar = vc.requiresNoNavigationBar } @@ -167,7 +168,7 @@ extension BaseNavigationController: UINavigationControllerDelegate { prefersLargeTitles = viewController.prefersLargeTitles largeTitleDisplayMode = viewController.largeTitleDisplayMode } - + delegate?.navigationController?(navigationController, willShow: viewController, animated: animated) if !hideBackButton && viewController.navigationItem.leftBarButtonItem == nil { @@ -184,14 +185,14 @@ extension BaseNavigationController: UINavigationControllerDelegate { } viewController.navigationItem.hidesBackButton = true - + if let vc = viewController as? NavigationBarAppearanceCustomizable { vc.setNavigationBarAppearance() } navigationController.setNavigationBarHidden(hideNavigationBar, animated: animated) navigationController.navigationBar.prefersLargeTitles = prefersLargeTitles - + viewController.navigationItem.largeTitleDisplayMode = largeTitleDisplayMode } diff --git a/DashWallet/Sources/UI/Views/SharedViews/Keyboard/NumberKeyboardButton.swift b/DashWallet/Sources/UI/Views/SharedViews/Keyboard/NumberKeyboardButton.swift index cb4285278..290442272 100644 --- a/DashWallet/Sources/UI/Views/SharedViews/Keyboard/NumberKeyboardButton.swift +++ b/DashWallet/Sources/UI/Views/SharedViews/Keyboard/NumberKeyboardButton.swift @@ -159,12 +159,12 @@ class NumberKeyboardButton: UIView { UIView.animate(withDuration: 0.075, delay: 0, options: [.curveEaseOut, .beginFromCurrentState]) { [unowned self] in - if self.isHighlighted { - self.backgroundColor = Styles.backgroundHighlightedColor - self.titleLabel.textColor = Styles.textHighlightedColor + if isHighlighted { + backgroundColor = Styles.backgroundHighlightedColor + titleLabel.textColor = Styles.textHighlightedColor } else { - self.backgroundColor = customBackgroundColor - self.titleLabel.textColor = Styles.textColor + backgroundColor = customBackgroundColor + titleLabel.textColor = Styles.textColor } } } From 6e1c619917c1848e6226e5d5ff2ffbfe84284e8a Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 11 May 2023 16:58:44 +0700 Subject: [PATCH 10/38] refactor: Rewrite 'DWTxListEmptyTableViewCell' in swift --- .../Views/Cells/DWTxListEmptyTableViewCell.m | 41 ------ .../Views/Cells/DWTxListTableViewCell.xib | 9 +- .../Home/Views/Cells/SyncingHeaderView.swift | 137 ++++++++++++++++++ ...wCell.h => TxListEmptyTableViewCell.swift} | 18 ++- ...wCell.xib => TxListEmptyTableViewCell.xib} | 19 ++- 5 files changed, 160 insertions(+), 64 deletions(-) delete mode 100644 DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.m create mode 100644 DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift rename DashWallet/Sources/UI/Home/Views/Cells/{DWTxListEmptyTableViewCell.h => TxListEmptyTableViewCell.swift} (55%) rename DashWallet/Sources/UI/Home/Views/Cells/{DWTxListEmptyTableViewCell.xib => TxListEmptyTableViewCell.xib} (89%) diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.m b/DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.m deleted file mode 100644 index b0535d96c..000000000 --- a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.m +++ /dev/null @@ -1,41 +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 "DWTxListEmptyTableViewCell.h" - -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWTxListEmptyTableViewCell () - -@property (strong, nonatomic) IBOutlet UILabel *placeholderLabel; - -@end - -@implementation DWTxListEmptyTableViewCell - -- (void)awakeFromNib { - [super awakeFromNib]; - - self.placeholderLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; - self.placeholderLabel.text = NSLocalizedString(@"There are no transactions to display", nil); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListTableViewCell.xib b/DashWallet/Sources/UI/Home/Views/Cells/DWTxListTableViewCell.xib index d2617130e..cc5258300 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListTableViewCell.xib +++ b/DashWallet/Sources/UI/Home/Views/Cells/DWTxListTableViewCell.xib @@ -1,9 +1,9 @@ - + - + @@ -11,7 +11,7 @@ - + @@ -63,9 +63,6 @@ - - - diff --git a/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift b/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift new file mode 100644 index 000000000..aff0ba8a6 --- /dev/null +++ b/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift @@ -0,0 +1,137 @@ +// +// Created by PT +// Copyright © 2023 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: - SyncingHeaderViewDelegate + +@objc(DWSyncingHeaderViewDelegate) +protocol SyncingHeaderViewDelegate: AnyObject { + func syncingHeaderView(_ view: SyncingHeaderView, filterButtonAction sender: UIButton) + func syncingHeaderView(_ view: SyncingHeaderView, syncingButtonAction sender: UIButton) +} + +// MARK: - SyncingHeaderView + +@objc(DWSyncingHeaderView) +final class SyncingHeaderView: UITableViewHeaderFooterView { + + @objc + weak var delegate: SyncingHeaderViewDelegate? + + @objc + var progress = 0.0 { + didSet { + refreshView() + } + } + + @objc + var isSyncing = false { + didSet { + refreshView() + } + } + + private var syncingButton: UIButton! + + override init(reuseIdentifier: String?) { + super.init(reuseIdentifier: reuseIdentifier) + + backgroundColor = UIColor.dw_secondaryBackground() + + let titleLabel = UILabel() + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.font = UIFont.dw_font(forTextStyle: .headline) + titleLabel.text = NSLocalizedString("History", comment: "") + titleLabel.textColor = UIColor.dw_darkTitle() + titleLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) + addSubview(titleLabel) + + syncingButton = DWButton() + syncingButton.translatesAutoresizingMaskIntoConstraints = false + syncingButton.contentHorizontalAlignment = .right + syncingButton.setTitleColor(UIColor.dw_darkTitle(), for: .normal) + syncingButton.setContentHuggingPriority(.defaultHigh - 1, for: .horizontal) + syncingButton.setContentCompressionResistancePriority(.required - 1, for: .horizontal) + syncingButton.addTarget(self, action: #selector(syncingButtonAction(_:)), for: .touchUpInside) + addSubview(syncingButton) + + let filterButton = UIButton(type: .custom) + filterButton.translatesAutoresizingMaskIntoConstraints = false + filterButton.setImage(UIImage(named: "icon_filter_button"), for: .normal) + filterButton.addTarget(self, action: #selector(filterButtonAction(_:)), for: .touchUpInside) + addSubview(filterButton) + + let padding: CGFloat = 16.0 + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: padding), + bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: padding), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding), + + syncingButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + bottomAnchor.constraint(greaterThanOrEqualTo: syncingButton.bottomAnchor), + syncingButton.centerYAnchor.constraint(equalTo: centerYAnchor), + syncingButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 8.0), + syncingButton.heightAnchor.constraint(equalToConstant: 44.0), + + filterButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + bottomAnchor.constraint(greaterThanOrEqualTo: filterButton.bottomAnchor), + filterButton.centerYAnchor.constraint(equalTo: centerYAnchor), + filterButton.leadingAnchor.constraint(equalTo: syncingButton.trailingAnchor), + trailingAnchor.constraint(equalTo: filterButton.trailingAnchor, constant: 10.0), + filterButton.heightAnchor.constraint(equalToConstant: 44.0), + filterButton.widthAnchor.constraint(equalToConstant: 44.0), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc + func filterButtonAction(_ sender: UIButton) { + delegate?.syncingHeaderView(self, filterButtonAction: sender) + } + + @objc + func syncingButtonAction(_ sender: UIButton) { + delegate?.syncingHeaderView(self, syncingButtonAction: sender) + } +} + +extension SyncingHeaderView { + private func refreshView() { + syncingButton.isHidden = !isSyncing + + guard isSyncing else { + return + } + let percentString = String(format: "%0.1f%%", progress * 100.0) + + let result = NSMutableAttributedString() + + let str1 = NSAttributedString(string: String(format: "%@ ", NSLocalizedString("Syncing", comment: "")), + attributes: [NSAttributedString.Key.font: UIFont.dw_font(forTextStyle: .body)]) + result.append(str1) + + let str2 = NSAttributedString(string: percentString, attributes: [NSAttributedString.Key.font: UIFont.dw_font(forTextStyle: .headline)]) + result.append(str2) + + syncingButton.setAttributedTitle(result, for: .normal) + } +} diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.h b/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.swift similarity index 55% rename from DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.h rename to DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.swift index b3b41ff77..462516831 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.h +++ b/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.swift @@ -1,6 +1,6 @@ // -// Created by Andrew Podkovyrin -// Copyright © 2019 Dash Core Group. All rights reserved. +// Created by PT +// Copyright © 2023 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. @@ -15,12 +15,16 @@ // limitations under the License. // -#import +import UIKit -NS_ASSUME_NONNULL_BEGIN +class TxListEmptyTableViewCell: UITableViewCell { -@interface DWTxListEmptyTableViewCell : UITableViewCell + @IBOutlet var placeholderLabel: UILabel! -@end + override func awakeFromNib() { + super.awakeFromNib() -NS_ASSUME_NONNULL_END + placeholderLabel.font = UIFont.preferredFont(forTextStyle: .footnote) + placeholderLabel.text = NSLocalizedString("There are no transactions to display", comment: "") + } +} diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.xib b/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.xib similarity index 89% rename from DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.xib rename to DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.xib index b7d4f67fa..2414a997e 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListEmptyTableViewCell.xib +++ b/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -13,18 +11,18 @@ - + - + - + - + + From c8e02d5144c3a2cfaf0cbae4e4df8988664d4d6e Mon Sep 17 00:00:00 2001 From: tikhop Date: Fri, 12 May 2023 12:59:13 +0700 Subject: [PATCH 11/38] refactor: Move 'ShortcutModel' out of 'HomeModel' --- ...iewController+DWSecureWalletDelegateImpl.m | 3 +- .../Sources/UI/Home/Models/DWHomeModel.m | 4 --- .../UI/Home/Protocols/DWHomeProtocol.h | 7 ++--- .../UI/Home/Protocols/DWShortcutsProtocol.h | 30 ------------------- .../Shortcuts/Models/ShortcutsModel.swift | 28 ++++------------- .../Home/Views/Shortcuts/ShortcutsView.swift | 23 +++++++------- .../UI/Onboarding/Stubs/DWHomeModelStub.m | 8 +---- 7 files changed, 21 insertions(+), 82 deletions(-) delete mode 100644 DashWallet/Sources/UI/Home/Protocols/DWShortcutsProtocol.h diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController+DWSecureWalletDelegateImpl.m b/DashWallet/Sources/UI/Home/DWHomeViewController+DWSecureWalletDelegateImpl.m index 1c02b9f27..cd5e5424e 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController+DWSecureWalletDelegateImpl.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController+DWSecureWalletDelegateImpl.m @@ -28,7 +28,8 @@ - (void)secureWalletRoutineDidCanceled:(UIViewController *)controller { } - (void)secureWalletRoutineDidVerify:(UIViewController *)controller { - [self.model reloadShortcuts]; + DWHomeView *view = (DWHomeView *) self.view; + [view reloadShortcuts]; } - (void)secureWalletRoutineDidFinish:(DWVerifiedSuccessfullyViewController *)controller { diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index e347bdf83..a9b87e985 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -65,7 +65,6 @@ @implementation DWHomeModel @synthesize payModel = _payModel; @synthesize receiveModel = _receiveModel; @synthesize dashPayModel = _dashPayModel; -@synthesize shortcutsModel = _shortcutsModel; @synthesize syncModel = _syncModel; @synthesize updatesObserver = _updatesObserver; @synthesize allDataSource = _allDataSource; @@ -101,9 +100,6 @@ - (instancetype)init { _receiveModel = [[DWReceiveModel alloc] init]; [_receiveModel updateReceivingInfo]; - - _shortcutsModel = [[DWShortcutsModel alloc] initWithDataSource:self]; - _payModel = [[DWPayModel alloc] init]; _balanceDisplayOptions = [[DWBalanceDisplayOptions alloc] init]; diff --git a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h index 251d7b923..0034b7df8 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h @@ -19,10 +19,8 @@ #import "DWBalanceProtocol.h" #import "DWDashPayProtocol.h" -#import "DWShortcutsProtocol.h" #import "DWSyncContainerProtocol.h" #import "DWTxDisplayModeProtocol.h" -#import "dashwallet-Swift.h" NS_ASSUME_NONNULL_BEGIN @@ -42,9 +40,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)homeModel:(id)model didReceiveNewIncomingTransaction:(DSTransaction *)transaction; +- (void)homeModelWantToReloadShortcuts:(id)model; @end -@protocol DWHomeProtocol +@protocol DWHomeProtocol @property (nullable, nonatomic, weak) id updatesObserver; @@ -59,8 +58,6 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, nonatomic, assign, getter=isWalletEmpty) BOOL walletEmpty; @property (readonly, nonatomic, assign, getter=isAllowedToShowReclassifyYourTransactions) BOOL allowedToShowReclassifyYourTransactions; -- (void)reloadShortcuts; - - (void)walletBackupReminderWasShown; - (void)registerForPushNotifications; diff --git a/DashWallet/Sources/UI/Home/Protocols/DWShortcutsProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWShortcutsProtocol.h deleted file mode 100644 index 89d886906..000000000 --- a/DashWallet/Sources/UI/Home/Protocols/DWShortcutsProtocol.h +++ /dev/null @@ -1,30 +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 DWShortcutsModel; - -@protocol DWShortcutsProtocol - -@property (readonly, nonatomic, strong) DWShortcutsModel *shortcutsModel; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutsModel.swift b/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutsModel.swift index 886a020e2..90c09f6b4 100644 --- a/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutsModel.swift +++ b/DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutsModel.swift @@ -17,37 +17,18 @@ import Foundation -// MARK: - ShortcutsModelDataSource - -@objc(DWShortcutsModelDataSource) -protocol ShortcutsModelDataSource: AnyObject { - func shouldShowCreateUserNameButton() -> Bool -} - -// MARK: - ShortcutsModelDelegate - -@objc(DWShortcutsModelDelegate) -protocol ShortcutsModelDelegate: AnyObject { - func shortcutItemsDidChange() -} let MAX_SHORTCUTS_COUNT = 4 // MARK: - ShortcutsModel -@objc(DWShortcutsModel) -class ShortcutsModel: NSObject { +final class ShortcutsModel { private var mutableItems: [ShortcutAction] = [] - weak var dataSource: ShortcutsModelDataSource? - weak var delegate: ShortcutsModelDelegate? + var shortcutItemsDidChangeHandler: (() -> ())? @objc - init(dataSource: ShortcutsModelDataSource) { - super.init() - - self.dataSource = dataSource - + init() { reloadShortcuts() } @@ -58,9 +39,10 @@ class ShortcutsModel: NSObject { @objc func reloadShortcuts() { mutableItems = Self.userShortcuts() - delegate?.shortcutItemsDidChange() + shortcutItemsDidChangeHandler?() } + //TODO: Move this to HomeModel static func userShortcuts() -> [ShortcutAction] { let options = DWGlobalOptions.sharedInstance() let walletNeedsBackup = options.walletNeedsBackup diff --git a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift index 2eb49713d..117b641cb 100644 --- a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift +++ b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift @@ -85,13 +85,7 @@ class ShortcutsView: UIView { @IBOutlet var collectionViewHeightConstraint: NSLayoutConstraint! - @objc - var model: ShortcutsModel! { - didSet { - model.delegate = self - collectionView.reloadData() - } - } + var model = ShortcutsModel() override init(frame: CGRect) { super.init(frame: frame) @@ -103,7 +97,15 @@ class ShortcutsView: UIView { commonInit() } + func reloadData() { + model.reloadShortcuts() + } + private func commonInit() { + model.shortcutItemsDidChangeHandler = { [weak self] in + self?.collectionView.reloadData() + } + Bundle.main.loadNibNamed(String(describing: type(of: self)), owner: self, options: nil) backgroundColor = .dw_secondaryBackground() @@ -209,12 +211,9 @@ extension ShortcutsView: UICollectionViewDataSource, UICollectionViewDelegate, U // MARK: ShortcutsModelDataSource, ShortcutsModelDelegate -extension ShortcutsView: ShortcutsModelDataSource, ShortcutsModelDelegate { +extension ShortcutsView { + // TODO: DashPay func shouldShowCreateUserNameButton() -> Bool { false } - - func shortcutItemsDidChange() { - collectionView.reloadData() - } } diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m index 45f090123..fd3d0d765 100644 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m +++ b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWHomeModelStub () +@interface DWHomeModelStub () @property (readonly, nonatomic, copy) NSArray *stubTxs; @@ -48,7 +48,6 @@ @implementation DWHomeModelStub @synthesize payModel = _payModel; @synthesize receiveModel = _receiveModel; @synthesize dashPayModel = _dashPayModel; -@synthesize shortcutsModel = _shortcutsModel; @synthesize syncModel = _syncModel; @synthesize updatesObserver = _updatesObserver; @synthesize allDataSource = _allDataSource; @@ -66,7 +65,6 @@ - (instancetype)init { #if DASHPAY_ENABLED _dashPayModel = [[DWDashPayModel alloc] init]; // TODO: DP consider using stub #endif /* DASHPAY_ENABLED */ - _shortcutsModel = [[DWShortcutsModel alloc] initWithDataSource:self]; _payModel = [[DWPayModelStub alloc] init]; _balanceDisplayOptions = [[DWBalanceDisplayOptionsStub alloc] init]; _allowedToShowReclassifyYourTransactions = NO; @@ -112,10 +110,6 @@ - (BOOL)isWalletEmpty { return NO; } -- (void)reloadShortcuts { - [self.shortcutsModel reloadShortcuts]; -} - - (void)retrySyncing { } From ff3d108a81a168ceb15c724757a0424f846bf675 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 15 May 2023 14:28:30 +0700 Subject: [PATCH 12/38] refactor: Make 'DWHomeModel' smaller and rewrite 'DWHomeView' in swift --- DashWallet.xcodeproj/project.pbxproj | 150 ++++++---- .../SyncingActivityMonitor.swift | 25 +- .../Categories/UITableView+DashWallet.swift | 8 +- .../Sources/UI/Home/DWHomeViewController.m | 1 - .../UI/Home/Models/DWBalanceDisplayOptions.m | 4 + .../Sources/UI/Home/Models/DWHomeModel.m | 33 +- .../Sources/UI/Home/Models/HomeModel.swift | 18 ++ .../Models/TransactionListDataSource.swift | 5 +- .../UI/Home/Protocols/DWBalanceProtocol.h | 2 +- .../UI/Home/Protocols/DWHomeProtocol.h | 4 +- .../SyncingAlertViewController.swift | 32 +- .../Home/Views/Cells/SyncingHeaderView.swift | 20 +- .../Cells/TxListEmptyTableViewCell.swift | 4 +- .../Views/Cells/TxListEmptyTableViewCell.xib | 4 +- .../Views/Cells/TxListTableViewCell.swift | 3 - ...leViewCell.xib => TxListTableViewCell.xib} | 5 +- .../Sources/UI/Home/Views/DWHomeHeaderView.h | 48 --- .../Sources/UI/Home/Views/DWHomeHeaderView.m | 206 ------------- DashWallet/Sources/UI/Home/Views/DWHomeView.h | 51 ---- DashWallet/Sources/UI/Home/Views/DWHomeView.m | 266 ----------------- .../HomeBalanceView.swift | 33 +- .../HomeBalanceView.xib | 0 .../DashPayProfileView.swift | 4 +- .../Home Header View/HomeHeaderModel.swift | 48 +++ .../Home Header View/HomeHeaderView.swift | 213 +++++++++++++ .../Sources/UI/Home/Views/HomeView.swift | 281 ++++++++++++++++++ .../UI/Home/Views/Sync View/SyncModel.swift | 83 ++++++ .../Home/Views/{ => Sync View}/SyncView.swift | 35 ++- .../SyncView.xib} | 0 .../UI/Home/Views/SyncingHeaderView.swift | 133 --------- .../UI/Main/MainTabbarController.swift | 101 +++++++ .../Sources/UI/Main/Views/PaymentButton.swift | 22 ++ .../UI/Onboarding/Stubs/DWHomeModelStub.m | 2 - .../Sources/UI/RootNavigation/DWRootModel.m | 3 +- DashWallet/Sources/UI/Views/BadgeView.swift | 2 +- .../Sources/UI/Views/ProgressView.swift | 6 +- .../Item/Model/TransactionDataItem.swift | 2 +- DashWallet/dashwallet-Bridging-Header.h | 37 ++- 38 files changed, 1011 insertions(+), 883 deletions(-) create mode 100644 DashWallet/Sources/UI/Home/Models/HomeModel.swift rename DashWallet/Sources/UI/Home/Views/Cells/{DWTxListTableViewCell.xib => TxListTableViewCell.xib} (95%) delete mode 100644 DashWallet/Sources/UI/Home/Views/DWHomeHeaderView.h delete mode 100644 DashWallet/Sources/UI/Home/Views/DWHomeHeaderView.m delete mode 100644 DashWallet/Sources/UI/Home/Views/DWHomeView.h delete mode 100644 DashWallet/Sources/UI/Home/Views/DWHomeView.m rename DashWallet/Sources/UI/Home/Views/{ => Home Balance View}/HomeBalanceView.swift (87%) rename DashWallet/Sources/UI/Home/Views/{ => Home Balance View}/HomeBalanceView.xib (100%) rename DashWallet/Sources/UI/Home/Views/{ => Home Header View}/DashPayProfileView.swift (97%) create mode 100644 DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderModel.swift create mode 100644 DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift create mode 100644 DashWallet/Sources/UI/Home/Views/HomeView.swift create mode 100644 DashWallet/Sources/UI/Home/Views/Sync View/SyncModel.swift rename DashWallet/Sources/UI/Home/Views/{ => Sync View}/SyncView.swift (90%) rename DashWallet/Sources/UI/Home/Views/{DWSyncView.xib => Sync View/SyncView.xib} (100%) delete mode 100644 DashWallet/Sources/UI/Home/Views/SyncingHeaderView.swift create mode 100644 DashWallet/Sources/UI/Main/MainTabbarController.swift create mode 100644 DashWallet/Sources/UI/Main/Views/PaymentButton.swift diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index d5fc7981c..5e553a34b 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -141,7 +141,6 @@ 2A2CD71822F99CAE008C7BC9 /* ShortcutsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A2CD71722F99CAE008C7BC9 /* ShortcutsView.xib */; }; 2A2CD72222F9B571008C7BC9 /* DWIntrinsicCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD72122F9B571008C7BC9 /* DWIntrinsicCollectionView.m */; }; 2A2CD72522FA05DD008C7BC9 /* DWPressableButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD72422FA05DD008C7BC9 /* DWPressableButton.m */; }; - 2A307CBC22E8925100A18347 /* DWHomeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A307CBB22E8925100A18347 /* DWHomeView.m */; }; 2A307CBF22E8A44200A18347 /* DWButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A307CBE22E8A44200A18347 /* DWButton.m */; }; 2A36A88B2350A05B0014DC60 /* DWBackupSeedPhraseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A36A88A2350A05B0014DC60 /* DWBackupSeedPhraseViewController.m */; }; 2A392565234CD21300316EA6 /* DWTabBarButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A392564234CD21300316EA6 /* DWTabBarButton.m */; }; @@ -177,17 +176,14 @@ 2A4E1D5325056297008AC53F /* DWPaymentsButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E1D5225056297008AC53F /* DWPaymentsButton.m */; }; 2A4E531422E9F0A200E5168A /* DWStartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB231D12196E25D00A6E7E6 /* DWStartViewController.m */; }; 2A4E531522E9F0A200E5168A /* DWStartModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB231D62196E5CF00A6E7E6 /* DWStartModel.m */; }; - 2A4E531822EA381F00E5168A /* DWSyncView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E531722EA381F00E5168A /* DWSyncView.m */; }; - 2A4E531A22EA382B00E5168A /* DWSyncView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E531922EA382B00E5168A /* DWSyncView.xib */; }; + 2A4E531A22EA382B00E5168A /* SyncView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E531922EA382B00E5168A /* SyncView.xib */; }; 2A4E531D22EA49FE00E5168A /* DWProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E531C22EA49FE00E5168A /* DWProgressView.m */; }; - 2A4E532022EB2DF400E5168A /* DWHomeHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E531F22EB2DF400E5168A /* DWHomeHeaderView.m */; }; 2A4E533822F023AB00E5168A /* DWHomeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E533722F023AB00E5168A /* DWHomeModel.m */; }; - 2A4E534022F025FE00E5168A /* DWTxListEmptyTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E533E22F025FE00E5168A /* DWTxListEmptyTableViewCell.m */; }; - 2A4E534122F025FE00E5168A /* DWTxListEmptyTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E533F22F025FE00E5168A /* DWTxListEmptyTableViewCell.xib */; }; + 2A4E534122F025FE00E5168A /* TxListEmptyTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E533F22F025FE00E5168A /* TxListEmptyTableViewCell.xib */; }; 2A4E534422F02BC300E5168A /* UIView+DWReuseHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E534322F02BC300E5168A /* UIView+DWReuseHelper.m */; }; 2A4E534B22F03A9E00E5168A /* DWFilterHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E534A22F03A9E00E5168A /* DWFilterHeaderView.m */; }; 2A4E534D22F03AAC00E5168A /* DWFilterHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */; }; - 2A4E535522F1D0D900E5168A /* DWTxListTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E535322F1D0D900E5168A /* DWTxListTableViewCell.xib */; }; + 2A4E535522F1D0D900E5168A /* TxListTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E535322F1D0D900E5168A /* TxListTableViewCell.xib */; }; 2A4E535C22F335C200E5168A /* DWTransactionListDataProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E535B22F335C200E5168A /* DWTransactionListDataProvider.m */; }; 2A5279BC23D994BC00F856D3 /* CoreNFC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A5279BB23D994BC00F856D3 /* CoreNFC.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 2A56EEFB2417E30F002C32F3 /* DWConfirmUsernameContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EEFA2417E30F002C32F3 /* DWConfirmUsernameContentView.m */; }; @@ -201,7 +197,6 @@ 2A5E4548243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5E4546243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.m */; }; 2A5E4549243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A5E4547243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.xib */; }; 2A60C9452444BF3A00AF72CF /* DWConfirmUsernameContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A60C9442444BF3900AF72CF /* DWConfirmUsernameContentView.xib */; }; - 2A612AB724409D6E0060CC77 /* DWDashPayProfileView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A612AB624409D6E0060CC77 /* DWDashPayProfileView.m */; }; 2A63003F2327B4BB00827825 /* DWPaymentOutput+DWView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A63003E2327B4BB00827825 /* DWPaymentOutput+DWView.m */; }; 2A6300422328CCE900827825 /* LockScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A6300412328CCE900827825 /* LockScreen.storyboard */; }; 2A6300452328D07500827825 /* DWLockPinInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6300442328D07500827825 /* DWLockPinInputView.m */; }; @@ -330,7 +325,6 @@ 2A9CEBAD22E1DA4000A50237 /* DWAppRootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBAC22E1DA4000A50237 /* DWAppRootViewController.m */; }; 2A9CEBB522E1EAC900A50237 /* DWTabBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBB422E1EAC900A50237 /* DWTabBarView.m */; }; 2A9CEBB922E1FA1000A50237 /* DWHomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBB822E1FA1000A50237 /* DWHomeViewController.m */; }; - 2A9D72A82497C52E00F79CD8 /* DWBadgeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9D72A72497C52E00F79CD8 /* DWBadgeView.m */; }; 2A9D72AC249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9D72AB249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.m */; }; 2A9E7DC723F6928C00CDA1EE /* DWTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DC623F6928C00CDA1EE /* DWTextField.m */; }; 2A9E7DCA23F6BD5200CDA1EE /* DWUsernameValidationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DC923F6BD5200CDA1EE /* DWUsernameValidationView.m */; }; @@ -428,7 +422,6 @@ 2AD6E5602487E13F00B52F14 /* DWRequestsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E55F2487E13F00B52F14 /* DWRequestsContentViewController.m */; }; 2AD6E5632487E16400B52F14 /* DWRequestsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5622487E16400B52F14 /* DWRequestsViewController.m */; }; 2AD6E56A2487E1DB00B52F14 /* DWRequestsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5692487E1DB00B52F14 /* DWRequestsModel.m */; }; - 2AD7BCBE2673BDE9008FF133 /* DWSyncingHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD7BCBD2673BDE9008FF133 /* DWSyncingHeaderView.m */; }; 2AD85A9F245881740045B480 /* DWQRScanStatusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD85A9E245881740045B480 /* DWQRScanStatusView.m */; }; 2ADB396924223D9B00A6F898 /* DWDashPayAnimationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADB396824223D9B00A6F898 /* DWDashPayAnimationView.m */; }; 2ADB396C242615C200A6F898 /* CALayer+MBAnimationPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADB396B242615C200A6F898 /* CALayer+MBAnimationPersistence.m */; }; @@ -719,9 +712,22 @@ C9F42FB429DD86FB001BC549 /* BackupInfoItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB329DD86FB001BC549 /* BackupInfoItemView.swift */; }; C9F42FB629DD8702001BC549 /* BackupInfoItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F42FB529DD8702001BC549 /* BackupInfoItemView.xib */; }; C9F42FB829DFC507001BC549 /* SpendableTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB729DFC506001BC549 /* SpendableTransaction.swift */; }; + C9F451E52A0B986E00825057 /* MainTabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E42A0B986E00825057 /* MainTabbarController.swift */; }; + C9F451E72A0BA16400825057 /* PaymentButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E62A0BA16400825057 /* PaymentButton.swift */; }; C9F451E92A0BDAE700825057 /* UIApplication+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E82A0BDAE700825057 /* UIApplication+DashWallet.swift */; }; C9F451EB2A0BF10B00825057 /* SyncingAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451EA2A0BF10B00825057 /* SyncingAlertViewController.swift */; }; C9F451EE2A0BF1F500825057 /* SyncingAlertContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451ED2A0BF1F500825057 /* SyncingAlertContentView.swift */; }; + C9F451F32A0C933700825057 /* SyncingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451F22A0C933700825057 /* SyncingHeaderView.swift */; }; + C9F451F52A0CAC9400825057 /* HomeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451F42A0CAC9400825057 /* HomeHeaderView.swift */; }; + C9F451F72A0CAE1300825057 /* SyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451F62A0CAE1300825057 /* SyncView.swift */; }; + C9F451F92A0CB08900825057 /* ProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451F82A0CB08900825057 /* ProgressView.swift */; }; + C9F451FB2A0CC2A800825057 /* DashPayProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451FA2A0CC2A800825057 /* DashPayProfileView.swift */; }; + C9F451FD2A0CC4A300825057 /* BadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451FC2A0CC4A300825057 /* BadgeView.swift */; }; + C9F452012A0CE6C900825057 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452002A0CE6C900825057 /* HomeView.swift */; }; + C9F452032A0CEB5800825057 /* TxListEmptyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452022A0CEB5800825057 /* TxListEmptyTableViewCell.swift */; }; + C9F452052A0CF4E500825057 /* HomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452042A0CF4E500825057 /* HomeModel.swift */; }; + C9F452082A11F28600825057 /* SyncModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452072A11F28600825057 /* SyncModel.swift */; }; + C9F4520B2A1209D100825057 /* HomeHeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F4520A2A1209D100825057 /* HomeHeaderModel.swift */; }; CC5F88E358330F8EE192D5BE /* libPods-DashWalletScreenshotsUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CDD4C961516ED20BC9F01FA /* libPods-DashWalletScreenshotsUITests.a */; }; DE3A167A235B79D705C0A962 /* libPods-dashwallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 982607F21196681DAC51A074 /* libPods-dashwallet.a */; }; FB248B5D1F73803100405AE0 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB248B5C1F73803100405AE0 /* UserNotifications.framework */; }; @@ -1002,8 +1008,6 @@ 2A2CD72122F9B571008C7BC9 /* DWIntrinsicCollectionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWIntrinsicCollectionView.m; sourceTree = ""; }; 2A2CD72322FA05DD008C7BC9 /* DWPressableButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPressableButton.h; sourceTree = ""; }; 2A2CD72422FA05DD008C7BC9 /* DWPressableButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWPressableButton.m; sourceTree = ""; }; - 2A307CBA22E8925100A18347 /* DWHomeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeView.h; sourceTree = ""; }; - 2A307CBB22E8925100A18347 /* DWHomeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWHomeView.m; sourceTree = ""; }; 2A307CBD22E8A44200A18347 /* DWButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWButton.h; sourceTree = ""; }; 2A307CBE22E8A44200A18347 /* DWButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWButton.m; sourceTree = ""; }; 2A36A887234F3F400014DC60 /* DWSelectorFormItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSelectorFormItem.h; sourceTree = ""; }; @@ -1068,25 +1072,19 @@ 2A46630E2279DF490027533B /* DashWalletScreenshotsUITests-Briding-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DashWalletScreenshotsUITests-Briding-Header.h"; sourceTree = ""; }; 2A4E1D5125056297008AC53F /* DWPaymentsButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPaymentsButton.h; sourceTree = ""; }; 2A4E1D5225056297008AC53F /* DWPaymentsButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWPaymentsButton.m; sourceTree = ""; }; - 2A4E531622EA381F00E5168A /* DWSyncView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSyncView.h; sourceTree = ""; }; - 2A4E531722EA381F00E5168A /* DWSyncView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSyncView.m; sourceTree = ""; }; - 2A4E531922EA382B00E5168A /* DWSyncView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWSyncView.xib; sourceTree = ""; }; + 2A4E531922EA382B00E5168A /* SyncView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SyncView.xib; sourceTree = ""; }; 2A4E531B22EA49FE00E5168A /* DWProgressView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWProgressView.h; sourceTree = ""; }; 2A4E531C22EA49FE00E5168A /* DWProgressView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWProgressView.m; sourceTree = ""; }; - 2A4E531E22EB2DF400E5168A /* DWHomeHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeHeaderView.h; sourceTree = ""; }; - 2A4E531F22EB2DF400E5168A /* DWHomeHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWHomeHeaderView.m; sourceTree = ""; }; 2A4E533622F023AB00E5168A /* DWHomeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeModel.h; sourceTree = ""; }; 2A4E533722F023AB00E5168A /* DWHomeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWHomeModel.m; sourceTree = ""; }; - 2A4E533D22F025FE00E5168A /* DWTxListEmptyTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTxListEmptyTableViewCell.h; sourceTree = ""; }; - 2A4E533E22F025FE00E5168A /* DWTxListEmptyTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWTxListEmptyTableViewCell.m; sourceTree = ""; }; - 2A4E533F22F025FE00E5168A /* DWTxListEmptyTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWTxListEmptyTableViewCell.xib; sourceTree = ""; }; + 2A4E533F22F025FE00E5168A /* TxListEmptyTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TxListEmptyTableViewCell.xib; sourceTree = ""; }; 2A4E534222F02BC300E5168A /* UIView+DWReuseHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+DWReuseHelper.h"; sourceTree = ""; }; 2A4E534322F02BC300E5168A /* UIView+DWReuseHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+DWReuseHelper.m"; sourceTree = ""; }; 2A4E534522F02C6000E5168A /* DWUIKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUIKit.h; sourceTree = ""; }; 2A4E534922F03A9E00E5168A /* DWFilterHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWFilterHeaderView.h; sourceTree = ""; }; 2A4E534A22F03A9E00E5168A /* DWFilterHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWFilterHeaderView.m; sourceTree = ""; }; 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWFilterHeaderView.xib; sourceTree = ""; }; - 2A4E535322F1D0D900E5168A /* DWTxListTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWTxListTableViewCell.xib; sourceTree = ""; }; + 2A4E535322F1D0D900E5168A /* TxListTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TxListTableViewCell.xib; sourceTree = ""; }; 2A4E535A22F335C200E5168A /* DWTransactionListDataProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTransactionListDataProvider.h; sourceTree = ""; }; 2A4E535B22F335C200E5168A /* DWTransactionListDataProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWTransactionListDataProvider.m; sourceTree = ""; }; 2A4E535D22F33BD200E5168A /* DWTransactionListDataProviderProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTransactionListDataProviderProtocol.h; sourceTree = ""; }; @@ -1114,8 +1112,6 @@ 2A5E4546243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPRegistrationStatusTableViewCell.m; sourceTree = ""; }; 2A5E4547243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWDPRegistrationStatusTableViewCell.xib; sourceTree = ""; }; 2A60C9442444BF3900AF72CF /* DWConfirmUsernameContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWConfirmUsernameContentView.xib; sourceTree = ""; }; - 2A612AB524409D6E0060CC77 /* DWDashPayProfileView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPayProfileView.h; sourceTree = ""; }; - 2A612AB624409D6E0060CC77 /* DWDashPayProfileView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDashPayProfileView.m; sourceTree = ""; }; 2A63003D2327B4BB00827825 /* DWPaymentOutput+DWView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWPaymentOutput+DWView.h"; sourceTree = ""; }; 2A63003E2327B4BB00827825 /* DWPaymentOutput+DWView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DWPaymentOutput+DWView.m"; sourceTree = ""; }; 2A6300412328CCE900827825 /* LockScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LockScreen.storyboard; sourceTree = ""; }; @@ -1336,7 +1332,6 @@ 2A913E7323A2EB0E006A2A59 /* DWBalanceProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBalanceProtocol.h; sourceTree = ""; }; 2A913E7623A2EF06006A2A59 /* DWSyncProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSyncProtocol.h; sourceTree = ""; }; 2A913E7723A2F2B6006A2A59 /* DWTxDisplayModeProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTxDisplayModeProtocol.h; sourceTree = ""; }; - 2A913E7823A2F575006A2A59 /* DWShortcutsProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWShortcutsProtocol.h; sourceTree = ""; }; 2A913E7923A2F637006A2A59 /* DWSyncContainerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSyncContainerProtocol.h; sourceTree = ""; }; 2A913E7A23A2F7ED006A2A59 /* DWHomeProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeProtocol.h; sourceTree = ""; }; 2A913E7B23A2FF4A006A2A59 /* DWRootProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRootProtocol.h; sourceTree = ""; }; @@ -1388,8 +1383,6 @@ 2A9CEBB422E1EAC900A50237 /* DWTabBarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWTabBarView.m; sourceTree = ""; }; 2A9CEBB722E1FA1000A50237 /* DWHomeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeViewController.h; sourceTree = ""; }; 2A9CEBB822E1FA1000A50237 /* DWHomeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWHomeViewController.m; sourceTree = ""; }; - 2A9D72A62497C52E00F79CD8 /* DWBadgeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBadgeView.h; sourceTree = ""; }; - 2A9D72A72497C52E00F79CD8 /* DWBadgeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBadgeView.m; sourceTree = ""; }; 2A9D72A9249A0E7800F79CD8 /* DWDPNewIncomingRequestItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPNewIncomingRequestItem.h; sourceTree = ""; }; 2A9D72AA249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPNewIncomingRequestObject.h; sourceTree = ""; }; 2A9D72AB249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPNewIncomingRequestObject.m; sourceTree = ""; }; @@ -1595,8 +1588,6 @@ 2AD6E5622487E16400B52F14 /* DWRequestsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWRequestsViewController.m; sourceTree = ""; }; 2AD6E5682487E1DB00B52F14 /* DWRequestsModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRequestsModel.h; sourceTree = ""; }; 2AD6E5692487E1DB00B52F14 /* DWRequestsModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWRequestsModel.m; sourceTree = ""; }; - 2AD7BCBC2673BDE9008FF133 /* DWSyncingHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSyncingHeaderView.h; sourceTree = ""; }; - 2AD7BCBD2673BDE9008FF133 /* DWSyncingHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSyncingHeaderView.m; sourceTree = ""; }; 2AD85A9C245874B30045B480 /* DWCaptureSessionFrameDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWCaptureSessionFrameDelegate.h; sourceTree = ""; }; 2AD85A9D245881740045B480 /* DWQRScanStatusView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWQRScanStatusView.h; sourceTree = ""; }; 2AD85A9E245881740045B480 /* DWQRScanStatusView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWQRScanStatusView.m; sourceTree = ""; }; @@ -1989,9 +1980,22 @@ C9F42FB329DD86FB001BC549 /* BackupInfoItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupInfoItemView.swift; sourceTree = ""; }; C9F42FB529DD8702001BC549 /* BackupInfoItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BackupInfoItemView.xib; sourceTree = ""; }; C9F42FB729DFC506001BC549 /* SpendableTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpendableTransaction.swift; sourceTree = ""; }; + C9F451E42A0B986E00825057 /* MainTabbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabbarController.swift; sourceTree = ""; }; + C9F451E62A0BA16400825057 /* PaymentButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentButton.swift; sourceTree = ""; }; C9F451E82A0BDAE700825057 /* UIApplication+DashWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+DashWallet.swift"; sourceTree = ""; }; C9F451EA2A0BF10B00825057 /* SyncingAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncingAlertViewController.swift; sourceTree = ""; }; C9F451ED2A0BF1F500825057 /* SyncingAlertContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncingAlertContentView.swift; sourceTree = ""; }; + C9F451F22A0C933700825057 /* SyncingHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncingHeaderView.swift; sourceTree = ""; }; + C9F451F42A0CAC9400825057 /* HomeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeHeaderView.swift; sourceTree = ""; }; + C9F451F62A0CAE1300825057 /* SyncView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncView.swift; sourceTree = ""; }; + C9F451F82A0CB08900825057 /* ProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressView.swift; sourceTree = ""; }; + C9F451FA2A0CC2A800825057 /* DashPayProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashPayProfileView.swift; sourceTree = ""; }; + C9F451FC2A0CC4A300825057 /* BadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeView.swift; sourceTree = ""; }; + C9F452002A0CE6C900825057 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; + C9F452022A0CEB5800825057 /* TxListEmptyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxListEmptyTableViewCell.swift; sourceTree = ""; }; + C9F452042A0CF4E500825057 /* HomeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeModel.swift; sourceTree = ""; }; + C9F452072A11F28600825057 /* SyncModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncModel.swift; sourceTree = ""; }; + C9F4520A2A1209D100825057 /* HomeHeaderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeHeaderModel.swift; sourceTree = ""; }; CE02413EF0C60B1D1EDE6457 /* Pods-WatchApp Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchApp Extension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WatchApp Extension/Pods-WatchApp Extension.release.xcconfig"; sourceTree = ""; }; D58B25CB4DC36975E05D3C0A /* Pods-dashwallet no watch.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashwallet no watch.debug.xcconfig"; path = "Pods/Target Support Files/Pods-dashwallet no watch/Pods-dashwallet no watch.debug.xcconfig"; sourceTree = ""; }; EA95ACF6CA2A73810B9BB451 /* Pods-DashWalletScreenshotsUITests.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashWalletScreenshotsUITests.testflight.xcconfig"; path = "Pods/Target Support Files/Pods-DashWalletScreenshotsUITests/Pods-DashWalletScreenshotsUITests.testflight.xcconfig"; sourceTree = ""; }; @@ -2651,6 +2655,7 @@ 472D13E7299E4EE7006903F1 /* BalanceModel.swift */, 2A4E533622F023AB00E5168A /* DWHomeModel.h */, 2A4E533722F023AB00E5168A /* DWHomeModel.m */, + C9F452042A0CF4E500825057 /* HomeModel.swift */, 47081198298CF57D003FCA3D /* TransactionListDataSource.swift */, 2A6B8E522387056200A2E5FA /* DWBalanceDisplayOptions.h */, 2A6B8E532387056200A2E5FA /* DWBalanceDisplayOptions.m */, @@ -2687,21 +2692,12 @@ 2A307CB322E6FA7F00A18347 /* Views */ = { isa = PBXGroup; children = ( + C9F452062A11F27400825057 /* Sync View */, + C9F451FF2A0CE63C00825057 /* Home Balance View */, + C9F451FE2A0CE60400825057 /* Home Header View */, 2A2CD70B22F97A9D008C7BC9 /* Shortcuts */, 2A4E533C22F025ED00E5168A /* Cells */, - C9F067F129E4576D0022D958 /* HomeBalanceView.swift */, - C9F067F329E457790022D958 /* HomeBalanceView.xib */, - 2A307CBA22E8925100A18347 /* DWHomeView.h */, - 2A307CBB22E8925100A18347 /* DWHomeView.m */, - 2A4E531622EA381F00E5168A /* DWSyncView.h */, - 2A4E531722EA381F00E5168A /* DWSyncView.m */, - 2A4E531922EA382B00E5168A /* DWSyncView.xib */, - 2A4E531E22EB2DF400E5168A /* DWHomeHeaderView.h */, - 2A4E531F22EB2DF400E5168A /* DWHomeHeaderView.m */, - 2A612AB524409D6E0060CC77 /* DWDashPayProfileView.h */, - 2A612AB624409D6E0060CC77 /* DWDashPayProfileView.m */, - 2AD7BCBC2673BDE9008FF133 /* DWSyncingHeaderView.h */, - 2AD7BCBD2673BDE9008FF133 /* DWSyncingHeaderView.m */, + C9F452002A0CE6C900825057 /* HomeView.swift */, ); path = Views; sourceTree = ""; @@ -2764,6 +2760,8 @@ 2ADC9D1B24603C4F001D7C0D /* UISearchBar+DWAdditions.m */, 47AE8C1428C6378E00490F5E /* HairlineView.swift */, C9F42FAF29DC27F4001BC549 /* EmptyView.swift */, + C9F451F82A0CB08900825057 /* ProgressView.swift */, + C9F451FC2A0CC4A300825057 /* BadgeView.swift */, ); path = Views; sourceTree = ""; @@ -2829,8 +2827,6 @@ 2A74EFF723053ECE00C475EB /* DWIntrinsicTextView.m */, 2A0C69B12312E8A0001B8C90 /* DWWindow.h */, 2A0C69B22312E8A0001B8C90 /* DWWindow.m */, - 2A9D72A62497C52E00F79CD8 /* DWBadgeView.h */, - 2A9D72A72497C52E00F79CD8 /* DWBadgeView.m */, ); path = SharedViews; sourceTree = ""; @@ -2866,10 +2862,10 @@ 2A44313C22CF631E009BAF7F /* UI */ = { isa = PBXGroup; children = ( + 4751137228DAF27300223B77 /* Assembly */, C9F42FA729DC09C6001BC549 /* Style */, 11C5F51128E5D0C500F6F135 /* CrowdNode */, 47A50F362912D9A800C70123 /* Payment Controller */, - 4751137228DAF27300223B77 /* Assembly */, 4751136A28D9A3BE00223B77 /* Portal */, 0F6EDFE028C8AE32000427E7 /* Coinbase */, 47AE8BB328C1305E00490F5E /* Explore Dash */, @@ -2995,15 +2991,15 @@ 2A4E533C22F025ED00E5168A /* Cells */ = { isa = PBXGroup; children = ( + C9F451F22A0C933700825057 /* SyncingHeaderView.swift */, 2A5E4544243E0595006BA067 /* RegistrationStatus */, - 2A4E533D22F025FE00E5168A /* DWTxListEmptyTableViewCell.h */, - 2A4E533E22F025FE00E5168A /* DWTxListEmptyTableViewCell.m */, - 2A4E533F22F025FE00E5168A /* DWTxListEmptyTableViewCell.xib */, + C9F452022A0CEB5800825057 /* TxListEmptyTableViewCell.swift */, + 2A4E533F22F025FE00E5168A /* TxListEmptyTableViewCell.xib */, 2A4E534922F03A9E00E5168A /* DWFilterHeaderView.h */, 2A4E534A22F03A9E00E5168A /* DWFilterHeaderView.m */, 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */, 474C7219298A803200475CA6 /* TxListTableViewCell.swift */, - 2A4E535322F1D0D900E5168A /* DWTxListTableViewCell.xib */, + 2A4E535322F1D0D900E5168A /* TxListTableViewCell.xib */, ); path = Cells; sourceTree = ""; @@ -3606,7 +3602,6 @@ 2A913E8623A30C2D006A2A59 /* DWBalanceDisplayOptionsProtocol.h */, 2A913E7323A2EB0E006A2A59 /* DWBalanceProtocol.h */, 2A913E7A23A2F7ED006A2A59 /* DWHomeProtocol.h */, - 2A913E7823A2F575006A2A59 /* DWShortcutsProtocol.h */, 2A913E7923A2F637006A2A59 /* DWSyncContainerProtocol.h */, 2A913E7623A2EF06006A2A59 /* DWSyncProtocol.h */, 2A913E7723A2F2B6006A2A59 /* DWTxDisplayModeProtocol.h */, @@ -3721,6 +3716,7 @@ 2A9CEBB222E1EAAA00A50237 /* Views */, 2A9CEBA622E1D5A200A50237 /* DWMainTabbarViewController.h */, 2A9CEBA722E1D5A200A50237 /* DWMainTabbarViewController.m */, + C9F451E42A0B986E00825057 /* MainTabbarController.swift */, ); path = Main; sourceTree = ""; @@ -3744,6 +3740,7 @@ 2A392564234CD21300316EA6 /* DWTabBarButton.m */, 2A4E1D5125056297008AC53F /* DWPaymentsButton.h */, 2A4E1D5225056297008AC53F /* DWPaymentsButton.m */, + C9F451E62A0BA16400825057 /* PaymentButton.swift */, ); path = Views; sourceTree = ""; @@ -5721,6 +5718,35 @@ path = View; sourceTree = ""; }; + C9F451FE2A0CE60400825057 /* Home Header View */ = { + isa = PBXGroup; + children = ( + C9F451FA2A0CC2A800825057 /* DashPayProfileView.swift */, + C9F451F42A0CAC9400825057 /* HomeHeaderView.swift */, + C9F4520A2A1209D100825057 /* HomeHeaderModel.swift */, + ); + path = "Home Header View"; + sourceTree = ""; + }; + C9F451FF2A0CE63C00825057 /* Home Balance View */ = { + isa = PBXGroup; + children = ( + C9F067F129E4576D0022D958 /* HomeBalanceView.swift */, + C9F067F329E457790022D958 /* HomeBalanceView.xib */, + ); + path = "Home Balance View"; + sourceTree = ""; + }; + C9F452062A11F27400825057 /* Sync View */ = { + isa = PBXGroup; + children = ( + C9F452072A11F28600825057 /* SyncModel.swift */, + C9F451F62A0CAE1300825057 /* SyncView.swift */, + 2A4E531922EA382B00E5168A /* SyncView.xib */, + ); + path = "Sync View"; + sourceTree = ""; + }; EBFC2EA47915CD4F5BA81564 /* Pods */ = { isa = PBXGroup; children = ( @@ -6016,7 +6042,7 @@ 2A1AF6DF23C7681B00442AF5 /* DWShortcutCollectionViewCell~iphone.xib in Resources */, 2A4431D622D52F67009BAF7F /* DWInfoTextCell.xib in Resources */, 2A44314922CF82EF009BAF7F /* BiometricAuth.storyboard in Resources */, - 2A4E534122F025FE00E5168A /* DWTxListEmptyTableViewCell.xib in Resources */, + 2A4E534122F025FE00E5168A /* TxListEmptyTableViewCell.xib in Resources */, 2A8B9E3D22FD71E100FF8653 /* Payments.storyboard in Resources */, 2A4E534D22F03AAC00E5168A /* DWFilterHeaderView.xib in Resources */, 757E09991ADB8EEB006FD352 /* Localizable.strings in Resources */, @@ -6035,8 +6061,8 @@ 75E83CF61B5F997A0038FB70 /* coinflip.aiff in Resources */, 2ADF83FF23633116008459A7 /* SharedAssets.xcassets in Resources */, 2A4431C522D4CAFA009BAF7F /* BackupInfo.storyboard in Resources */, - 2A4E531A22EA382B00E5168A /* DWSyncView.xib in Resources */, - 2A4E535522F1D0D900E5168A /* DWTxListTableViewCell.xib in Resources */, + 2A4E531A22EA382B00E5168A /* SyncView.xib in Resources */, + 2A4E535522F1D0D900E5168A /* TxListTableViewCell.xib in Resources */, C9F42FB629DD8702001BC549 /* BackupInfoItemView.xib in Resources */, 47AE8BB028BFF28700490F5E /* explore.db in Resources */, 2A0C69F423179C0F001B8C90 /* DWConfirmPaymentContentView.xib in Resources */, @@ -6392,7 +6418,6 @@ 4709C32128818D5400B4BD48 /* Foundation+Bitcoin.swift in Sources */, 47AE8BE928C1305F00490F5E /* AtmDetailsView.swift in Sources */, 2A9FFE872230FF4700956D5F /* DWPlaceholderFormTableViewCell.m in Sources */, - 2A4E532022EB2DF400E5168A /* DWHomeHeaderView.m in Sources */, 2A0C69F22316B93F001B8C90 /* DWTitleDetailCellView.m in Sources */, 47C661B828FE907300028A8D /* AmountInputControl.swift in Sources */, 2A1F640C238D5BBB00A9B505 /* DWSegmentSliderFormTableViewCell.m in Sources */, @@ -6488,6 +6513,9 @@ C3DAD2CF247585C10001624F /* NSPredicate+DWFullTextSearch.m in Sources */, 47F2C6842860513900C2B774 /* TxReclassifyTransactionsWhereToChangeViewController.swift in Sources */, 2A7A7C16234B763600451078 /* DWLocalCurrencyViewController.m in Sources */, + C9F451FD2A0CC4A300825057 /* BadgeView.swift in Sources */, + C9F451F52A0CAC9400825057 /* HomeHeaderView.swift in Sources */, + C9F451F92A0CB08900825057 /* ProgressView.swift in Sources */, 2A2120EA2214566A009906DC /* DWAmountInputValidator.m in Sources */, 47AE8BF628C1306000490F5E /* ExplorePointOfUseListViewController.swift in Sources */, 117ECC7E29742A42003D798E /* CrowdNodeWebViewController.swift in Sources */, @@ -6500,13 +6528,13 @@ 2A8B9E4F22FED47E00FF8653 /* DWOverlapControl.m in Sources */, 119E8D062905200300D406C1 /* CoinsToAddressTxFilter.swift in Sources */, 2AB3417323A926C9004E37A7 /* DWDemoAppRootViewController.m in Sources */, - 2AD7BCBE2673BDE9008FF133 /* DWSyncingHeaderView.m in Sources */, 2A9FFE9B2230FF4700956D5F /* DWUpholdLogoutTutorialViewController.m in Sources */, 47CF46A529654E190067B6EE /* CBAccount.swift in Sources */, 47AF180529070B720025803E /* Types.swift in Sources */, 2A0C69EB2316B344001B8C90 /* DWConfirmPaymentViewController.m in Sources */, 2A7AF3402481A1B2001D74F9 /* DWDPGenericItemView.m in Sources */, 4774DCDB28F3FA9C008CF87D /* Coinbase.swift in Sources */, + C9F452082A11F28600825057 /* SyncModel.swift in Sources */, C9F42FAB29DC1098001BC549 /* ReceiveContentView.swift in Sources */, 47838B7528FFD1D10003E8AB /* AmountView.swift in Sources */, 4759D512292FD6F3002F20DC /* DWBasePayViewController.m in Sources */, @@ -6522,13 +6550,16 @@ 47A2A2E2293DCCEA00938DB7 /* Coinbase+Constants.swift in Sources */, 47AE8C0728C2274200490F5E /* PointOfUseListSearchCell.swift in Sources */, 4774DCE128F44BA4008CF87D /* ServiceDataProvider.swift in Sources */, + C9F451E72A0BA16400825057 /* PaymentButton.swift in Sources */, 2A4431DB22D675CD009BAF7F /* DWPreviewSeedPhraseViewController.m in Sources */, + C9F451FB2A0CC2A800825057 /* DashPayProfileView.swift in Sources */, 110D1781298BA9AF005BEB30 /* WKWebView+CrowdNode.swift in Sources */, 47FA3AFF29350929008D58DC /* SyncingActivityMonitor.swift in Sources */, C9F42FB029DC27F4001BC549 /* EmptyView.swift in Sources */, 2AFCB9BE23BE3C0800FF59A6 /* DWConfirmSendPaymentViewController.m in Sources */, 2AC92C8A1FEB0B8B008CAEE0 /* DWQRScanModel.m in Sources */, 2A3DC87123972331004B3DBA /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.m in Sources */, + C9F451F72A0CAE1300825057 /* SyncView.swift in Sources */, 47522F502927CB9000EE143E /* SuccessfulOperationStatusViewController.swift in Sources */, 47E94B9A296D7F99000FE68E /* CustodialSwapsModel.swift in Sources */, 2A7A7C20234B79B700451078 /* DWLocalCurrencyTableViewCell.m in Sources */, @@ -6678,6 +6709,7 @@ 2A8B9E2922FB1C5D00FF8653 /* DWSharedUIConstants.m in Sources */, 2A858A12237EE89C0097A7B5 /* BRAppleWatchTransactionData.m in Sources */, 47A50F3E29192F8D00C70123 /* TerritoriesListViewController.swift in Sources */, + C9F4520B2A1209D100825057 /* HomeHeaderModel.swift in Sources */, 47C6E6E02919578C003FEDF2 /* TerritoryListModel.swift in Sources */, 2A9FFE952230FF4700956D5F /* DWUpholdConfirmTransferModel.m in Sources */, 2AFF01DB243F4559003718DC /* DWDPRegistrationStatus.m in Sources */, @@ -6722,6 +6754,7 @@ 475AE2B92974348F009A1055 /* App.swift in Sources */, 2A0C69A3231048DA001B8C90 /* DWPaymentProcessor.m in Sources */, 2A7AF32824814A17001D74F9 /* DWNotificationsData.m in Sources */, + C9F451F32A0C933700825057 /* SyncingHeaderView.swift in Sources */, 114CFED2296489CD005F421B /* MinimumDepositBanner.swift in Sources */, 47838B8F2906D7340003E8AB /* BalanceView.swift in Sources */, 2AD85A9F245881740045B480 /* DWQRScanStatusView.m in Sources */, @@ -6731,6 +6764,7 @@ 2AC92C841FEB0A6D008CAEE0 /* DWQRScanViewController.m in Sources */, 2A9FFE802230FF4600956D5F /* DWSelectorFormTableViewCell.m in Sources */, 2A3CCEF9242BB1B900300AF8 /* DWRegistrationCompletedViewController.m in Sources */, + C9F452052A0CF4E500825057 /* HomeModel.swift in Sources */, 2ADC9D712462D4AD001D7C0D /* DWUserProfileHeaderView.m in Sources */, 47305872295C971F004641DA /* UIViewController+AlertPresenting.swift in Sources */, 2A9CEBB522E1EAC900A50237 /* DWTabBarView.m in Sources */, @@ -6740,7 +6774,6 @@ 477F501529531C07003C7508 /* ViewModel+Coinbase.swift in Sources */, 478A9297299242EC0008C43E /* CNCreateAccountTxDetailsModel.swift in Sources */, 47A2A2EC293E618600938DB7 /* CBUser.swift in Sources */, - 2A307CBC22E8925100A18347 /* DWHomeView.m in Sources */, 47A2A2EE293E622700938DB7 /* CBSecureTokenService.swift in Sources */, C9F42FA129DA95F5001BC549 /* PayTableViewCell.swift in Sources */, C3DAD2C8247538AA0001624F /* DWTitleActionHeaderView.m in Sources */, @@ -6755,6 +6788,7 @@ 2A7A7BB22347927700451078 /* DWMainMenuViewController.m in Sources */, 47838B7D290133610003E8AB /* PointOfUseListFiltersViewController.swift in Sources */, 47AE8C1A28C6A21A00490F5E /* AllMerchantLocationsDataProvider.swift in Sources */, + C9F451E52A0B986E00825057 /* MainTabbarController.swift in Sources */, 47B30D78290BFCA60080C326 /* NumberFormatter+DashWallet.swift in Sources */, 2AEC5CBB2494045C00F4A689 /* DWNotificationsFetchedDataSource.m in Sources */, 47AF18082907B7880025803E /* BaseAmountModel.swift in Sources */, @@ -6834,7 +6868,6 @@ 47F4B6CA29484C9800AED4C9 /* ConfirmOrderModel.swift in Sources */, 472CEE012924AA6D00656B48 /* PointOfUseListEmptyResultsView.swift in Sources */, C94F5E8C29D3FEC10034FD57 /* ShortcutsModel.swift in Sources */, - 2A4E531822EA381F00E5168A /* DWSyncView.m in Sources */, 2A6300452328D07500827825 /* DWLockPinInputView.m in Sources */, 2A392565234CD21300316EA6 /* DWTabBarButton.m in Sources */, 2A0C69AC23125074001B8C90 /* UIView+DWHUD.m in Sources */, @@ -6899,6 +6932,7 @@ 2A307CBF22E8A44200A18347 /* DWButton.m in Sources */, 0F6EDFD128C896BD000427E7 /* CoinbaseCreateAddressesRequest.swift in Sources */, 2A10EB352358996700C38B61 /* DWImportWalletInfoViewController.m in Sources */, + C9F452032A0CEB5800825057 /* TxListEmptyTableViewCell.swift in Sources */, C9F451EE2A0BF1F500825057 /* SyncingAlertContentView.swift in Sources */, 471A2605289ACD5C0056B7B2 /* AddressUserInfo.swift in Sources */, 47AE8C0C28C53E4A00490F5E /* MerchantInfoViewController.swift in Sources */, @@ -6907,8 +6941,8 @@ 2A0C69E0231556D6001B8C90 /* DWModalChevronView.m in Sources */, 2A58815921A5906C00FD4D2C /* DWBaseLegacyViewController.m in Sources */, C3CA2030247E54C400158074 /* DWNoNotificationsCell.m in Sources */, + C9F452012A0CE6C900825057 /* HomeView.swift in Sources */, 47A2E3AC2972B1A60032A63B /* CoinbaseRatesProvider.swift in Sources */, - 2A4E534022F025FE00E5168A /* DWTxListEmptyTableViewCell.m in Sources */, 472D13DF299DF5C6006903F1 /* TaxReportGenerator.swift in Sources */, 119E8D122909513F00D406C1 /* CrowdNodeError.swift in Sources */, 111C3C4E296C52F800788E18 /* WithdrawalLimit.swift in Sources */, @@ -6930,7 +6964,6 @@ 2A9FFE8E2230FF4700956D5F /* DWUpholdAuthViewController.m in Sources */, 2A1F640F238D5C0900A9B505 /* DWSegmentSliderFormCellModel.m in Sources */, 2AD1CE6022D8E9D900C99324 /* DWSeedPhraseView.m in Sources */, - 2A612AB724409D6E0060CC77 /* DWDashPayProfileView.m in Sources */, 2A3CCEFC242BB1DD00300AF8 /* DWDPAvatarView.m in Sources */, 47B30D7C29100ABA0080C326 /* UIStackView+DashWallet.swift in Sources */, 2ADC9D7C24644E46001D7C0D /* DWUserProfileContactActionsCell.m in Sources */, @@ -6986,7 +7019,6 @@ 1141E4C5291FDC7A00ACDA9E /* WelcomeToCrowdNodeViewController.swift in Sources */, 47C6E6E329196D48003FEDF2 /* TerritoriesListCell.swift in Sources */, 2A4E531D22EA49FE00E5168A /* DWProgressView.m in Sources */, - 2A9D72A82497C52E00F79CD8 /* DWBadgeView.m in Sources */, 47A5146428491C60005A8E3E /* TxDetailCells.swift in Sources */, 477F501729543834003C7508 /* BaseViewController+NetworkReachability.swift in Sources */, 75D5F3CE191EC270004AB296 /* main.m in Sources */, diff --git a/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift b/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift index c1b57cf72..f3561b004 100644 --- a/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift +++ b/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift @@ -54,13 +54,15 @@ class SyncingActivityMonitor: NSObject, NetworkReachabilityHandling { case unknown } + @objc public var progress: Double = 0 { didSet { observers.forEach { $0.syncingActivityMonitorProgressDidChange(progress) } } } - @objc public var state: State = .unknown { + @objc + public var state: State = .unknown { didSet { guard state != oldValue else { return } @@ -151,6 +153,16 @@ class SyncingActivityMonitor: NSObject, NetworkReachabilityHandling { startSyncingActivity() } + @objc + func peerManagerConnectedPeersDidChangeNotification(notification: Notification) { + let isConnected = DWEnvironment.sharedInstance().currentChainManager.peerManager.connected + + if isConnected { + NotificationCenter.default.removeObserver(self, name: .peerManagerConnectedPeersDidChange, object: nil) + startSyncingIfNeeded() + } + } + deinit { NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(reachabilityObserver!) @@ -164,6 +176,9 @@ class SyncingActivityMonitor: NSObject, NetworkReachabilityHandling { extension SyncingActivityMonitor { private func startSyncingIfNeeded() { guard DWEnvironment.sharedInstance().currentChainManager.peerManager.connected else { + NotificationCenter.default.addObserver(self, selector: #selector(peerManagerConnectedPeersDidChangeNotification(notification:)), + name: .peerManagerConnectedPeersDidChange, object: nil) + return } @@ -173,7 +188,7 @@ extension SyncingActivityMonitor { private func startSyncingActivity() { guard !isSyncing else { return } - progress = 0 + progress = chainSyncProgress lastPeakDate = nil NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(syncLoop), object: nil) @@ -224,9 +239,6 @@ extension SyncingActivityMonitor { perform(#selector(syncLoop), with: nil, afterDelay: kSyncLoopInterval) } else { - self.progress = 1.0 - state = .syncDone - stopSyncingActivity(failed: false) } } @@ -300,4 +312,7 @@ extension Notification.Name { static let chainManagerSyncFailed: Notification.Name = .init(rawValue: "DSChainManagerSyncFailedNotification") static let chainManagerChainSyncBlocksDidChange: Notification .Name = .init(rawValue: "DSChainChainSyncBlocksDidChangeNotification") + static let peerManagerConnectedPeersDidChange: Notification + .Name = .init(rawValue: "DSPeerManagerConnectedPeersDidChangeNotification") + } diff --git a/DashWallet/Sources/Categories/UITableView+DashWallet.swift b/DashWallet/Sources/Categories/UITableView+DashWallet.swift index abbd955f8..d560242fb 100644 --- a/DashWallet/Sources/Categories/UITableView+DashWallet.swift +++ b/DashWallet/Sources/Categories/UITableView+DashWallet.swift @@ -29,11 +29,15 @@ extension UITableView { func dequeueReusableCell(type: T.Type, for indexPath: IndexPath) -> T { dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T } - + + func registerNibForHeaderFooterView(for type: T.Type) { + register(UINib(nibName: T.reuseIdentifier, bundle: nil), forHeaderFooterViewReuseIdentifier: T.reuseIdentifier) + } + func registerClassforHeaderFooterView(for type: T.Type) { register(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier) } - + func dequeueReusableHeaderFooterView(type: T.Type) -> T { dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as! T } diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController.m b/DashWallet/Sources/UI/Home/DWHomeViewController.m index 82989c259..460456c0a 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController.m @@ -21,7 +21,6 @@ #import "DWEnvironment.h" #import "DWGlobalOptions.h" #import "DWHomeModel.h" -#import "DWHomeView.h" #import "DWHomeViewController+DWBackupReminder.h" #import "DWHomeViewController+DWJailbreakCheck.h" #import "DWHomeViewController+DWShortcuts.h" diff --git a/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.m b/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.m index f632de859..ecde9e69d 100644 --- a/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.m +++ b/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.m @@ -33,6 +33,10 @@ - (instancetype)init { return self; } +- (BOOL)balanceHidden { + return _balanceHidden; +} + - (void)hideBalanceIfNeeded { if ([DWGlobalOptions sharedInstance].balanceHidden) { self.balanceHidden = YES; diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index a9b87e985..d31781c3a 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWHomeModel () +@interface DWHomeModel () @property (nonatomic, strong) dispatch_queue_t queue; @property (strong, nonatomic) DSReachabilityManager *reachability; @@ -65,7 +65,6 @@ @implementation DWHomeModel @synthesize payModel = _payModel; @synthesize receiveModel = _receiveModel; @synthesize dashPayModel = _dashPayModel; -@synthesize syncModel = _syncModel; @synthesize updatesObserver = _updatesObserver; @synthesize allDataSource = _allDataSource; @synthesize allowedToShowReclassifyYourTransactions = _allowedToShowReclassifyYourTransactions; @@ -224,10 +223,6 @@ - (BOOL)shouldShowWalletBackupReminder { return (secondsSinceBalanceChanged > DAY_TIME_INTERVAL); } -- (void)reloadShortcuts { - [self.shortcutsModel reloadShortcuts]; -} - - (void)registerForPushNotifications { [[AppDelegate appDelegate] registerForPushNotifications]; } @@ -274,7 +269,7 @@ - (BOOL)performOnSetupUpgrades { if (needsCheck) { // Show backup reminder shortcut [DWGlobalOptions sharedInstance].walletNeedsBackup = YES; - [self reloadShortcuts]; + [self.updatesObserver homeModelWantToReloadShortcuts:self]; } }]; }]; @@ -318,14 +313,14 @@ - (BOOL)shouldShowCreateUserNameButton { BOOL canRegisterUsername = YES; const uint64_t balanceValue = wallet.balance; BOOL isEnoughBalance = balanceValue >= DWDP_MIN_BALANCE_TO_CREATE_USERNAME; - BOOL isSynced = self.syncModel.state == DWSyncModelState_SyncDone; + BOOL isSynced = [SyncingActivityMonitor shared].state == SyncingActivityMonitorStateSyncDone; return canRegisterUsername && isSynced && isEnoughBalance; } #pragma mark - Notifications - (void)reachabilityDidChangeNotification { - [self reloadShortcuts]; + [self.updatesObserver homeModelWantToReloadShortcuts:self]; if (self.reachability.networkReachabilityStatus != DSReachabilityStatusNotReachable && [UIApplication sharedApplication].applicationState != UIApplicationStateBackground) { @@ -351,21 +346,7 @@ - (void)applicationWillEnterForegroundNotification { - (void)fiatCurrencyDidChangeNotification { [self updateBalance]; [self reloadTxDataSource]; -} - -- (void)syncStateChangedNotification { - BOOL isSynced = self.syncModel.state == DWSyncModelState_SyncDone; - if (isSynced) { - [self.dashPayModel updateUsernameStatus]; - - if (self.dashPayModel.username != nil) { - [self.receiveModel updateReceivingInfo]; - [[DWDashPayContactsUpdater sharedInstance] beginUpdating]; - } - } - - [self updateBalance]; - [self reloadTxDataSource]; + [self.updatesObserver homeModelDidChangeInnerModels:self]; } - (void)chainWalletsDidChangeNotification:(NSNotification *)notification { @@ -522,7 +503,7 @@ - (void)updateBalance { balanceValue > self.balanceModel.value && self.balanceModel.value > 0 && [UIApplication sharedApplication].applicationState != UIApplicationStateBackground && - self.syncModel.progress > 0.995) { + [SyncingActivityMonitor shared].progress > 0.995) { [[UIDevice currentDevice] dw_playCoinSound]; } @@ -535,7 +516,7 @@ - (void)updateBalance { options.userHasBalance = balanceValue > 0; - [self reloadShortcuts]; + [self.updatesObserver homeModelWantToReloadShortcuts:self]; } - (NSArray *)filterTransactions:(NSArray *)allTransactions diff --git a/DashWallet/Sources/UI/Home/Models/HomeModel.swift b/DashWallet/Sources/UI/Home/Models/HomeModel.swift new file mode 100644 index 000000000..c2851126f --- /dev/null +++ b/DashWallet/Sources/UI/Home/Models/HomeModel.swift @@ -0,0 +1,18 @@ +// +// Created by PT +// Copyright © 2023 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 diff --git a/DashWallet/Sources/UI/Home/Models/TransactionListDataSource.swift b/DashWallet/Sources/UI/Home/Models/TransactionListDataSource.swift index 2db05ebc4..078697a84 100644 --- a/DashWallet/Sources/UI/Home/Models/TransactionListDataSource.swift +++ b/DashWallet/Sources/UI/Home/Models/TransactionListDataSource.swift @@ -147,8 +147,7 @@ final class TransactionListDataSource: NSObject, UITableViewDataSource { cell.update(with: txs) return cell case .tx(let tx): - let cellId = TxListTableViewCell.dw_reuseIdentifier - let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! TxListTableViewCell + let cell = tableView.dequeueReusableCell(type: TxListTableViewCell.self, for: indexPath) cell.update(with: tx) return cell } @@ -166,7 +165,7 @@ enum TransactionListDataItemType: Int { extension TransactionListDataSource { @objc - func itemType(by indexPath: NSIndexPath) -> TransactionListDataItemType { + func itemType(by indexPath: IndexPath) -> TransactionListDataItemType { if case TransactionListDataItem.tx = _items[indexPath.row] { return .tx } diff --git a/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h index ef399e84e..e0ccec9d5 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @class DWBalanceModel; -@protocol DWBalanceProtocol +@protocol DWBalanceProtocol @property (readonly, nullable, nonatomic, strong) DWBalanceModel *balanceModel; @property (readonly, nonatomic, strong) id balanceDisplayOptions; diff --git a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h index 0034b7df8..29223dffb 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h @@ -40,10 +40,11 @@ NS_ASSUME_NONNULL_BEGIN - (void)homeModel:(id)model didReceiveNewIncomingTransaction:(DSTransaction *)transaction; +- (void)homeModelDidChangeInnerModels:(id)model; - (void)homeModelWantToReloadShortcuts:(id)model; @end -@protocol DWHomeProtocol +@protocol DWHomeProtocol @property (nullable, nonatomic, weak) id updatesObserver; @@ -69,6 +70,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)walletDidWipe; +- (void)retrySyncing; @end NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift b/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift index 4ecd3a575..faead6b56 100644 --- a/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift +++ b/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift @@ -32,6 +32,8 @@ final class SyncingAlertViewController: BaseViewController, SyncingAlertContentV return childView }() + internal lazy var model: SyncModel = SyncModelImpl() + init() { super.init(nibName: nil, bundle: nil) transitioningDelegate = modalTransition @@ -45,7 +47,19 @@ final class SyncingAlertViewController: BaseViewController, SyncingAlertContentV override func viewDidLoad() { super.viewDidLoad() - SyncingActivityMonitor.shared.add(observer: self) + model.networkStatusDidChange = { [weak self] _ in + // TODO: update view based on network connection instead of passing sync state + } + + model.progressDidChange = { [weak self] progress in + guard let self else { return } + self.childView.update(with: progress) + self.childView.update(with: self.model.state) + } + + model.stateDidChage = { [weak self] state in + self?.childView.update(with: state) + } view.backgroundColor = .clear @@ -74,20 +88,4 @@ final class SyncingAlertViewController: BaseViewController, SyncingAlertContentV func syncingAlertContentView(_ view: SyncingAlertContentView, okButtonAction sender: UIButton) { dismiss(animated: true, completion: nil) } - - deinit { - SyncingActivityMonitor.shared.remove(observer: self) - } -} - -// MARK: SyncingActivityMonitorObserver - -extension SyncingAlertViewController: SyncingActivityMonitorObserver { - func syncingActivityMonitorProgressDidChange(_ progress: Double) { - childView.update(with: progress) - } - - func syncingActivityMonitorStateDidChange(previousState: SyncingActivityMonitor.State, state: SyncingActivityMonitor.State) { - childView.update(with: state) - } } diff --git a/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift b/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift index aff0ba8a6..499869c6a 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift +++ b/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift @@ -34,7 +34,7 @@ final class SyncingHeaderView: UITableViewHeaderFooterView { weak var delegate: SyncingHeaderViewDelegate? @objc - var progress = 0.0 { + var progress: Float = 0.0 { didSet { refreshView() } @@ -49,6 +49,8 @@ final class SyncingHeaderView: UITableViewHeaderFooterView { private var syncingButton: UIButton! + internal lazy var model: SyncModel = SyncModelImpl() + override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) @@ -97,6 +99,22 @@ final class SyncingHeaderView: UITableViewHeaderFooterView { filterButton.heightAnchor.constraint(equalToConstant: 44.0), filterButton.widthAnchor.constraint(equalToConstant: 44.0), ]) + + model.networkStatusDidChange = { [weak self] _ in + } + + model.progressDidChange = { [weak self] progress in + self?.progress = Float(progress) + } + + model.stateDidChage = { [weak self] state in + self?.isSyncing = state == .syncing + } + + progress = Float(model.progress) + isSyncing = model.state == .syncing + + refreshView() } required init?(coder: NSCoder) { diff --git a/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.swift b/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.swift index 462516831..e9daf1e57 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.swift +++ b/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.swift @@ -24,7 +24,7 @@ class TxListEmptyTableViewCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() - placeholderLabel.font = UIFont.preferredFont(forTextStyle: .footnote) - placeholderLabel.text = NSLocalizedString("There are no transactions to display", comment: "") + placeholderLabel?.font = UIFont.preferredFont(forTextStyle: .footnote) + placeholderLabel?.text = NSLocalizedString("There are no transactions to display", comment: "") } } diff --git a/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.xib b/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.xib index 2414a997e..a0e5b13e3 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.xib +++ b/DashWallet/Sources/UI/Home/Views/Cells/TxListEmptyTableViewCell.xib @@ -11,7 +11,7 @@ - + @@ -51,7 +51,7 @@ - + diff --git a/DashWallet/Sources/UI/Home/Views/Cells/TxListTableViewCell.swift b/DashWallet/Sources/UI/Home/Views/Cells/TxListTableViewCell.swift index d082fe643..f6fe9f3ec 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/TxListTableViewCell.swift +++ b/DashWallet/Sources/UI/Home/Views/Cells/TxListTableViewCell.swift @@ -17,7 +17,6 @@ import UIKit -@objc(DWTxListTableViewCell) final class TxListTableViewCell: UITableViewCell { @IBOutlet var txItemView: TransactionItemView! @@ -30,6 +29,4 @@ final class TxListTableViewCell: UITableViewCell { dw_pressedAnimation(.light, pressed: highlighted) } - - override class var dw_reuseIdentifier: String { "DWTxListTableViewCell" } } diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListTableViewCell.xib b/DashWallet/Sources/UI/Home/Views/Cells/TxListTableViewCell.xib similarity index 95% rename from DashWallet/Sources/UI/Home/Views/Cells/DWTxListTableViewCell.xib rename to DashWallet/Sources/UI/Home/Views/Cells/TxListTableViewCell.xib index cc5258300..c1ade7245 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/DWTxListTableViewCell.xib +++ b/DashWallet/Sources/UI/Home/Views/Cells/TxListTableViewCell.xib @@ -11,7 +11,7 @@ - + @@ -63,6 +63,9 @@ + + + diff --git a/DashWallet/Sources/UI/Home/Views/DWHomeHeaderView.h b/DashWallet/Sources/UI/Home/Views/DWHomeHeaderView.h deleted file mode 100644 index 15fd59409..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWHomeHeaderView.h +++ /dev/null @@ -1,48 +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 - -#import "DWHomeProtocol.h" - - -NS_ASSUME_NONNULL_BEGIN - -@class DWHomeHeaderView; -@protocol DWShortcutsActionDelegate; - -@protocol DWHomeHeaderViewDelegate - -- (void)homeHeaderViewDidUpdateContents:(DWHomeHeaderView *)view; -- (void)homeHeaderView:(DWHomeHeaderView *)view profileButtonAction:(UIControl *)sender; - -@end - -@interface DWHomeHeaderView : KVOUIView - -@property (nullable, nonatomic, strong) id model; -@property (nullable, nonatomic, weak) id delegate; -@property (nullable, nonatomic, weak) id shortcutsDelegate; - -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE; -- (instancetype)init NS_UNAVAILABLE; - -- (void)parentScrollViewDidScroll:(UIScrollView *)scrollView; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/DWHomeHeaderView.m b/DashWallet/Sources/UI/Home/Views/DWHomeHeaderView.m deleted file mode 100644 index 6f860fd30..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWHomeHeaderView.m +++ /dev/null @@ -1,206 +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 "DWHomeHeaderView.h" - -#import "DWDPRegistrationStatus.h" -#import "DWDashPayProfileView.h" -#import "DWSyncView.h" -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -static CGSize const AVATAR_SIZE = {72.0, 72.0}; - -@interface DWHomeHeaderView () - -@property (readonly, nonatomic, strong) DWDashPayProfileView *profileView; -@property (readonly, nonatomic, strong) DWHomeBalanceView *balanceView; -@property (readonly, nonatomic, strong) DWSyncView *syncView; -@property (readonly, nonatomic, strong) ShortcutsView *shortcutsView; -@property (readonly, nonatomic, strong) UIStackView *stackView; - -@end - -@implementation DWHomeHeaderView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - DWDashPayProfileView *profileView = [[DWDashPayProfileView alloc] initWithFrame:CGRectZero]; - profileView.translatesAutoresizingMaskIntoConstraints = NO; - [profileView addTarget:self action:@selector(profileViewAction:) forControlEvents:UIControlEventTouchUpInside]; - _profileView = profileView; - - DWHomeBalanceView *balanceView = [[DWHomeBalanceView alloc] initWithFrame:CGRectZero]; - balanceView.delegate = self; - _balanceView = balanceView; - - DWSyncView *syncView = [[DWSyncView alloc] initWithFrame:CGRectZero]; - syncView.delegate = self; - _syncView = syncView; - - ShortcutsView *shortcutsView = [[ShortcutsView alloc] initWithFrame:CGRectZero]; - shortcutsView.translatesAutoresizingMaskIntoConstraints = NO; - _shortcutsView = shortcutsView; - - NSArray *views = @[ profileView, balanceView, shortcutsView, syncView ]; - UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:views]; - stackView.translatesAutoresizingMaskIntoConstraints = NO; - stackView.axis = UILayoutConstraintAxisVertical; - [self addSubview:stackView]; - _stackView = stackView; - - [NSLayoutConstraint activateConstraints:@[ - [stackView.topAnchor constraintEqualToAnchor:self.topAnchor], - [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [stackView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], - [stackView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], - ]]; - - // KVO - - [self mvvm_observe:DW_KEYPATH(self, model.balanceModel) - with:^(typeof(self) self, id value) { - [self.balanceView reloadData]; - }]; - - [self mvvm_observe:DW_KEYPATH(self, model.balanceDisplayOptions.balanceHidden) - with:^(typeof(self) self, NSNumber *value) { - [self.balanceView hideBalance: self.model.balanceDisplayOptions.balanceHidden]; - }]; - - [self mvvm_observe:DW_KEYPATH(self, model.syncModel.state) - with:^(typeof(self) self, NSNumber *value) { - if (!value) { - return; - } - - const DWSyncModelState state = self.model.syncModel.state; - - self.balanceView.state = state == DWSyncModelState_Syncing ? DWHomeBalanceViewState_Syncing : DWHomeBalanceViewState_Default; - - [self.syncView setSyncState:state]; - - if (state == DWSyncModelState_SyncFailed || state == DWSyncModelState_NoConnection) { - [self showSyncView]; - } - else { - [self hideSyncView]; - } - }]; - - [self mvvm_observe:DW_KEYPATH(self, model.dashPayModel.registrationStatus) - with:^(typeof(self) self, id value) { - [self updateProfileView]; - }]; - - [self mvvm_observe:DW_KEYPATH(self, model.dashPayModel.username) - with:^(typeof(self) self, id value) { - [self updateProfileView]; - }]; - - [self mvvm_observe:DW_KEYPATH(self, model.dashPayModel.unreadNotificationsCount) - with:^(typeof(self) self, id value) { - self.profileView.unreadCount = self.model.dashPayModel.unreadNotificationsCount; - }]; - } - return self; -} - -- (void)setModel:(nullable id)model { - _model = model; - - self.shortcutsView.model = model.shortcutsModel; - [self updateProfileView]; - - self.balanceView.dataSource = model; -} - -- (nullable id)shortcutsDelegate { - return self.shortcutsView.actionDelegate; -} - -- (void)setShortcutsDelegate:(nullable id)shortcutsDelegate { - self.shortcutsView.actionDelegate = shortcutsDelegate; -} - -- (void)parentScrollViewDidScroll:(UIScrollView *)scrollView { -} - -#pragma mark - DWBalanceViewDelegate - -- (void)balanceView:(DWHomeBalanceView *)view balanceLongPressAction:(UIControl *)sender { - DWShortcutAction *action = [DWShortcutAction actionWithType:DWShortcutActionTypeLocalCurrency]; - [self.shortcutsDelegate shortcutsView:self.balanceView didSelectAction:action sender:sender]; -} - -- (void)balanceViewDidToggleBalanceVisibility:(DWHomeBalanceView *)view { - id balanceDisplayOptions = self.model.balanceDisplayOptions; - balanceDisplayOptions.balanceHidden = !balanceDisplayOptions.balanceHidden; -} - - -#pragma mark - DWShortcutsViewDelegate - -- (void)shortcutsViewDidUpdateContentSize:(ShortcutsView *)view { - [self.delegate homeHeaderViewDidUpdateContents:self]; -} - -#pragma mark - DWSyncViewDelegate - -- (void)syncViewRetryButtonAction:(DWSyncView *)view { - [self.model retrySyncing]; -} - -#pragma mark - Private - -- (void)profileViewAction:(UIControl *)sender { - [self.delegate homeHeaderView:self profileButtonAction:sender]; -} - -- (void)updateProfileView { - DWDPRegistrationStatus *status = self.model.dashPayModel.registrationStatus; - const BOOL completed = self.model.dashPayModel.registrationCompleted; - if (status.state == DWDPRegistrationState_Done || completed) { - self.profileView.username = self.model.dashPayModel.username; - self.profileView.hidden = NO; - } - else { - self.profileView.hidden = YES; - } - [self.delegate homeHeaderViewDidUpdateContents:self]; -} - -- (void)hideSyncView { - self.syncView.hidden = YES; - - [self.delegate homeHeaderViewDidUpdateContents:self]; -} - -- (void)showSyncView { - self.syncView.hidden = NO; - - [self.delegate homeHeaderViewDidUpdateContents:self]; -} - - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/DWHomeView.h b/DashWallet/Sources/UI/Home/Views/DWHomeView.h deleted file mode 100644 index 5a2cbfefd..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWHomeView.h +++ /dev/null @@ -1,51 +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 - -#import "DWHomeProtocol.h" -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWHomeView; -@class DSTransaction; - -@protocol DWHomeViewDelegate - -- (void)homeView:(DWHomeView *)homeView showTxFilter:(UIView *)sender; -- (void)homeView:(DWHomeView *)homeView showSyncingStatus:(UIView *)sender; -- (void)homeView:(DWHomeView *)homeView profileButtonAction:(UIControl *)sender; -- (void)homeView:(DWHomeView *)homeView didSelectTransaction:(DSTransaction *)transaction; -- (void)homeViewShowDashPayRegistrationFlow:(DWHomeView *)homeView; -- (void)homeView:(DWHomeView *)homeView showReclassifyYourTransactionsFlowWithTransaction:(DSTransaction *)transaction; -- (void)homeView:(DWHomeView *)homeView showCrowdNodeTxs:(NSArray *)transactions; - -@end - -@interface DWHomeView : KVOUIView - -@property (nonatomic, strong) id model; -@property (nullable, nonatomic, weak) id delegate; -@property (nullable, nonatomic, weak) id shortcutsDelegate; - -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE; -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/DWHomeView.m b/DashWallet/Sources/UI/Home/Views/DWHomeView.m deleted file mode 100644 index c48ceaedd..000000000 --- a/DashWallet/Sources/UI/Home/Views/DWHomeView.m +++ /dev/null @@ -1,266 +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 "DWHomeView.h" - -#import "DWDPRegistrationDoneTableViewCell.h" -#import "DWDPRegistrationErrorTableViewCell.h" -#import "DWDPRegistrationStatus.h" -#import "DWDPRegistrationStatusTableViewCell.h" -#import "DWDashPayProtocol.h" -#import "DWHomeHeaderView.h" -#import "DWSharedUIConstants.h" -#import "DWSyncingHeaderView.h" -#import "DWTxListEmptyTableViewCell.h" -#import "DWUIKit.h" -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWHomeView () - -@property (readonly, nonatomic, strong) DWHomeHeaderView *headerView; -@property (readonly, nonatomic, strong) UIView *topOverscrollView; -@property (readonly, nonatomic, strong) UITableView *tableView; - -@property (nullable, nonatomic, weak) DWSyncingHeaderView *syncingHeaderView; - -// strong ref to current datasource to make sure it always exists while tableView uses it -@property (nonatomic, strong) DWTransactionListDataSource *currentDataSource; - -@end - -@implementation DWHomeView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - DWHomeHeaderView *headerView = [[DWHomeHeaderView alloc] initWithFrame:CGRectZero]; - headerView.delegate = self; - _headerView = headerView; - - UIView *topOverscrollView = [[UIView alloc] initWithFrame:CGRectZero]; - topOverscrollView.backgroundColor = [UIColor dw_dashNavigationBlueColor]; - _topOverscrollView = topOverscrollView; - - UITableView *tableView = [[UITableView alloc] initWithFrame:self.bounds style:UITableViewStylePlain]; - tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - tableView.tableHeaderView = headerView; - tableView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - tableView.dataSource = self; - tableView.delegate = self; - tableView.rowHeight = UITableViewAutomaticDimension; - tableView.estimatedRowHeight = 74.0; - tableView.sectionHeaderHeight = UITableViewAutomaticDimension; - tableView.estimatedSectionHeaderHeight = 64.0; - tableView.separatorStyle = UITableViewCellSeparatorStyleNone; - tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, DW_TABBAR_NOTCH, 0.0); - [tableView addSubview:topOverscrollView]; - [self addSubview:tableView]; - _tableView = tableView; - - NSArray *cellIds = @[ - DWTxListEmptyTableViewCell.dw_reuseIdentifier, - DWTxListTableViewCell.dw_reuseIdentifier, - DWDPRegistrationStatusTableViewCell.dw_reuseIdentifier, - DWDPRegistrationErrorTableViewCell.dw_reuseIdentifier, - DWDPRegistrationDoneTableViewCell.dw_reuseIdentifier, - ]; - for (NSString *cellId in cellIds) { - UINib *nib = [UINib nibWithNibName:cellId bundle:nil]; - NSParameterAssert(nib); - [tableView registerNib:nib forCellReuseIdentifier:cellId]; - } - - - UINib *nib = [UINib nibWithNibName:@"CNCreateAccountCell" bundle:nil]; - [tableView registerNib:nib forCellReuseIdentifier:CNCreateAccountCell.dw_reuseIdentifier]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(setNeedsLayout) - name:UIContentSizeCategoryDidChangeNotification - object:nil]; - - [self mvvm_observe:DW_KEYPATH(self, model.syncModel.state) - with:^(typeof(self) self, NSNumber *value) { - if (!value) { - return; - } - - //TODO: [self.syncingHeaderView setSyncState:self.model.syncModel.state]; - }]; - - [self mvvm_observe:DW_KEYPATH(self, model.syncModel.progress) - with:^(typeof(self) self, NSNumber *value) { - if (!value) { - return; - } - - //TODO: [self.syncingHeaderView setProgress:self.model.syncModel.progress]; - }]; - } - return self; -} - -- (void)setModel:(id)model { - NSParameterAssert(model); - _model = model; - model.updatesObserver = self; - - self.headerView.model = model; -} - -- (nullable id)shortcutsDelegate { - return self.headerView.shortcutsDelegate; -} - -- (void)setShortcutsDelegate:(nullable id)shortcutsDelegate { - self.headerView.shortcutsDelegate = shortcutsDelegate; -} - -- (void)layoutSubviews { - [super layoutSubviews]; - - CGSize size = self.bounds.size; - self.topOverscrollView.frame = CGRectMake(0.0, -size.height, size.width, size.height); - - UIView *tableHeaderView = self.tableView.tableHeaderView; - if (tableHeaderView) { - CGSize headerSize = [tableHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; - if (CGRectGetHeight(tableHeaderView.frame) != headerSize.height) { - tableHeaderView.frame = CGRectMake(0.0, 0.0, headerSize.width, headerSize.height); - self.tableView.tableHeaderView = tableHeaderView; - } - } -} - -#pragma mark - DWHomeModelUpdatesObserver - -- (void)homeModel:(id)model didUpdateDataSource:(DWTransactionListDataSource *)dataSource shouldAnimate:(BOOL)shouldAnimate { - self.currentDataSource = dataSource; - dataSource.retryDelegate = self; - - if (dataSource.isEmpty) { - self.tableView.dataSource = self; - [self.tableView reloadData]; - } - else { - self.tableView.dataSource = dataSource; - [self.tableView reloadData]; - } -} - -- (void)homeModel:(id)model didReceiveNewIncomingTransaction:(DSTransaction *)transaction { - [self.delegate homeView:self showReclassifyYourTransactionsFlowWithTransaction:transaction]; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return 1; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - NSString *cellId = DWTxListEmptyTableViewCell.dw_reuseIdentifier; - DWTxListEmptyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId - forIndexPath:indexPath]; - return cell; -} - -#pragma mark - UITableViewDelegate - -- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { - DWSyncingHeaderView *headerView = [[DWSyncingHeaderView alloc] initWithFrame:CGRectZero]; - headerView.delegate = self; - //TODO: [headerView setSyncState:self.model.syncModel.state]; - [headerView setProgress:self.model.syncModel.progress]; - self.syncingHeaderView = headerView; - - self.syncingHeaderView.isSyncing = YES; - [self.syncingHeaderView setProgress:0.5]; - - return headerView; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - [tableView deselectRowAtIndexPath:indexPath animated:YES]; - - if (self.currentDataSource.isEmpty) { - return; - } - - DWTransactionListDataItemType type = [self.currentDataSource itemTypeBy:indexPath]; - - if (type == DWTransactionListDataItemTypeCrowdnode) { - [self.delegate homeView:self showCrowdNodeTxs:[self.currentDataSource crowdnodeTxs]]; - return; - } - - DSTransaction *transaction = [self.currentDataSource transactionForIndexPath:indexPath]; - if (transaction) { - [self.delegate homeView:self didSelectTransaction:transaction]; - } - else { // registration status cell - [self.delegate homeViewShowDashPayRegistrationFlow:self]; - } -} - -#pragma mark - UIScrollViewDelegate - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - [self.headerView parentScrollViewDidScroll:scrollView]; -} - -#pragma mark - DWSyncingHeaderViewDelegate - -- (void)syncingHeaderView:(DWSyncingHeaderView *)view syncingButtonAction:(UIButton *)sender { - [self.delegate homeView:self showSyncingStatus:sender]; -} - -- (void)syncingHeaderView:(DWSyncingHeaderView *)view filterButtonAction:(UIButton *)sender { - [self.delegate homeView:self showTxFilter:sender]; -} - -#pragma mark - DWHomeHeaderViewDelegate - -- (void)homeHeaderViewDidUpdateContents:(DWHomeHeaderView *)view { - [self setNeedsLayout]; -} - -- (void)homeHeaderView:(DWHomeHeaderView *)view profileButtonAction:(UIControl *)sender { - [self.delegate homeView:self profileButtonAction:sender]; -} - -#pragma mark - DWDPRegistrationErrorRetryDelegate - -- (void)registrationErrorRetryAction { - if ([self.model.dashPayModel canRetry]) { - [self.model.dashPayModel retry]; - } - else { - [self.delegate homeViewShowDashPayRegistrationFlow:self]; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/HomeBalanceView.swift b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift similarity index 87% rename from DashWallet/Sources/UI/Home/Views/HomeBalanceView.swift rename to DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift index 477eb3704..86dbb01f9 100644 --- a/DashWallet/Sources/UI/Home/Views/HomeBalanceView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift @@ -17,35 +17,29 @@ import Foundation -// MARK: - BalanceViewDelegate +// MARK: - HomeBalanceViewDelegate @objc(DWHomeBalanceViewDelegate) -protocol BalanceViewDelegate: AnyObject { +protocol HomeBalanceViewDelegate: AnyObject { func balanceView(_ view: HomeBalanceView, balanceLongPressAction sender: UIControl) func balanceViewDidToggleBalanceVisibility(_ view: HomeBalanceView) } // MARK: - HomeBalanceViewDataSource -@objc(DWHomeBalanceViewDataSource) protocol HomeBalanceViewDataSource: BalanceViewDataSource { var isBalanceHidden: Bool { get } } // MARK: - HomeBalanceViewState -@objc(DWHomeBalanceViewState) enum HomeBalanceViewState: Int { - @objc(DWHomeBalanceViewState_Default) case `default` - - @objc(DWHomeBalanceViewState_Syncing) case syncing } // MARK: - HomeBalanceView -@objc(DWHomeBalanceView) final class HomeBalanceView: UIView { @IBOutlet private weak var contentView: UIView! @IBOutlet private var balanceButton: UIControl! @@ -56,7 +50,6 @@ final class HomeBalanceView: UIView { @IBOutlet private var amountsView: UIView! @IBOutlet private var balanceView: BalanceView! - @objc weak var dataSource: HomeBalanceViewDataSource? { didSet { balanceView.dataSource = dataSource @@ -65,16 +58,18 @@ final class HomeBalanceView: UIView { } } - @objc - weak var delegate: BalanceViewDelegate? + weak var delegate: HomeBalanceViewDelegate? - @objc var state: HomeBalanceViewState = .default { didSet { reloadView() } } + private var isBalanceHidden: Bool { + dataSource?.isBalanceHidden ?? false + } + override init(frame: CGRect) { super.init(frame: frame) commonInit() @@ -108,6 +103,7 @@ final class HomeBalanceView: UIView { } titleLabel.text = titleString + hideBalance(isBalanceHidden) } @objc @@ -136,16 +132,24 @@ final class HomeBalanceView: UIView { eyeSlashImageView.tintColor = UIColor.dw_darkBlue() - tapToUnhideLabel.titleLabel?.font = UIFont.dw_font(forTextStyle: .caption1) tapToUnhideLabel.setTitle(NSLocalizedString("Tap to hide balance", comment: ""), for: .normal) tapToUnhideLabel.setTitleColor(UIColor.white.withAlphaComponent(0.5), for: .normal) + tapToUnhideLabel.isUserInteractionEnabled = true + + let tapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(balanceButtonAction(_:))) + tapToUnhideLabel.addGestureRecognizer(tapRecognizer) let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(balanceLongPressAction(_:))) balanceButton.addGestureRecognizer(recognizer) balanceView.tint = .white + let isBalanceHidden = isBalanceHidden + hidingView.alpha = isBalanceHidden ? 1.0 : 0.0 + amountsView.alpha = isBalanceHidden ? 0.0 : 1.0 + tapToUnhideLabel.alpha = isBalanceHidden ? 0.0 : 1.0 + NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChangeNotification(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) } @@ -174,6 +178,9 @@ final class HomeBalanceView: UIView { @objc func hideBalance(_ hidden: Bool) { let animated = window != nil + let isAlreadyHidden = amountsView.alpha == 0 + + guard isAlreadyHidden != hidden else { return } UIView.animate(withDuration: animated ? kAnimationDuration : 0.0) { self.hidingView.alpha = hidden ? 1.0 : 0.0 diff --git a/DashWallet/Sources/UI/Home/Views/HomeBalanceView.xib b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.xib similarity index 100% rename from DashWallet/Sources/UI/Home/Views/HomeBalanceView.xib rename to DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.xib diff --git a/DashWallet/Sources/UI/Home/Views/DashPayProfileView.swift b/DashWallet/Sources/UI/Home/Views/Home Header View/DashPayProfileView.swift similarity index 97% rename from DashWallet/Sources/UI/Home/Views/DashPayProfileView.swift rename to DashWallet/Sources/UI/Home/Views/Home Header View/DashPayProfileView.swift index 5978fab68..e6adcf621 100644 --- a/DashWallet/Sources/UI/Home/Views/DashPayProfileView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Header View/DashPayProfileView.swift @@ -23,7 +23,7 @@ let AVATAR_SIZE = CGSize(width: 72.0, height: 72.0) // MARK: - DashPayProfileView -class DashPayProfileView: UIView { +class DashPayProfileView: UIControl { private(set) var contentView: UIView private(set) var avatarView: DWDPAvatarView private(set) var bellImageView: UIImageView @@ -43,7 +43,7 @@ class DashPayProfileView: UIView { } } - var isHighlighted = false { + override var isHighlighted: Bool { didSet { self.contentView.dw_pressedAnimation(.medium, pressed: isHighlighted) } diff --git a/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderModel.swift b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderModel.swift new file mode 100644 index 000000000..b434e3c10 --- /dev/null +++ b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderModel.swift @@ -0,0 +1,48 @@ +// +// Created by PT +// Copyright © 2023 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: - HomeHeaderModel + +final class HomeHeaderModel { + var stateDidChage: ((SyncingActivityMonitor.State) -> ())? + private(set) var state: SyncingActivityMonitor.State + + init() { + state = SyncingActivityMonitor.shared.state + SyncingActivityMonitor.shared.add(observer: self) + } + + deinit { + SyncingActivityMonitor.shared.remove(observer: self) + } +} + +// MARK: SyncingActivityMonitorObserver + + +extension HomeHeaderModel: SyncingActivityMonitorObserver { + func syncingActivityMonitorProgressDidChange(_ progress: Double) { + // NOP + } + + func syncingActivityMonitorStateDidChange(previousState: SyncingActivityMonitor.State, state: SyncingActivityMonitor.State) { + self.state = state + stateDidChage?(state) + } +} diff --git a/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift new file mode 100644 index 000000000..4f2c7d447 --- /dev/null +++ b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift @@ -0,0 +1,213 @@ +// +// Created by PT +// Copyright © 2023 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 + +private let kAvatarSize = CGSize(width: 72.0, height: 72.0) + +// MARK: - HomeHeaderViewDelegate + +protocol HomeHeaderViewDelegate: AnyObject { + func homeHeaderView(_ headerView: HomeHeaderView, profileButtonAction sender: UIControl) + func homeHeaderView(_ headerView: HomeHeaderView, retrySyncButtonAction sender: UIView) + func homeHeaderViewDidUpdateContents(_ headerView: HomeHeaderView) + func homeHeaderViewDidToggleBalanceVisibility(_ headerView: HomeHeaderView) +} + +// MARK: - HomeHeaderView + +@objc(DWHomeHeaderView) +final class HomeHeaderView: UIView { + + public weak var delegate: HomeHeaderViewDelegate? + + private(set) var profileView: DashPayProfileView! + private(set) var balanceView: HomeBalanceView! + private(set) var syncView: SyncView! + private(set) var shortcutsView: ShortcutsView! + private(set) var stackView: UIStackView! + + weak var balanceDataSource: HomeBalanceViewDataSource? { + get { + balanceView.dataSource + } + set { + balanceView.dataSource = newValue + } + } + + weak var shortcutsDelegate: ShortcutsActionDelegate? { + get { + shortcutsView.actionDelegate + } + set { + shortcutsView.actionDelegate = newValue + } + } + + private let model: HomeHeaderModel + + override init(frame: CGRect) { + model = HomeHeaderModel() + + super.init(frame: frame) + + profileView = DashPayProfileView(frame: .zero) + profileView.translatesAutoresizingMaskIntoConstraints = false + profileView.addTarget(self, action: #selector(profileViewAction(_:)), for: .touchUpInside) + profileView.isHidden = true + + balanceView = HomeBalanceView(frame: .zero) + balanceView.delegate = self + + syncView = SyncView.view() + syncView.delegate = self + + shortcutsView = ShortcutsView(frame: .zero) + shortcutsView.translatesAutoresizingMaskIntoConstraints = false + + let views: [UIView] = [profileView, balanceView, shortcutsView, syncView] + let stackView = UIStackView(arrangedSubviews: views) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + addSubview(stackView) + self.stackView = stackView + + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: topAnchor), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor), + ]) + + if model.state == .syncFailed || model.state == .noConnection { + showSyncView() + + } else { + hideSyncView() + } + + // TODO: Platform +// [self mvvm_observe:DW_KEYPATH(self, model.dashPayModel.registrationStatus) +// with:^(typeof(self) self, id value) { +// [self updateProfileView]; +// }]; +// +// [self mvvm_observe:DW_KEYPATH(self, model.dashPayModel.username) +// with:^(typeof(self) self, id value) { +// [self updateProfileView]; +// }]; +// +// [self mvvm_observe:DW_KEYPATH(self, model.dashPayModel.unreadNotificationsCount) +// with:^(typeof(self) self, id value) { +// self.profileView.unreadCount = self.model.dashPayModel.unreadNotificationsCount; +// }]; + + reloadBalance() + updateProfileView() + + model.stateDidChage = { [weak self] state in + self?.balanceView.state = state == .syncing ? .syncing : .default + + if state == .syncFailed || state == .noConnection { + self?.showSyncView() + } else { + self?.hideSyncView() + } + + self?.reloadBalance() + self?.reloadShortcuts() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc + func profileViewAction(_ sender: UIControl) { + delegate?.homeHeaderView(self, profileButtonAction: sender) + } + + func parentScrollViewDidScroll(_ scrollView: UIScrollView) { } + + func reloadBalance() { + let isSyncing = SyncingActivityMonitor.shared.state == .syncing + + balanceView.reloadView() + balanceView.reloadData() + balanceView.state = isSyncing ? .syncing : .`default` + } + + func reloadShortcuts() { + shortcutsView.reloadData() + } + + private func updateProfileView() { + profileView.isHidden = true + + // TODO: Platform +// let status = model?.dashPayModel.registrationStatus +// let completed = model?.dashPayModel.registrationCompleted ?? false +// if status?.state == .done || completed { +// profileView.username = model?.dashPay +// profileView.isHidden = false +// } else { +// profileView.isHidden = true +// } +// delegate?.homeHeaderViewDidUpdateContents(self) + } + + private func hideSyncView() { + syncView.isHidden = true + delegate?.homeHeaderViewDidUpdateContents(self) + } + + private func showSyncView() { + syncView.isHidden = false + delegate?.homeHeaderViewDidUpdateContents(self) + } +} + +// MARK: HomeBalanceViewDelegate + +extension HomeHeaderView: HomeBalanceViewDelegate { + func balanceView(_ view: HomeBalanceView, balanceLongPressAction sender: UIControl) { + let action = ShortcutAction(type: .localCurrency) + shortcutsDelegate?.shortcutsView(view, didSelectAction: action, sender: sender) + } + + func balanceViewDidToggleBalanceVisibility(_ view: HomeBalanceView) { + delegate?.homeHeaderViewDidToggleBalanceVisibility(self) + } +} + +// MARK: ShortcutsViewDelegate + +extension HomeHeaderView: ShortcutsViewDelegate { + func shortcutsViewDidUpdateContentSize(_ view: ShortcutsView) { + delegate?.homeHeaderViewDidUpdateContents(self) + } +} + +// MARK: SyncViewDelegate + +extension HomeHeaderView: SyncViewDelegate { + func syncViewRetryButtonAction(_ view: SyncView) { + delegate?.homeHeaderView(self, retrySyncButtonAction: view) + } +} diff --git a/DashWallet/Sources/UI/Home/Views/HomeView.swift b/DashWallet/Sources/UI/Home/Views/HomeView.swift new file mode 100644 index 000000000..bd1638d4f --- /dev/null +++ b/DashWallet/Sources/UI/Home/Views/HomeView.swift @@ -0,0 +1,281 @@ +// +// Created by PT +// Copyright © 2023 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: - HomeViewDelegate + +@objc(DWHomeViewDelegate) +protocol HomeViewDelegate: AnyObject { + func homeView(_ homeView: HomeView, showTxFilter sender: UIView) + func homeView(_ homeView: HomeView, showSyncingStatus sender: UIView) + func homeView(_ homeView: HomeView, profileButtonAction sender: UIControl) + func homeView(_ homeView: HomeView, didSelectTransaction transaction: DSTransaction) + func homeViewShowDashPayRegistrationFlow(_ homeView: HomeView) + func homeView(_ homeView: HomeView, showReclassifyYourTransactionsFlowWithTransaction transaction: DSTransaction) + func homeView(_ homeView: HomeView, showCrowdNodeTxs transactions: [DSTransaction]) +} + +// MARK: - HomeView + +@objc(DWHomeView) +final class HomeView: UIView, DWHomeModelUpdatesObserver, DWDPRegistrationErrorRetryDelegate { + + @objc + weak var delegate: HomeViewDelegate? + + private(set) var headerView: HomeHeaderView! + private(set) var topOverscrollView: UIView! + private(set) var tableView: UITableView! + + weak var syncingHeaderView: SyncingHeaderView? + + // Strong ref to current dataSource to make sure it always exists while tableView uses it + var currentDataSource: TransactionListDataSource? + + @objc + var model: DWHomeProtocol? { + didSet { + model?.updatesObserver = self + } + } + + @objc + weak var shortcutsDelegate: ShortcutsActionDelegate? { + get { headerView.shortcutsDelegate } + set { headerView.shortcutsDelegate = newValue } + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + } + + override func layoutSubviews() { + super.layoutSubviews() + + let size = bounds.size + topOverscrollView.frame = CGRect(x: 0.0, y: -size.height, width: size.width, height: size.height) + + if let tableHeaderView = tableView.tableHeaderView { + let headerSize = tableHeaderView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + if tableHeaderView.frame.height != headerSize.height { + tableHeaderView.frame = CGRect(x: 0.0, y: 0.0, width: headerSize.width, height: headerSize.height) + tableView.tableHeaderView = tableHeaderView + } + } + } + + private func setupView() { + backgroundColor = UIColor.dw_secondaryBackground() + + headerView = HomeHeaderView(frame: CGRect.zero) + headerView.balanceDataSource = self + headerView.delegate = self + + topOverscrollView = UIView(frame: CGRect.zero) + topOverscrollView.backgroundColor = UIColor.dw_dashNavigationBlue() + + tableView = UITableView(frame: bounds, style: .plain) + tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + tableView.tableHeaderView = headerView + tableView.backgroundColor = UIColor.dw_secondaryBackground() + tableView.dataSource = self + tableView.delegate = self + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 74.0 + tableView.sectionHeaderHeight = UITableView.automaticDimension + tableView.estimatedSectionHeaderHeight = 64.0 + tableView.separatorStyle = .none + // NOTE: tableView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: DW_TABBAR_NOTCH, right: 0.0) + tableView.addSubview(topOverscrollView) + addSubview(tableView) + + let cellIds = [ + TxListEmptyTableViewCell.reuseIdentifier, + TxListTableViewCell.reuseIdentifier, + DWDPRegistrationStatusTableViewCell.dw_reuseIdentifier, + DWDPRegistrationErrorTableViewCell.dw_reuseIdentifier, + DWDPRegistrationDoneTableViewCell.dw_reuseIdentifier, + ] + for cellId in cellIds { + let nib = UINib(nibName: cellId, bundle: nil) + tableView.register(nib, forCellReuseIdentifier: cellId) + } + + let nib = UINib(nibName: "CNCreateAccountCell", bundle: nil) + tableView.register(nib, forCellReuseIdentifier: CNCreateAccountCell.dw_reuseIdentifier) + tableView.registerClassforHeaderFooterView(for: SyncingHeaderView.self) + NotificationCenter.default.addObserver(self, + selector: #selector(setNeedsLayout), + name: UIContentSizeCategory.didChangeNotification, + object: nil) + } + + // MARK: - DWHomeModelUpdatesObserver + + func homeModel(_ model: DWHomeProtocol, didUpdate dataSource: TransactionListDataSource, shouldAnimate: Bool) { + currentDataSource = dataSource + dataSource.retryDelegate = self + + if dataSource.isEmpty { + tableView.dataSource = self + } else { + tableView.dataSource = dataSource + } + tableView.reloadData() + + headerView.reloadBalance() + } + + func homeModel(_ model: DWHomeProtocol, didReceiveNewIncomingTransaction transaction: DSTransaction) { + delegate?.homeView(self, showReclassifyYourTransactionsFlowWithTransaction: transaction) + } + + func homeModelDidChangeInnerModels(_ model: DWHomeProtocol) { + headerView.reloadBalance() + } + + func homeModelWant(toReloadShortcuts model: DWHomeProtocol) { + reloadShortcuts() + } + + + // MARK: - DWDPRegistrationErrorRetryDelegate + + func registrationErrorRetryAction() { + // TODO: Platform +// if model?.dashPayModel.canRetry ?? false { +// model?.dashPayModel.retry() +// } else { +// delegate?.homeViewShowDashPayRegistrationFlow(self) +// } + } + + @objc + func reloadShortcuts() { + headerView?.reloadShortcuts() + } +} + +// MARK: HomeHeaderViewDelegate + +extension HomeView: HomeHeaderViewDelegate { + func homeHeaderView(_ headerView: HomeHeaderView, retrySyncButtonAction sender: UIView) { + model?.retrySyncing() + } + + func homeHeaderViewDidToggleBalanceVisibility(_ headerView: HomeHeaderView) { + model?.balanceDisplayOptions.balanceHidden.toggle() + headerView.reloadBalance() + } + + func homeHeaderViewDidUpdateContents(_ view: HomeHeaderView) { + setNeedsLayout() + } + + func homeHeaderView(_ view: HomeHeaderView, profileButtonAction sender: UIControl) { + delegate?.homeView(self, profileButtonAction: sender) + } +} + +// MARK: SyncingHeaderViewDelegate + +extension HomeView: SyncingHeaderViewDelegate { + func syncingHeaderView(_ view: SyncingHeaderView, syncingButtonAction sender: UIButton) { + delegate?.homeView(self, showSyncingStatus: sender) + } + + func syncingHeaderView(_ view: SyncingHeaderView, filterButtonAction sender: UIButton) { + delegate?.homeView(self, showTxFilter: sender) + } +} + +// MARK: UITableViewDataSource, UITableViewDelegate + +extension HomeView: UITableViewDataSource, UITableViewDelegate { + // MARK: - UITableViewDataSource + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + 1 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cellId = TxListEmptyTableViewCell.reuseIdentifier + let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) + return cell + } + + // MARK: - UITableViewDelegate + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let headerView = tableView.dequeueReusableHeaderFooterView(type: SyncingHeaderView.self) + headerView.delegate = self + syncingHeaderView = headerView + return headerView + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + guard let currentDataSource, + !currentDataSource.isEmpty else { return } + + let type = currentDataSource.itemType(by: indexPath) + + if type == .crowdnode { + delegate?.homeView(self, showCrowdNodeTxs: currentDataSource.crowdnodeTxs()) + return + } + + if let transaction = currentDataSource.transactionForIndexPath(indexPath) { + delegate?.homeView(self, didSelectTransaction: transaction) + } else { // registration status cell + delegate?.homeViewShowDashPayRegistrationFlow(self) + } + } + + // MARK: - UIScrollViewDelegate + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + headerView.parentScrollViewDidScroll(scrollView) + } + + + +} + +// MARK: HomeBalanceViewDataSource + +extension HomeView: HomeBalanceViewDataSource { + var isBalanceHidden: Bool { + model?.balanceDisplayOptions.balanceHidden ?? false + } + + var mainAmountString: String { + model?.balanceModel?.mainAmountString() ?? "0" + } + + var supplementaryAmountString: String { + model?.balanceModel?.fiatAmountString() ?? "Not available" + } +} diff --git a/DashWallet/Sources/UI/Home/Views/Sync View/SyncModel.swift b/DashWallet/Sources/UI/Home/Views/Sync View/SyncModel.swift new file mode 100644 index 000000000..ee4b2be8c --- /dev/null +++ b/DashWallet/Sources/UI/Home/Views/Sync View/SyncModel.swift @@ -0,0 +1,83 @@ +// +// Created by PT +// Copyright © 2023 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: - SyncModel + +protocol SyncModel { + var networkStatusDidChange: ((NetworkStatus) -> ())? { get set } + var networkStatus: NetworkStatus { get } + + var stateDidChage: ((SyncingActivityMonitor.State) -> ())? { get set } + var state: SyncingActivityMonitor.State { get } + + var progressDidChange: ((Double) -> ())? { get set } + var progress: Double { get } +} + +// MARK: - SyncModelImpl + +final class SyncModelImpl: SyncModel { + var networkStatusDidChange: ((NetworkStatus) -> ())? + + var stateDidChage: ((SyncingActivityMonitor.State) -> ())? + private(set) var state: SyncingActivityMonitor.State + + var progressDidChange: ((Double) -> ())? + private(set) var progress: Double + + internal var reachabilityObserver: Any! + private let syncMonitor: SyncingActivityMonitor + + init() { + syncMonitor = SyncingActivityMonitor.shared + state = syncMonitor.state + progress = syncMonitor.progress + syncMonitor.add(observer: self) + + startNetworkMonitoring() + } + + func forceStartSyncingActivity() { + syncMonitor.forceStartSyncingActivity() + } + + deinit { + syncMonitor.remove(observer: self) + stopNetworkMonitoring() + } +} + +// MARK: NetworkReachabilityHandling + +extension SyncModelImpl: NetworkReachabilityHandling { } + +// MARK: SyncingActivityMonitorObserver + + +extension SyncModelImpl: SyncingActivityMonitorObserver { + func syncingActivityMonitorProgressDidChange(_ progress: Double) { + self.progress = progress + progressDidChange?(progress) + } + + func syncingActivityMonitorStateDidChange(previousState: SyncingActivityMonitor.State, state: SyncingActivityMonitor.State) { + self.state = state + stateDidChage?(state) + } +} diff --git a/DashWallet/Sources/UI/Home/Views/SyncView.swift b/DashWallet/Sources/UI/Home/Views/Sync View/SyncView.swift similarity index 90% rename from DashWallet/Sources/UI/Home/Views/SyncView.swift rename to DashWallet/Sources/UI/Home/Views/Sync View/SyncView.swift index 57e5a34ff..e922726e5 100644 --- a/DashWallet/Sources/UI/Home/Views/SyncView.swift +++ b/DashWallet/Sources/UI/Home/Views/Sync View/SyncView.swift @@ -28,16 +28,12 @@ protocol SyncViewDelegate: AnyObject { final class SyncView: UIView { weak var delegate: SyncViewDelegate? - var hasNetwork = true { - didSet { - updateView() - } + var hasNetwork: Bool { + model.networkStatus == .online } - var syncState: SyncingActivityMonitor.State = .unknown { - didSet { - updateView() - } + var syncState: SyncingActivityMonitor.State { + model.state } var viewStateSeeingBlocks = false @@ -49,14 +45,14 @@ final class SyncView: UIView { @IBOutlet var retryButton: UIButton! @IBOutlet var progressView: ProgressView! + internal lazy var model: SyncModel = SyncModelImpl() + override init(frame: CGRect) { super.init(frame: frame) - commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() } private func commonInit() { @@ -69,9 +65,21 @@ final class SyncView: UIView { let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(changeSeeBlocksStateAction(_:))) roundedView.addGestureRecognizer(tapGestureRecognizer) + + model.networkStatusDidChange = { [weak self] _ in + self?.updateView() + } + + model.progressDidChange = { [weak self] progress in + self?.set(progress: Float(progress), animated: true) + } + + model.stateDidChage = { [weak self] _ in + self?.updateView() + } } - func set(progress: Float, animated: Bool) { + private func set(progress: Float, animated: Bool) { percentLabel.text = String(format: "%.1f%%", progress * 100.0) progressView.setProgress(progress, animated: animated) @@ -123,6 +131,11 @@ final class SyncView: UIView { delegate?.syncViewRetryButtonAction(self) } + override func awakeFromNib() { + super.awakeFromNib() + + commonInit() + } } extension SyncView { diff --git a/DashWallet/Sources/UI/Home/Views/DWSyncView.xib b/DashWallet/Sources/UI/Home/Views/Sync View/SyncView.xib similarity index 100% rename from DashWallet/Sources/UI/Home/Views/DWSyncView.xib rename to DashWallet/Sources/UI/Home/Views/Sync View/SyncView.xib diff --git a/DashWallet/Sources/UI/Home/Views/SyncingHeaderView.swift b/DashWallet/Sources/UI/Home/Views/SyncingHeaderView.swift deleted file mode 100644 index 66c8fa58e..000000000 --- a/DashWallet/Sources/UI/Home/Views/SyncingHeaderView.swift +++ /dev/null @@ -1,133 +0,0 @@ -// -// Created by PT -// Copyright © 2023 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: - SyncingHeaderViewDelegate - -@objc(DWSyncingHeaderViewDelegate) -protocol SyncingHeaderViewDelegate: AnyObject { - func syncingHeaderView(_ view: SyncingHeaderView, filterButtonAction sender: UIButton) - func syncingHeaderView(_ view: SyncingHeaderView, syncingButtonAction sender: UIButton) -} - -// MARK: - SyncingHeaderView - -@objc(DWSyncingHeaderView) -final class SyncingHeaderView: UITableViewHeaderFooterView { - weak var delegate: SyncingHeaderViewDelegate? - - var syncingButton: UIButton! - - var progress = 0.0 { - didSet { - refreshView() - } - } - - var isSyncing = false { - didSet { - refreshView() - } - } - - override init(reuseIdentifier: String?) { - super.init(reuseIdentifier: reuseIdentifier) - - backgroundColor = UIColor.dw_secondaryBackground() - - let titleLabel = UILabel() - titleLabel.translatesAutoresizingMaskIntoConstraints = false - titleLabel.font = UIFont.dw_font(forTextStyle: .headline) - titleLabel.text = NSLocalizedString("History", comment: "") - titleLabel.textColor = UIColor.dw_darkTitle() - titleLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) - addSubview(titleLabel) - - syncingButton = DWButton() - syncingButton.translatesAutoresizingMaskIntoConstraints = false - syncingButton.contentHorizontalAlignment = .right - syncingButton.setTitleColor(UIColor.dw_darkTitle(), for: .normal) - syncingButton.setContentHuggingPriority(.defaultHigh - 1, for: .horizontal) - syncingButton.setContentCompressionResistancePriority(.required - 1, for: .horizontal) - syncingButton.addTarget(self, action: #selector(syncingButtonAction(_:)), for: .touchUpInside) - addSubview(syncingButton) - - let filterButton = UIButton(type: .custom) - filterButton.translatesAutoresizingMaskIntoConstraints = false - filterButton.setImage(UIImage(named: "icon_filter_button"), for: .normal) - filterButton.addTarget(self, action: #selector(filterButtonAction(_:)), for: .touchUpInside) - addSubview(filterButton) - - let padding: CGFloat = 16.0 - NSLayoutConstraint.activate([ - titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: padding), - bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: padding), - titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding), - - syncingButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), - bottomAnchor.constraint(greaterThanOrEqualTo: syncingButton.bottomAnchor), - syncingButton.centerYAnchor.constraint(equalTo: centerYAnchor), - syncingButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 8.0), - syncingButton.heightAnchor.constraint(equalToConstant: 44.0), - - filterButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), - bottomAnchor.constraint(greaterThanOrEqualTo: filterButton.bottomAnchor), - filterButton.centerYAnchor.constraint(equalTo: centerYAnchor), - filterButton.leadingAnchor.constraint(equalTo: syncingButton.trailingAnchor), - trailingAnchor.constraint(equalTo: filterButton.trailingAnchor, constant: 10.0), - filterButton.heightAnchor.constraint(equalToConstant: 44.0), - filterButton.widthAnchor.constraint(equalToConstant: 44.0), - ]) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc - func filterButtonAction(_ sender: UIButton) { - delegate?.syncingHeaderView(self, filterButtonAction: sender) - } - - @objc - func syncingButtonAction(_ sender: UIButton) { - delegate?.syncingHeaderView(self, syncingButtonAction: sender) - } -} - -extension SyncingHeaderView { - private func refreshView() { - syncingButton.isHidden = !isSyncing - - guard isSyncing else { - return - } - let percentString = String(format: "%0.1f%%", progress * 100.0) - - let result = NSMutableAttributedString() - - let str1 = NSAttributedString(string: String(format: "%@ ", NSLocalizedString("Syncing", comment: "")), - attributes: [NSAttributedString.Key.font: UIFont.dw_font(forTextStyle: .body)]) - result.append(str1) - - let str2 = NSAttributedString(string: percentString, attributes: [NSAttributedString.Key.font: UIFont.dw_font(forTextStyle: .headline)]) - result.append(str2) - - syncingButton.setAttributedTitle(result, for: .normal) - } -} diff --git a/DashWallet/Sources/UI/Main/MainTabbarController.swift b/DashWallet/Sources/UI/Main/MainTabbarController.swift new file mode 100644 index 000000000..bbfdfc51e --- /dev/null +++ b/DashWallet/Sources/UI/Main/MainTabbarController.swift @@ -0,0 +1,101 @@ +//// +//// Created by PT +//// Copyright © 2023 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 +// +// final class MainTabbarController: UITabBarController { +// static let kAnimationDuration: TimeInterval = 0.35 +// +// weak var homeController: DWHomeViewController? +// } +// +// class DWMainTabbarViewController: UITabBarController, DWHomeViewControllerDelegate, UINavigationControllerDelegate, DWWipeDelegate, DWMainMenuViewControllerDelegate { +// +// var isDemoMode: Bool = false +// +// +//// var homeNavigationController: DWNavigationController! +//// var contactsNavigationController: DWNavigationController! +//// var menuNavigationController: DWNavigationController! +//// +// +// func performScanQRCodeAction() { +// dismiss(animated: false, completion: nil) +// +//// transitionToController(homeNavigationController, transitionType: .withoutAnimation) +//// tabBarView?.updateSelectedTabButton(.home) +//// homeController?.performScanQRCodeAction() +// } +// +// func performPayToURL(_ url: URL) { +// dismiss(animated: false, completion: nil) +//// transitionToController(homeNavigationController, transitionType: .withoutAnimation) +//// tabBarView?.updateSelectedTabButton(.home) +//// homeController?.performPayToURL(url) +// } +// +// func handleFile(_ file: Data) { +// dismiss(animated: false, completion: nil) +//// transitionToController(homeNavigationController, transitionType: .withoutAnimation) +//// tabBarView?.updateSelectedTabButton(.home) +//// homeController?.handleFile(file) +// } +// +// +// +// +// override func viewDidLoad() { +// super.viewDidLoad() +// +// configureHierarchy() +// } +// } +// +// private extension DWMainTabbarViewController { +// func configureHierarchy() { +// +// } +// } +// +//// MARK: Demo mode +// extension DWMainTabbarViewController { +// func openPaymentsScreen() { +// assert(isDemoMode, "Invalid usage. Should be used in Demo mode only") +// //showPaymentsController(withActivePage: .pay) +// } +// +// func closePaymentsScreen() { +// assert(isDemoMode, "Invalid usage. Should be used in Demo mode only") +// //tabBarViewDidClosePayments(tabBarView) +// } +// } +// +// extension DWMainTabbarViewController: PaymentsViewControllerDelegate { +// func paymentsViewControllerWantsToImportPrivateKey(_ controller: PaymentsViewController) { +// +// } +// +// func paymentsViewControllerDidCancel(_ controller: PaymentsViewController) { +// +// } +// +// func paymentsViewControllerDidFinishPayment(_ controller: PaymentsViewController, contact: DWDPBasicUserItem?) { +// +// } +// +// +// } diff --git a/DashWallet/Sources/UI/Main/Views/PaymentButton.swift b/DashWallet/Sources/UI/Main/Views/PaymentButton.swift new file mode 100644 index 000000000..5236ba980 --- /dev/null +++ b/DashWallet/Sources/UI/Main/Views/PaymentButton.swift @@ -0,0 +1,22 @@ +// +// Created by PT +// Copyright © 2023 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 + +final class PaymentButton: UIButton { + var isOpened = false +} diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m index fd3d0d765..e6a842575 100644 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m +++ b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m @@ -48,7 +48,6 @@ @implementation DWHomeModelStub @synthesize payModel = _payModel; @synthesize receiveModel = _receiveModel; @synthesize dashPayModel = _dashPayModel; -@synthesize syncModel = _syncModel; @synthesize updatesObserver = _updatesObserver; @synthesize allDataSource = _allDataSource; @synthesize allowedToShowReclassifyYourTransactions = _allowedToShowReclassifyYourTransactions; @@ -56,7 +55,6 @@ @implementation DWHomeModelStub - (instancetype)init { self = [super init]; if (self) { - _syncModel = [[DWSyncModelStub alloc] init]; _dataProvider = [[DWTransactionListDataProviderStub alloc] init]; _stubTxs = [DWTransactionStub stubs]; diff --git a/DashWallet/Sources/UI/RootNavigation/DWRootModel.m b/DashWallet/Sources/UI/RootNavigation/DWRootModel.m index f276075c6..59d23d189 100644 --- a/DashWallet/Sources/UI/RootNavigation/DWRootModel.m +++ b/DashWallet/Sources/UI/RootNavigation/DWRootModel.m @@ -97,7 +97,8 @@ - (BOOL)shouldShowLockScreen { } - (void)setupDidFinish { - [self.homeModel.shortcutsModel reloadShortcuts]; + //TODO: check whether we really need to do that here + //[self.homeModel.shortcutsModel reloadShortcuts]; } - (void)wipeWallet { diff --git a/DashWallet/Sources/UI/Views/BadgeView.swift b/DashWallet/Sources/UI/Views/BadgeView.swift index e8f93ac41..015893d6f 100644 --- a/DashWallet/Sources/UI/Views/BadgeView.swift +++ b/DashWallet/Sources/UI/Views/BadgeView.swift @@ -57,7 +57,7 @@ final class BadgeView: UIView { override var backgroundColor: UIColor? { didSet { - label.backgroundColor = backgroundColor + label?.backgroundColor = backgroundColor } } diff --git a/DashWallet/Sources/UI/Views/ProgressView.swift b/DashWallet/Sources/UI/Views/ProgressView.swift index db8733591..74fe080a9 100644 --- a/DashWallet/Sources/UI/Views/ProgressView.swift +++ b/DashWallet/Sources/UI/Views/ProgressView.swift @@ -25,11 +25,7 @@ final class ProgressView: UIView { private var blueLayer: CALayer! private var animating = false - var progress: Float = 0.0 { - didSet { - setProgress(progress, animated: false) - } - } + var progress: Float = 0.0 required init?(coder: NSCoder) { super.init(coder: coder) diff --git a/DashWallet/Sources/UI/Views/Transitions/Item/Model/TransactionDataItem.swift b/DashWallet/Sources/UI/Views/Transitions/Item/Model/TransactionDataItem.swift index 1c1cd6344..4bc833f2a 100644 --- a/DashWallet/Sources/UI/Views/Transitions/Item/Model/TransactionDataItem.swift +++ b/DashWallet/Sources/UI/Views/Transitions/Item/Model/TransactionDataItem.swift @@ -51,7 +51,7 @@ extension TransactionDataItem { return NSAttributedString(string: NSLocalizedString("Syncing...", comment: "Transaction/Amount")) } - var formatted = formattedDashAmountWithDirectionalSymbol + let formatted = formattedDashAmountWithDirectionalSymbol return formatted.attributedAmountStringWithDashSymbol(tintColor: color, dashSymbolColor: color, font: font) } } diff --git a/DashWallet/dashwallet-Bridging-Header.h b/DashWallet/dashwallet-Bridging-Header.h index 02a6beb13..4634e566c 100644 --- a/DashWallet/dashwallet-Bridging-Header.h +++ b/DashWallet/dashwallet-Bridging-Header.h @@ -24,31 +24,13 @@ static const bool _SNAPSHOT = 0; //MARK: DashWallet #import "DWActionButton.h" -#import "DWDPBasicUserItem.h" #import "DWEnvironment.h" #import "DWTitleDetailCellModel.h" #import "DWTitleDetailItem.h" #import "DWGlobalOptions.h" -#import "DWDPUserObject.h" #import "DWUIKit.h" #import "DWAboutModel.h" #import "DWDateFormatter.h" -#import "DWDPRegistrationStatus.h" -#import "DWDPRegistrationErrorTableViewCell.h" -#import "DWDPRegistrationDoneTableViewCell.h" -#import "DWDPRegistrationStatusTableViewCell.h" -#import "DWDPRegistrationErrorRetryDelegate.h" - -//MARK: CrowdNode -#import "DWCheckbox.h" -#import "DWPreviewSeedPhraseModel.h" -#import "DWSeedPhraseModel.h" -#import "DWSecureWalletDelegate.h" -#import "UIImage+Utils.h" -#import "NSData+Dash.h" -// end CrowdNode - - #import "DWBaseActionButtonViewController.h" #import "DWNumberKeyboardInputViewAudioFeedback.h" #import "DWInputValidator.h" @@ -73,7 +55,8 @@ static const bool _SNAPSHOT = 0; #import "CALayer+DWShadow.h" #import "DSTransaction+DashWallet.h" #import "DWAlertController.h" - +#import "DWHomeProtocol.h" +#import "DWDPRegistrationErrorRetryDelegate.h" //MARK: Backup Wallet #import "DWBackupSeedPhraseViewController.h" @@ -93,6 +76,8 @@ static const bool _SNAPSHOT = 0; #import "UIViewController+DWShareReceiveInfo.h" #import "DWImportWalletInfoViewController.h" +//MARK: Tabbar +#import "DWWipeDelegate.h" //MARK: Uphold #import "DWUpholdTransactionObject.h" @@ -111,3 +96,17 @@ static const bool _SNAPSHOT = 0; //MARK: Platform #import "DWDPBasicUserItem.h" +#import "DWDPAvatarView.h" +#import "DWDPRegistrationStatus.h" +#import "DWDPRegistrationErrorTableViewCell.h" +#import "DWDPRegistrationDoneTableViewCell.h" +#import "DWDPRegistrationStatusTableViewCell.h" +#import "DWDPRegistrationErrorRetryDelegate.h" +#import "DWDPUserObject.h" + +//MARK: CrowdNode +#import "DWCheckbox.h" +#import "DWPreviewSeedPhraseModel.h" +#import "DWSeedPhraseModel.h" +#import "UIImage+Utils.h" +#import "NSData+Dash.h" From f67fe106997ef01473ec61e869ddfaeb18de5601 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 15 May 2023 22:02:44 +0700 Subject: [PATCH 13/38] chore: Code clean up --- DashWallet.xcodeproj/project.pbxproj | 36 +++++++++---------- .../DWExploreTestnetViewController.h | 1 - .../UI/Home/Protocols/DWBalanceProtocol.h | 1 - .../UI/Home/Protocols/DWHomeProtocol.h | 1 - .../Home/Protocols/DWSyncContainerProtocol.h | 32 ----------------- .../UI/Home/Protocols/DWSyncProtocol.h | 36 ------------------- .../Alert}/SyncingAlertContentView.swift | 0 .../Alert}/SyncingAlertViewController.swift | 0 .../Model}/SyncModel.swift | 0 .../Sync View/SyncView.swift | 0 .../Sync View/SyncView.xib | 0 .../UI/Menu/Main/DWMainMenuViewController.h | 1 - .../UI/Onboarding/Stubs/DWHomeModelStub.m | 1 - .../UI/Onboarding/Stubs/DWSyncModelStub.h | 28 --------------- .../UI/Onboarding/Stubs/DWSyncModelStub.m | 34 ------------------ 15 files changed, 17 insertions(+), 154 deletions(-) delete mode 100644 DashWallet/Sources/UI/Home/Protocols/DWSyncContainerProtocol.h delete mode 100644 DashWallet/Sources/UI/Home/Protocols/DWSyncProtocol.h rename DashWallet/Sources/UI/Home/{SyncingAlert/View => Syncing Views/Alert}/SyncingAlertContentView.swift (100%) rename DashWallet/Sources/UI/Home/{SyncingAlert => Syncing Views/Alert}/SyncingAlertViewController.swift (100%) rename DashWallet/Sources/UI/Home/{Views/Sync View => Syncing Views/Model}/SyncModel.swift (100%) rename DashWallet/Sources/UI/Home/{Views => Syncing Views}/Sync View/SyncView.swift (100%) rename DashWallet/Sources/UI/Home/{Views => Syncing Views}/Sync View/SyncView.xib (100%) delete mode 100644 DashWallet/Sources/UI/Onboarding/Stubs/DWSyncModelStub.h delete mode 100644 DashWallet/Sources/UI/Onboarding/Stubs/DWSyncModelStub.m diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 5fcc6d908..1bc290659 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -305,7 +305,6 @@ 2A913E7123A2667E006A2A59 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A913E7023A2667E006A2A59 /* Onboarding.storyboard */; }; 2A913E7F23A30609006A2A59 /* DWRootModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E7E23A30609006A2A59 /* DWRootModelStub.m */; }; 2A913E8223A30623006A2A59 /* DWHomeModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8123A30623006A2A59 /* DWHomeModelStub.m */; }; - 2A913E8523A309EA006A2A59 /* DWSyncModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8423A309EA006A2A59 /* DWSyncModelStub.m */; }; 2A913E8923A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8823A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.m */; }; 2A913E8C23A3134E006A2A59 /* UIView+DWEmbedding.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8B23A3134E006A2A59 /* UIView+DWEmbedding.m */; }; 2A913E9023A31713006A2A59 /* UIViewController+DWEmbedding.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8F23A31713006A2A59 /* UIViewController+DWEmbedding.m */; }; @@ -1330,17 +1329,13 @@ 2A913E6E23A26663006A2A59 /* DWOnboardingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWOnboardingViewController.m; sourceTree = ""; }; 2A913E7023A2667E006A2A59 /* Onboarding.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = ""; }; 2A913E7323A2EB0E006A2A59 /* DWBalanceProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBalanceProtocol.h; sourceTree = ""; }; - 2A913E7623A2EF06006A2A59 /* DWSyncProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSyncProtocol.h; sourceTree = ""; }; 2A913E7723A2F2B6006A2A59 /* DWTxDisplayModeProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTxDisplayModeProtocol.h; sourceTree = ""; }; - 2A913E7923A2F637006A2A59 /* DWSyncContainerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSyncContainerProtocol.h; sourceTree = ""; }; 2A913E7A23A2F7ED006A2A59 /* DWHomeProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeProtocol.h; sourceTree = ""; }; 2A913E7B23A2FF4A006A2A59 /* DWRootProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRootProtocol.h; sourceTree = ""; }; 2A913E7D23A30609006A2A59 /* DWRootModelStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRootModelStub.h; sourceTree = ""; }; 2A913E7E23A30609006A2A59 /* DWRootModelStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWRootModelStub.m; sourceTree = ""; }; 2A913E8023A30623006A2A59 /* DWHomeModelStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeModelStub.h; sourceTree = ""; }; 2A913E8123A30623006A2A59 /* DWHomeModelStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWHomeModelStub.m; sourceTree = ""; }; - 2A913E8323A309EA006A2A59 /* DWSyncModelStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSyncModelStub.h; sourceTree = ""; }; - 2A913E8423A309EA006A2A59 /* DWSyncModelStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSyncModelStub.m; sourceTree = ""; }; 2A913E8623A30C2D006A2A59 /* DWBalanceDisplayOptionsProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBalanceDisplayOptionsProtocol.h; sourceTree = ""; }; 2A913E8723A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBalanceDisplayOptionsStub.h; sourceTree = ""; }; 2A913E8823A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBalanceDisplayOptionsStub.m; sourceTree = ""; }; @@ -2559,13 +2554,14 @@ path = DataMigration; sourceTree = ""; }; - 2A1A60B92674083B003DAC65 /* SyncingAlert */ = { + 2A1A60B92674083B003DAC65 /* Syncing Views */ = { isa = PBXGroup; children = ( - C9F451EC2A0BF1EF00825057 /* View */, - C9F451EA2A0BF10B00825057 /* SyncingAlertViewController.swift */, + C9F4520C2A121EEF00825057 /* Model */, + C9F4520D2A121EF900825057 /* Sync View */, + C9F451EC2A0BF1EF00825057 /* Alert */, ); - path = SyncingAlert; + path = "Syncing Views"; sourceTree = ""; }; 2A1AE78B23F463FB00179A6E /* Views */ = { @@ -2692,7 +2688,6 @@ 2A307CB322E6FA7F00A18347 /* Views */ = { isa = PBXGroup; children = ( - C9F452062A11F27400825057 /* Sync View */, C9F451FF2A0CE63C00825057 /* Home Balance View */, C9F451FE2A0CE60400825057 /* Home Header View */, 2A2CD70B22F97A9D008C7BC9 /* Shortcuts */, @@ -3602,8 +3597,6 @@ 2A913E8623A30C2D006A2A59 /* DWBalanceDisplayOptionsProtocol.h */, 2A913E7323A2EB0E006A2A59 /* DWBalanceProtocol.h */, 2A913E7A23A2F7ED006A2A59 /* DWHomeProtocol.h */, - 2A913E7923A2F637006A2A59 /* DWSyncContainerProtocol.h */, - 2A913E7623A2EF06006A2A59 /* DWSyncProtocol.h */, 2A913E7723A2F2B6006A2A59 /* DWTxDisplayModeProtocol.h */, 2A56EF0724193BA9002C32F3 /* DWDashPayProtocol.h */, ); @@ -3623,8 +3616,6 @@ 2AB3416123A81E8B004E37A7 /* DWReceiveModelStub.m */, 2A913E7D23A30609006A2A59 /* DWRootModelStub.h */, 2A913E7E23A30609006A2A59 /* DWRootModelStub.m */, - 2A913E8323A309EA006A2A59 /* DWSyncModelStub.h */, - 2A913E8423A309EA006A2A59 /* DWSyncModelStub.m */, 2A913EA623A79AD2006A2A59 /* DWTransactionListDataProviderStub.h */, 2A913EA723A79AD2006A2A59 /* DWTransactionListDataProviderStub.m */, 2A913EB423A7E145006A2A59 /* DWTransactionStub.h */, @@ -3748,7 +3739,7 @@ 2A9CEBB622E1FA0000A50237 /* Home */ = { isa = PBXGroup; children = ( - 2A1A60B92674083B003DAC65 /* SyncingAlert */, + 2A1A60B92674083B003DAC65 /* Syncing Views */, 2A913E7223A2EAEF006A2A59 /* Protocols */, 2A2CD6E522F46D1A008C7BC9 /* Models */, 2A307CB322E6FA7F00A18347 /* Views */, @@ -5710,12 +5701,13 @@ path = Style; sourceTree = ""; }; - C9F451EC2A0BF1EF00825057 /* View */ = { + C9F451EC2A0BF1EF00825057 /* Alert */ = { isa = PBXGroup; children = ( C9F451ED2A0BF1F500825057 /* SyncingAlertContentView.swift */, + C9F451EA2A0BF10B00825057 /* SyncingAlertViewController.swift */, ); - path = View; + path = Alert; sourceTree = ""; }; C9F451FE2A0CE60400825057 /* Home Header View */ = { @@ -5737,10 +5729,17 @@ path = "Home Balance View"; sourceTree = ""; }; - C9F452062A11F27400825057 /* Sync View */ = { + C9F4520C2A121EEF00825057 /* Model */ = { isa = PBXGroup; children = ( C9F452072A11F28600825057 /* SyncModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + C9F4520D2A121EF900825057 /* Sync View */ = { + isa = PBXGroup; + children = ( C9F451F62A0CAE1300825057 /* SyncView.swift */, 2A4E531922EA382B00E5168A /* SyncView.xib */, ); @@ -6978,7 +6977,6 @@ C9F42FA429DBC6E5001BC549 /* PaymentsViewController.swift in Sources */, 2A7AF3832482954C001D74F9 /* DWDPContactsItemsFactory.m in Sources */, 4774DCDD28F43A68008CF87D /* ServiceDataSource.swift in Sources */, - 2A913E8523A309EA006A2A59 /* DWSyncModelStub.m in Sources */, 47838B7A2900196F0003E8AB /* ConverterView.swift in Sources */, 2A0C69CA23142E11001B8C90 /* DWModalBaseAnimation.m in Sources */, C9F42FB829DFC507001BC549 /* SpendableTransaction.swift in Sources */, diff --git a/DashWallet/Sources/UI/Explore Dash/DWExploreTestnetViewController.h b/DashWallet/Sources/UI/Explore Dash/DWExploreTestnetViewController.h index f1c4c73c1..8d837471c 100644 --- a/DashWallet/Sources/UI/Explore Dash/DWExploreTestnetViewController.h +++ b/DashWallet/Sources/UI/Explore Dash/DWExploreTestnetViewController.h @@ -15,7 +15,6 @@ // limitations under the License. // -#import "DWSyncProtocol.h" #import "dashwallet-Swift.h" #import diff --git a/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h index e0ccec9d5..3ce006e7b 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h @@ -18,7 +18,6 @@ #import #import "DWBalanceDisplayOptionsProtocol.h" -#import "DWSyncContainerProtocol.h" NS_ASSUME_NONNULL_BEGIN diff --git a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h index 29223dffb..0c8a0e1ac 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h @@ -19,7 +19,6 @@ #import "DWBalanceProtocol.h" #import "DWDashPayProtocol.h" -#import "DWSyncContainerProtocol.h" #import "DWTxDisplayModeProtocol.h" NS_ASSUME_NONNULL_BEGIN diff --git a/DashWallet/Sources/UI/Home/Protocols/DWSyncContainerProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWSyncContainerProtocol.h deleted file mode 100644 index 3a74cf81f..000000000 --- a/DashWallet/Sources/UI/Home/Protocols/DWSyncContainerProtocol.h +++ /dev/null @@ -1,32 +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 - -#import "DWSyncProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@protocol DWSyncContainerProtocol - -@property (readonly, nonatomic, strong) id syncModel; - -- (void)retrySyncing; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Protocols/DWSyncProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWSyncProtocol.h deleted file mode 100644 index 212083a20..000000000 --- a/DashWallet/Sources/UI/Home/Protocols/DWSyncProtocol.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 - -typedef NS_ENUM(NSUInteger, DWSyncModelState) { - DWSyncModelState_Syncing, - DWSyncModelState_SyncDone, - DWSyncModelState_SyncFailed, - DWSyncModelState_NoConnection, -}; - -@protocol DWSyncProtocol - -@property (readonly, nonatomic, assign) DWSyncModelState state; -@property (readonly, nonatomic, assign) float progress; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift b/DashWallet/Sources/UI/Home/Syncing Views/Alert/SyncingAlertContentView.swift similarity index 100% rename from DashWallet/Sources/UI/Home/SyncingAlert/View/SyncingAlertContentView.swift rename to DashWallet/Sources/UI/Home/Syncing Views/Alert/SyncingAlertContentView.swift diff --git a/DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift b/DashWallet/Sources/UI/Home/Syncing Views/Alert/SyncingAlertViewController.swift similarity index 100% rename from DashWallet/Sources/UI/Home/SyncingAlert/SyncingAlertViewController.swift rename to DashWallet/Sources/UI/Home/Syncing Views/Alert/SyncingAlertViewController.swift diff --git a/DashWallet/Sources/UI/Home/Views/Sync View/SyncModel.swift b/DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Sync View/SyncModel.swift rename to DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift diff --git a/DashWallet/Sources/UI/Home/Views/Sync View/SyncView.swift b/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Sync View/SyncView.swift rename to DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift diff --git a/DashWallet/Sources/UI/Home/Views/Sync View/SyncView.xib b/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.xib similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Sync View/SyncView.xib rename to DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.xib diff --git a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h index 14e072a6a..04e6843b5 100644 --- a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h +++ b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h @@ -17,7 +17,6 @@ #import -#import "DWSyncProtocol.h" #import "DWWipeDelegate.h" #import "dashwallet-Swift.h" diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m index e6a842575..dfca3af0b 100644 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m +++ b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m @@ -22,7 +22,6 @@ #import "DWEnvironment.h" #import "DWPayModelStub.h" #import "DWReceiveModelStub.h" -#import "DWSyncModelStub.h" #import "DWTransactionListDataProviderStub.h" #import "DWTransactionStub.h" #import "dashwallet-Swift.h" diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWSyncModelStub.h b/DashWallet/Sources/UI/Onboarding/Stubs/DWSyncModelStub.h deleted file mode 100644 index 441b112a8..000000000 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWSyncModelStub.h +++ /dev/null @@ -1,28 +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 - -#import "DWSyncProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWSyncModelStub : NSObject - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWSyncModelStub.m b/DashWallet/Sources/UI/Onboarding/Stubs/DWSyncModelStub.m deleted file mode 100644 index 6ead90c57..000000000 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWSyncModelStub.m +++ /dev/null @@ -1,34 +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 "DWSyncModelStub.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation DWSyncModelStub - -- (DWSyncModelState)state { - return DWSyncModelState_SyncDone; -} - -- (float)progress { - return 1.0; -} - -@end - -NS_ASSUME_NONNULL_END From a0c7c7e219ef65d8968236d676a46e0fd845f046 Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 18 May 2023 14:31:01 +0700 Subject: [PATCH 14/38] refactor: Initiate re-sync from RootModel instead of HomeModel --- DashWallet/Sources/UI/Home/Models/DWHomeModel.h | 1 - DashWallet/Sources/UI/Home/Models/DWHomeModel.m | 4 ---- DashWallet/Sources/UI/RootNavigation/DWRootModel.m | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.h b/DashWallet/Sources/UI/Home/Models/DWHomeModel.h index 7f434f105..404e6a8e7 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.h +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.h @@ -23,7 +23,6 @@ NS_ASSUME_NONNULL_BEGIN @interface DWHomeModel : NSObject -- (void)forceStartSyncingActivity; @end diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index e5a37f878..36c9d3a70 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -276,10 +276,6 @@ - (BOOL)performOnSetupUpgrades { return YES; } -- (void)forceStartSyncingActivity { - [[SyncingActivityMonitor shared] forceStartSyncingActivity]; -} - - (void)walletDidWipe { #if DASHPAY_ENABLED self.dashPayModel = [[DWDashPayModel alloc] init]; diff --git a/DashWallet/Sources/UI/RootNavigation/DWRootModel.m b/DashWallet/Sources/UI/RootNavigation/DWRootModel.m index 59d23d189..5ebf272df 100644 --- a/DashWallet/Sources/UI/RootNavigation/DWRootModel.m +++ b/DashWallet/Sources/UI/RootNavigation/DWRootModel.m @@ -119,7 +119,7 @@ - (void)currentNetworkDidChangeNotification:(NSNotification *)notification { self.currentNetworkDidChangeBlock(); } - [homeModel forceStartSyncingActivity]; + [SyncingActivityMonitor.shared forceStartSyncingActivity]; } @end From d03706c83d41bccbe0fc624f9900c6143394ae91 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 22 May 2023 10:51:33 +0700 Subject: [PATCH 15/38] wip: Moving BalanceModel out of HomeModel --- DashWallet.xcodeproj/project.pbxproj | 2 +- .../Sources/UI/Home/Models/DWHomeModel.m | 28 -------------- .../UI/Home/Protocols/DWBalanceProtocol.h | 1 - .../Home Balance View}/BalanceModel.swift | 38 +++++++++++++------ .../Home Balance View/HomeBalanceView.swift | 1 + DashWallet/dashwallet-Bridging-Header.h | 1 + 6 files changed, 30 insertions(+), 41 deletions(-) rename DashWallet/Sources/UI/Home/{Models => Views/Home Balance View}/BalanceModel.swift (56%) diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 8d7a2ddfb..838cf865c 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -2648,7 +2648,6 @@ isa = PBXGroup; children = ( 2A913EA123A799D9006A2A59 /* Transactions */, - 472D13E7299E4EE7006903F1 /* BalanceModel.swift */, 2A4E533622F023AB00E5168A /* DWHomeModel.h */, 2A4E533722F023AB00E5168A /* DWHomeModel.m */, C9F452042A0CF4E500825057 /* HomeModel.swift */, @@ -5723,6 +5722,7 @@ C9F451FF2A0CE63C00825057 /* Home Balance View */ = { isa = PBXGroup; children = ( + 472D13E7299E4EE7006903F1 /* BalanceModel.swift */, C9F067F129E4576D0022D958 /* HomeBalanceView.swift */, C9F067F329E457790022D958 /* HomeBalanceView.xib */, ); diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index 36c9d3a70..fd46ff1e6 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -44,7 +44,6 @@ @interface DWHomeModel () dashPayModel; @property (nonatomic, strong) SyncingActivityMonitor *syncMonitor; @@ -492,25 +491,6 @@ - (void)reloadTxDataSource { - (void)updateBalance { [self.receiveModel updateReceivingInfo]; - - uint64_t balanceValue = [DWEnvironment sharedInstance].currentWallet.balance; - if (self.balanceModel && - balanceValue > self.balanceModel.value && - self.balanceModel.value > 0 && - [UIApplication sharedApplication].applicationState != UIApplicationStateBackground && - [SyncingActivityMonitor shared].progress > 0.995) { - [[UIDevice currentDevice] dw_playCoinSound]; - } - - self.balanceModel = [[DWBalanceModel alloc] initWith:balanceValue]; - - DWGlobalOptions *options = [DWGlobalOptions sharedInstance]; - if (balanceValue > 0 && options.walletNeedsBackup && !options.balanceChangedDate) { - options.balanceChangedDate = [NSDate date]; - } - - options.userHasBalance = balanceValue > 0; - [self.updatesObserver homeModelWantToReloadShortcuts:self]; } @@ -540,14 +520,6 @@ - (void)updateBalance { return [mutableTransactions copy]; } -- (NSString *)supplementaryAmountString { - return [self.balanceModel fiatAmountString]; -} - -- (NSString *)mainAmountString { - return [self.balanceModel mainAmountString]; -} - - (BOOL)isBalanceHidden { return self.balanceDisplayOptions.balanceHidden; } diff --git a/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h index 3ce006e7b..c7576f138 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h @@ -25,7 +25,6 @@ NS_ASSUME_NONNULL_BEGIN @protocol DWBalanceProtocol -@property (readonly, nullable, nonatomic, strong) DWBalanceModel *balanceModel; @property (readonly, nonatomic, strong) id balanceDisplayOptions; @end diff --git a/DashWallet/Sources/UI/Home/Models/BalanceModel.swift b/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift similarity index 56% rename from DashWallet/Sources/UI/Home/Models/BalanceModel.swift rename to DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift index 951beb70c..e8637f590 100644 --- a/DashWallet/Sources/UI/Home/Models/BalanceModel.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift @@ -17,29 +17,45 @@ import Foundation -@objc(DWBalanceModel) -final class BalanceModel: NSObject { - @objc - let value: UInt64 +final class BalanceModel { + var value: UInt64 = 0 - @objc - init(with value: UInt64) { - self.value = value + init() { + reloadBalance() + } + + private func reloadBalance() { + let balanceValue = DWEnvironment.sharedInstance().currentWallet.balance + + if (balanceValue > value && + value > 0 && + UIApplication.shared.applicationState != .background && + SyncingActivityMonitor.shared.progress > 0.995) { + UIDevice.current.dw_playCoinSound() + } - super.init() + value = balanceValue + + let options = DWGlobalOptions.sharedInstance() + if (balanceValue > 0 + && options.walletNeedsBackup + && (options.balanceChangedDate == nil)) { + options.balanceChangedDate = Date() + } + + options.userHasBalance = balanceValue > 0; } +} - @objc +extension BalanceModel { func dashAmountStringWithFont(_ font: UIFont, tintColor: UIColor) -> NSAttributedString { NSAttributedString.dashAttributedString(for: value, tintColor: tintColor, font: font) } - @objc func mainAmountString() -> String { value.formattedDashAmount } - @objc func fiatAmountString() -> String { CurrencyExchanger.shared.fiatAmountString(for: value.dashAmount) } diff --git a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift index 86dbb01f9..80f214ca2 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift @@ -70,6 +70,7 @@ final class HomeBalanceView: UIView { dataSource?.isBalanceHidden ?? false } + private let model = BalanceModel() override init(frame: CGRect) { super.init(frame: frame) commonInit() diff --git a/DashWallet/dashwallet-Bridging-Header.h b/DashWallet/dashwallet-Bridging-Header.h index 512cce5b1..624a6e28a 100644 --- a/DashWallet/dashwallet-Bridging-Header.h +++ b/DashWallet/dashwallet-Bridging-Header.h @@ -59,6 +59,7 @@ static const bool _SNAPSHOT = 0; #import "DWAlertController.h" #import "DWHomeProtocol.h" #import "DWDPRegistrationErrorRetryDelegate.h" +#import "UIDevice+DashWallet.h" //MARK: Backup Wallet #import "DWBackupSeedPhraseViewController.h" From f8429060c709bc214c9ae919515c882df4f65ae6 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 29 May 2023 13:12:03 +0700 Subject: [PATCH 16/38] refactor: Make it simpler to toggle balance visibility --- DashWallet.xcodeproj/project.pbxproj | 20 ------ .../SyncingActivityMonitor.swift | 4 ++ .../Sources/UI/Home/DWHomeViewController.m | 3 +- .../UI/Home/Models/DWBalanceDisplayOptions.h | 28 --------- .../UI/Home/Models/DWBalanceDisplayOptions.m | 48 -------------- .../Sources/UI/Home/Models/DWHomeModel.m | 8 --- .../Sources/UI/Home/Models/HomeModel.swift | 18 ------ .../DWBalanceDisplayOptionsProtocol.h | 30 --------- .../UI/Home/Protocols/DWBalanceProtocol.h | 32 ---------- .../UI/Home/Protocols/DWHomeProtocol.h | 3 +- .../Home/Syncing Views/Model/SyncModel.swift | 2 + .../Syncing Views/Sync View/SyncView.swift | 2 + .../Home Balance View/BalanceModel.swift | 62 ++++++++++++++++--- .../Home Balance View/HomeBalanceView.swift | 46 +++++++------- .../Home Header View/HomeHeaderView.swift | 14 ----- .../Sources/UI/Home/Views/HomeView.swift | 28 +++------ .../Home/Views/Shortcuts/ShortcutsView.swift | 5 +- .../UI/Main/DWMainTabbarViewController.m | 2 +- .../UI/Menu/Main/DWMainMenuViewController.h | 4 -- .../UI/Menu/Main/DWMainMenuViewController.m | 7 +-- .../UI/Menu/Security/DWSecurityMenuModel.h | 4 -- .../UI/Menu/Security/DWSecurityMenuModel.m | 6 +- .../Security/DWSecurityMenuViewController.h | 4 +- .../Security/DWSecurityMenuViewController.m | 8 +-- .../UI/Menu/Settings/DWSettingsMenuModel.h | 2 - .../Stubs/DWBalanceDisplayOptionsStub.h | 28 --------- .../Stubs/DWBalanceDisplayOptionsStub.m | 39 ------------ .../UI/Onboarding/Stubs/DWHomeModelStub.m | 18 ------ 28 files changed, 107 insertions(+), 368 deletions(-) delete mode 100644 DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.h delete mode 100644 DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.m delete mode 100644 DashWallet/Sources/UI/Home/Models/HomeModel.swift delete mode 100644 DashWallet/Sources/UI/Home/Protocols/DWBalanceDisplayOptionsProtocol.h delete mode 100644 DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h delete mode 100644 DashWallet/Sources/UI/Onboarding/Stubs/DWBalanceDisplayOptionsStub.h delete mode 100644 DashWallet/Sources/UI/Onboarding/Stubs/DWBalanceDisplayOptionsStub.m diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 838cf865c..3e4308336 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -203,7 +203,6 @@ 2A6300492328EA8900827825 /* DWLockActionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6300482328EA8900827825 /* DWLockActionButton.m */; }; 2A63004E2328F37C00827825 /* DWLockScreenViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A63004D2328F37C00827825 /* DWLockScreenViewController.m */; }; 2A6688FD24BCB739008E10F0 /* DWDPTxItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6688FC24BCB739008E10F0 /* DWDPTxItemView.m */; }; - 2A6B8E542387056200A2E5FA /* DWBalanceDisplayOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6B8E532387056200A2E5FA /* DWBalanceDisplayOptions.m */; }; 2A741DC223639A9700840ADF /* TodayExtension.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A741DC123639A9700840ADF /* TodayExtension.storyboard */; }; 2A741DC323639C7E00840ADF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 757E09971ADB8EEB006FD352 /* Localizable.strings */; }; 2A741DC62363A06000840ADF /* DWURLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A741DC52363A06000840ADF /* DWURLParser.m */; }; @@ -305,7 +304,6 @@ 2A913E7123A2667E006A2A59 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A913E7023A2667E006A2A59 /* Onboarding.storyboard */; }; 2A913E7F23A30609006A2A59 /* DWRootModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E7E23A30609006A2A59 /* DWRootModelStub.m */; }; 2A913E8223A30623006A2A59 /* DWHomeModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8123A30623006A2A59 /* DWHomeModelStub.m */; }; - 2A913E8923A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8823A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.m */; }; 2A913E8C23A3134E006A2A59 /* UIView+DWEmbedding.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8B23A3134E006A2A59 /* UIView+DWEmbedding.m */; }; 2A913E9023A31713006A2A59 /* UIViewController+DWEmbedding.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8F23A31713006A2A59 /* UIViewController+DWEmbedding.m */; }; 2A913E9523A3F75F006A2A59 /* DWInitialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E9423A3F75F006A2A59 /* DWInitialViewController.m */; }; @@ -724,7 +722,6 @@ C9F451FD2A0CC4A300825057 /* BadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451FC2A0CC4A300825057 /* BadgeView.swift */; }; C9F452012A0CE6C900825057 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452002A0CE6C900825057 /* HomeView.swift */; }; C9F452032A0CEB5800825057 /* TxListEmptyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452022A0CEB5800825057 /* TxListEmptyTableViewCell.swift */; }; - C9F452052A0CF4E500825057 /* HomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452042A0CF4E500825057 /* HomeModel.swift */; }; C9F452082A11F28600825057 /* SyncModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452072A11F28600825057 /* SyncModel.swift */; }; C9F4520B2A1209D100825057 /* HomeHeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F4520A2A1209D100825057 /* HomeHeaderModel.swift */; }; CC5F88E358330F8EE192D5BE /* libPods-DashWalletScreenshotsUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CDD4C961516ED20BC9F01FA /* libPods-DashWalletScreenshotsUITests.a */; }; @@ -1123,8 +1120,6 @@ 2A6688FA24BCB3AC008E10F0 /* DWDPBasicItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPBasicItem.h; sourceTree = ""; }; 2A6688FB24BCB739008E10F0 /* DWDPTxItemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPTxItemView.h; sourceTree = ""; }; 2A6688FC24BCB739008E10F0 /* DWDPTxItemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPTxItemView.m; sourceTree = ""; }; - 2A6B8E522387056200A2E5FA /* DWBalanceDisplayOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBalanceDisplayOptions.h; sourceTree = ""; }; - 2A6B8E532387056200A2E5FA /* DWBalanceDisplayOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBalanceDisplayOptions.m; sourceTree = ""; }; 2A741DC02363993600840ADF /* DWTodayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWTodayViewController.h; sourceTree = ""; }; 2A741DC123639A9700840ADF /* TodayExtension.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = TodayExtension.storyboard; sourceTree = ""; }; 2A741DC42363A06000840ADF /* DWURLParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWURLParser.h; sourceTree = ""; }; @@ -1328,7 +1323,6 @@ 2A913E6D23A26663006A2A59 /* DWOnboardingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWOnboardingViewController.h; sourceTree = ""; }; 2A913E6E23A26663006A2A59 /* DWOnboardingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWOnboardingViewController.m; sourceTree = ""; }; 2A913E7023A2667E006A2A59 /* Onboarding.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = ""; }; - 2A913E7323A2EB0E006A2A59 /* DWBalanceProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBalanceProtocol.h; sourceTree = ""; }; 2A913E7723A2F2B6006A2A59 /* DWTxDisplayModeProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTxDisplayModeProtocol.h; sourceTree = ""; }; 2A913E7A23A2F7ED006A2A59 /* DWHomeProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeProtocol.h; sourceTree = ""; }; 2A913E7B23A2FF4A006A2A59 /* DWRootProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRootProtocol.h; sourceTree = ""; }; @@ -1336,9 +1330,6 @@ 2A913E7E23A30609006A2A59 /* DWRootModelStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWRootModelStub.m; sourceTree = ""; }; 2A913E8023A30623006A2A59 /* DWHomeModelStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeModelStub.h; sourceTree = ""; }; 2A913E8123A30623006A2A59 /* DWHomeModelStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWHomeModelStub.m; sourceTree = ""; }; - 2A913E8623A30C2D006A2A59 /* DWBalanceDisplayOptionsProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBalanceDisplayOptionsProtocol.h; sourceTree = ""; }; - 2A913E8723A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBalanceDisplayOptionsStub.h; sourceTree = ""; }; - 2A913E8823A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBalanceDisplayOptionsStub.m; sourceTree = ""; }; 2A913E8A23A3134E006A2A59 /* UIView+DWEmbedding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+DWEmbedding.h"; sourceTree = ""; }; 2A913E8B23A3134E006A2A59 /* UIView+DWEmbedding.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+DWEmbedding.m"; sourceTree = ""; }; 2A913E8E23A31713006A2A59 /* UIViewController+DWEmbedding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+DWEmbedding.h"; sourceTree = ""; }; @@ -1988,7 +1979,6 @@ C9F451FC2A0CC4A300825057 /* BadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeView.swift; sourceTree = ""; }; C9F452002A0CE6C900825057 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; C9F452022A0CEB5800825057 /* TxListEmptyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxListEmptyTableViewCell.swift; sourceTree = ""; }; - C9F452042A0CF4E500825057 /* HomeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeModel.swift; sourceTree = ""; }; C9F452072A11F28600825057 /* SyncModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncModel.swift; sourceTree = ""; }; C9F4520A2A1209D100825057 /* HomeHeaderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeHeaderModel.swift; sourceTree = ""; }; CE02413EF0C60B1D1EDE6457 /* Pods-WatchApp Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchApp Extension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WatchApp Extension/Pods-WatchApp Extension.release.xcconfig"; sourceTree = ""; }; @@ -2650,10 +2640,7 @@ 2A913EA123A799D9006A2A59 /* Transactions */, 2A4E533622F023AB00E5168A /* DWHomeModel.h */, 2A4E533722F023AB00E5168A /* DWHomeModel.m */, - C9F452042A0CF4E500825057 /* HomeModel.swift */, 47081198298CF57D003FCA3D /* TransactionListDataSource.swift */, - 2A6B8E522387056200A2E5FA /* DWBalanceDisplayOptions.h */, - 2A6B8E532387056200A2E5FA /* DWBalanceDisplayOptions.m */, 2A56EF0424193AEB002C32F3 /* DWDashPayModel.h */, 2A56EF0524193AEB002C32F3 /* DWDashPayModel.m */, 2AFF01D9243F4559003718DC /* DWDPRegistrationStatus.h */, @@ -3593,8 +3580,6 @@ 2A913E7223A2EAEF006A2A59 /* Protocols */ = { isa = PBXGroup; children = ( - 2A913E8623A30C2D006A2A59 /* DWBalanceDisplayOptionsProtocol.h */, - 2A913E7323A2EB0E006A2A59 /* DWBalanceProtocol.h */, 2A913E7A23A2F7ED006A2A59 /* DWHomeProtocol.h */, 2A913E7723A2F2B6006A2A59 /* DWTxDisplayModeProtocol.h */, 2A56EF0724193BA9002C32F3 /* DWDashPayProtocol.h */, @@ -3605,8 +3590,6 @@ 2A913E7C23A305E5006A2A59 /* Stubs */ = { isa = PBXGroup; children = ( - 2A913E8723A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.h */, - 2A913E8823A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.m */, 2A913E8023A30623006A2A59 /* DWHomeModelStub.h */, 2A913E8123A30623006A2A59 /* DWHomeModelStub.m */, 2AB3415C23A8133A004E37A7 /* DWPayModelStub.h */, @@ -6465,7 +6448,6 @@ 478C982F294305D800FAA0F0 /* ActivePaymentMethodView.swift in Sources */, 47CDEEC72949DC38008AE06D /* BasicInfoController.swift in Sources */, 2AD1CEA822E0C8C900C99324 /* DWPreviewSeedPhraseModel.m in Sources */, - 2A913E8923A30DA8006A2A59 /* DWBalanceDisplayOptionsStub.m in Sources */, 2A0C69CD23142F90001B8C90 /* DWModalPresentationAnimation.m in Sources */, 110C679A29227948006B580C /* UINavigationController+CrowdNode.swift in Sources */, 2A8B9E7A2302E67400FF8653 /* DWReceiveModel.m in Sources */, @@ -6763,7 +6745,6 @@ 2AC92C841FEB0A6D008CAEE0 /* DWQRScanViewController.m in Sources */, 2A9FFE802230FF4600956D5F /* DWSelectorFormTableViewCell.m in Sources */, 2A3CCEF9242BB1B900300AF8 /* DWRegistrationCompletedViewController.m in Sources */, - C9F452052A0CF4E500825057 /* HomeModel.swift in Sources */, 2ADC9D712462D4AD001D7C0D /* DWUserProfileHeaderView.m in Sources */, 47305872295C971F004641DA /* UIViewController+AlertPresenting.swift in Sources */, 2A9CEBB522E1EAC900A50237 /* DWTabBarView.m in Sources */, @@ -6859,7 +6840,6 @@ 2A8B9E5622FEDF2900FF8653 /* DWControllerCollectionView.m in Sources */, 2A9FFDF52230FF1A00956D5F /* SFSafariViewController+DashWallet.m in Sources */, 2AD6E54D2487CE0100B52F14 /* DWContactsDataSourceObject.m in Sources */, - 2A6B8E542387056200A2E5FA /* DWBalanceDisplayOptions.m in Sources */, 474C7218298A422500475CA6 /* TransactionItemView.swift in Sources */, 474C7211298A1A9500475CA6 /* UIView+Reuse.swift in Sources */, 2A9FFE072230FF2B00956D5F /* DWUpholdClient.m in Sources */, diff --git a/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift b/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift index cdf26c3ac..12efa2d2b 100644 --- a/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift +++ b/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift @@ -68,6 +68,10 @@ class SyncingActivityMonitor: NSObject, NetworkReachabilityHandling { DWGlobalOptions.sharedInstance().isResyncingWallet = false } + guard oldValue != state else { + return + } + NotificationCenter.default.post(name: .syncStateChangedNotification, object: nil, userInfo: [ kSyncStateChangedFromStateKey: oldValue, diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController.m b/DashWallet/Sources/UI/Home/DWHomeViewController.m index 0acea131c..3c78e18e7 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController.m @@ -17,7 +17,6 @@ #import "DWHomeViewController.h" -#import "DWBalanceDisplayOptionsProtocol.h" #import "DWEnvironment.h" #import "DWGlobalOptions.h" #import "DWHomeModel.h" @@ -97,7 +96,7 @@ - (void)viewDidAppear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; - [self.model.balanceDisplayOptions hideBalanceIfNeeded]; + [self.view hideBalanceIfNeeded]; } #pragma mark - DWHomeViewDelegate diff --git a/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.h b/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.h deleted file mode 100644 index 637b0d44a..000000000 --- a/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.h +++ /dev/null @@ -1,28 +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 - -#import "DWBalanceDisplayOptionsProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWBalanceDisplayOptions : NSObject - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.m b/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.m deleted file mode 100644 index ecde9e69d..000000000 --- a/DashWallet/Sources/UI/Home/Models/DWBalanceDisplayOptions.m +++ /dev/null @@ -1,48 +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 "DWBalanceDisplayOptions.h" - -#import "DWGlobalOptions.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation DWBalanceDisplayOptions - -@synthesize balanceHidden = _balanceHidden; - -- (instancetype)init { - self = [super init]; - if (self) { - _balanceHidden = [DWGlobalOptions sharedInstance].balanceHidden; - } - return self; -} - -- (BOOL)balanceHidden { - return _balanceHidden; -} - -- (void)hideBalanceIfNeeded { - if ([DWGlobalOptions sharedInstance].balanceHidden) { - self.balanceHidden = YES; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index fd46ff1e6..5ef9497fd 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -23,7 +23,6 @@ #import #import "AppDelegate.h" -#import "DWBalanceDisplayOptions.h" #import "DWDashPayConstants.h" #import "DWDashPayContactsUpdater.h" #import "DWDashPayModel.h" @@ -59,7 +58,6 @@ @interface DWHomeModel () - -NS_ASSUME_NONNULL_BEGIN - -@protocol DWBalanceDisplayOptionsProtocol - -@property (nonatomic, assign) BOOL balanceHidden; - -- (void)hideBalanceIfNeeded; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h deleted file mode 100644 index c7576f138..000000000 --- a/DashWallet/Sources/UI/Home/Protocols/DWBalanceProtocol.h +++ /dev/null @@ -1,32 +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 - -#import "DWBalanceDisplayOptionsProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWBalanceModel; - -@protocol DWBalanceProtocol - -@property (readonly, nonatomic, strong) id balanceDisplayOptions; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h index 0c8a0e1ac..0f1f90504 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h @@ -17,7 +17,6 @@ #import -#import "DWBalanceProtocol.h" #import "DWDashPayProtocol.h" #import "DWTxDisplayModeProtocol.h" @@ -43,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)homeModelWantToReloadShortcuts:(id)model; @end -@protocol DWHomeProtocol +@protocol DWHomeProtocol @property (nullable, nonatomic, weak) id updatesObserver; diff --git a/DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift b/DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift index ee4b2be8c..551da4ae3 100644 --- a/DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift +++ b/DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift @@ -28,6 +28,8 @@ protocol SyncModel { var progressDidChange: ((Double) -> ())? { get set } var progress: Double { get } + + func forceStartSyncingActivity() } // MARK: - SyncModelImpl diff --git a/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift b/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift index e922726e5..cfe50b6e5 100644 --- a/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift +++ b/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift @@ -128,6 +128,8 @@ final class SyncView: UIView { @IBAction func retryButtonAction(_ sender: Any) { + model.forceStartSyncingActivity() + delegate?.syncViewRetryButtonAction(self) } diff --git a/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift b/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift index e8637f590..c6cfd08b0 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift @@ -18,12 +18,35 @@ import Foundation final class BalanceModel { - var value: UInt64 = 0 - + private(set) var state: SyncingActivityMonitor.State + + private(set) var value: UInt64 = 0 + var isBalanceHidden: Bool + + var balanceDidChange: (() -> ())? + init() { + isBalanceHidden = DWGlobalOptions.sharedInstance().balanceHidden + state = SyncingActivityMonitor.shared.state + + SyncingActivityMonitor.shared.add(observer: self) + reloadBalance() + + NotificationCenter.default.addObserver(self, selector: #selector(self.applicationWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) } + func hideBalanceIfNeeded() { + if DWGlobalOptions.sharedInstance().balanceHidden { + isBalanceHidden = true + } + } + + @objc + func applicationWillEnterForeground(_ notification: NSNotification) { + hideBalanceIfNeeded() + } + private func reloadBalance() { let balanceValue = DWEnvironment.sharedInstance().currentWallet.balance @@ -43,7 +66,35 @@ final class BalanceModel { options.balanceChangedDate = Date() } - options.userHasBalance = balanceValue > 0; + options.userHasBalance = balanceValue > 0 + + balanceDidChange?() + } + + deinit { + NotificationCenter.default.removeObserver(self) + SyncingActivityMonitor.shared.remove(observer: self) + } +} + +extension BalanceModel: BalanceViewDataSource { + var mainAmountString: String { + value.formattedDashAmount + } + + var supplementaryAmountString: String { + fiatAmountString() + } +} + +extension BalanceModel: SyncingActivityMonitorObserver { + func syncingActivityMonitorProgressDidChange(_ progress: Double) { + // NOP + } + + func syncingActivityMonitorStateDidChange(previousState: SyncingActivityMonitor.State, state: SyncingActivityMonitor.State) { + self.state = state + reloadBalance() } } @@ -52,11 +103,8 @@ extension BalanceModel { NSAttributedString.dashAttributedString(for: value, tintColor: tintColor, font: font) } - func mainAmountString() -> String { - value.formattedDashAmount - } - func fiatAmountString() -> String { CurrencyExchanger.shared.fiatAmountString(for: value.dashAmount) } } + diff --git a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift index 80f214ca2..1e7606268 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift @@ -22,13 +22,6 @@ import Foundation @objc(DWHomeBalanceViewDelegate) protocol HomeBalanceViewDelegate: AnyObject { func balanceView(_ view: HomeBalanceView, balanceLongPressAction sender: UIControl) - func balanceViewDidToggleBalanceVisibility(_ view: HomeBalanceView) -} - -// MARK: - HomeBalanceViewDataSource - -protocol HomeBalanceViewDataSource: BalanceViewDataSource { - var isBalanceHidden: Bool { get } } // MARK: - HomeBalanceViewState @@ -50,14 +43,6 @@ final class HomeBalanceView: UIView { @IBOutlet private var amountsView: UIView! @IBOutlet private var balanceView: BalanceView! - weak var dataSource: HomeBalanceViewDataSource? { - didSet { - balanceView.dataSource = dataSource - reloadView() - reloadData() - } - } - weak var delegate: HomeBalanceViewDelegate? var state: HomeBalanceViewState = .default { @@ -67,25 +52,32 @@ final class HomeBalanceView: UIView { } private var isBalanceHidden: Bool { - dataSource?.isBalanceHidden ?? false + model.isBalanceHidden } private let model = BalanceModel() + override init(frame: CGRect) { super.init(frame: frame) + commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) + commonInit() } - @objc + func hideBalanceIfNeeded() { + model.hideBalanceIfNeeded() + hideBalance(model.isBalanceHidden) + } + func reloadView() { var titleString = "" - let isBalanceHidden = dataSource?.isBalanceHidden ?? false + let isBalanceHidden = model.isBalanceHidden if !isBalanceHidden && state == .syncing { titleString = NSLocalizedString("Syncing Balance", comment: "") @@ -107,12 +99,13 @@ final class HomeBalanceView: UIView { hideBalance(isBalanceHidden) } - @objc public func reloadData() { balanceView.reloadData() } private func commonInit() { + + Bundle.main.loadNibNamed("HomeBalanceView", owner: self, options: nil) contentView.translatesAutoresizingMaskIntoConstraints = false addSubview(contentView) @@ -145,7 +138,8 @@ final class HomeBalanceView: UIView { balanceButton.addGestureRecognizer(recognizer) balanceView.tint = .white - + balanceView.dataSource = model + let isBalanceHidden = isBalanceHidden hidingView.alpha = isBalanceHidden ? 1.0 : 0.0 amountsView.alpha = isBalanceHidden ? 0.0 : 1.0 @@ -153,6 +147,14 @@ final class HomeBalanceView: UIView { NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChangeNotification(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) + + reloadView() + reloadData() + + model.balanceDidChange = { [weak self] in + self?.reloadView() + self?.reloadData() + } } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -163,7 +165,8 @@ final class HomeBalanceView: UIView { @IBAction private func balanceButtonAction(_ sender: UIControl) { - delegate?.balanceViewDidToggleBalanceVisibility(self) + model.isBalanceHidden.toggle() + reloadView() } @objc @@ -176,7 +179,6 @@ final class HomeBalanceView: UIView { reloadData() } - @objc func hideBalance(_ hidden: Bool) { let animated = window != nil let isAlreadyHidden = amountsView.alpha == 0 diff --git a/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift index 4f2c7d447..8ec6700d7 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift @@ -25,7 +25,6 @@ protocol HomeHeaderViewDelegate: AnyObject { func homeHeaderView(_ headerView: HomeHeaderView, profileButtonAction sender: UIControl) func homeHeaderView(_ headerView: HomeHeaderView, retrySyncButtonAction sender: UIView) func homeHeaderViewDidUpdateContents(_ headerView: HomeHeaderView) - func homeHeaderViewDidToggleBalanceVisibility(_ headerView: HomeHeaderView) } // MARK: - HomeHeaderView @@ -41,15 +40,6 @@ final class HomeHeaderView: UIView { private(set) var shortcutsView: ShortcutsView! private(set) var stackView: UIStackView! - weak var balanceDataSource: HomeBalanceViewDataSource? { - get { - balanceView.dataSource - } - set { - balanceView.dataSource = newValue - } - } - weak var shortcutsDelegate: ShortcutsActionDelegate? { get { shortcutsView.actionDelegate @@ -190,10 +180,6 @@ extension HomeHeaderView: HomeBalanceViewDelegate { let action = ShortcutAction(type: .localCurrency) shortcutsDelegate?.shortcutsView(view, didSelectAction: action, sender: sender) } - - func balanceViewDidToggleBalanceVisibility(_ view: HomeBalanceView) { - delegate?.homeHeaderViewDidToggleBalanceVisibility(self) - } } // MARK: ShortcutsViewDelegate diff --git a/DashWallet/Sources/UI/Home/Views/HomeView.swift b/DashWallet/Sources/UI/Home/Views/HomeView.swift index bd1638d4f..78fb68a51 100644 --- a/DashWallet/Sources/UI/Home/Views/HomeView.swift +++ b/DashWallet/Sources/UI/Home/Views/HomeView.swift @@ -70,6 +70,11 @@ final class HomeView: UIView, DWHomeModelUpdatesObserver, DWDPRegistrationErrorR setupView() } + @objc + func hideBalanceIfNeeded() { + headerView?.balanceView.hideBalanceIfNeeded() + } + override func layoutSubviews() { super.layoutSubviews() @@ -89,7 +94,6 @@ final class HomeView: UIView, DWHomeModelUpdatesObserver, DWDPRegistrationErrorR backgroundColor = UIColor.dw_secondaryBackground() headerView = HomeHeaderView(frame: CGRect.zero) - headerView.balanceDataSource = self headerView.delegate = self topOverscrollView = UIView(frame: CGRect.zero) @@ -145,6 +149,7 @@ final class HomeView: UIView, DWHomeModelUpdatesObserver, DWDPRegistrationErrorR tableView.reloadData() headerView.reloadBalance() + reloadShortcuts() } func homeModel(_ model: DWHomeProtocol, didReceiveNewIncomingTransaction transaction: DSTransaction) { @@ -153,6 +158,7 @@ final class HomeView: UIView, DWHomeModelUpdatesObserver, DWDPRegistrationErrorR func homeModelDidChangeInnerModels(_ model: DWHomeProtocol) { headerView.reloadBalance() + reloadShortcuts() } func homeModelWant(toReloadShortcuts model: DWHomeProtocol) { @@ -184,11 +190,6 @@ extension HomeView: HomeHeaderViewDelegate { model?.retrySyncing() } - func homeHeaderViewDidToggleBalanceVisibility(_ headerView: HomeHeaderView) { - model?.balanceDisplayOptions.balanceHidden.toggle() - headerView.reloadBalance() - } - func homeHeaderViewDidUpdateContents(_ view: HomeHeaderView) { setNeedsLayout() } @@ -264,18 +265,3 @@ extension HomeView: UITableViewDataSource, UITableViewDelegate { } -// MARK: HomeBalanceViewDataSource - -extension HomeView: HomeBalanceViewDataSource { - var isBalanceHidden: Bool { - model?.balanceDisplayOptions.balanceHidden ?? false - } - - var mainAmountString: String { - model?.balanceModel?.mainAmountString() ?? "0" - } - - var supplementaryAmountString: String { - model?.balanceModel?.fiatAmountString() ?? "Not available" - } -} diff --git a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift index fba546172..93bce5890 100644 --- a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift +++ b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift @@ -121,7 +121,6 @@ class ShortcutsView: UIView { contentView.widthAnchor.constraint(equalTo: widthAnchor), ]) - collectionView.layer.cornerRadius = 8 collectionView.layer.masksToBounds = true @@ -146,7 +145,9 @@ class ShortcutsView: UIView { } private func updateCellSizeForContentSizeCategory(_ contentSizeCategory: UIContentSizeCategory, initialSetup: Bool) { - let cellSize = cellSize(for: contentSizeCategory) + var cellSize = cellSize(for: contentSizeCategory) + cellSize.height = ceil(cellSize.height) // This fixes the autolayout issue when the size of the cell is higher than the collection view itself + collectionViewHeightConstraint.constant = cellSize.height setNeedsUpdateConstraints() diff --git a/DashWallet/Sources/UI/Main/DWMainTabbarViewController.m b/DashWallet/Sources/UI/Main/DWMainTabbarViewController.m index 09567782e..97b477956 100644 --- a/DashWallet/Sources/UI/Main/DWMainTabbarViewController.m +++ b/DashWallet/Sources/UI/Main/DWMainTabbarViewController.m @@ -269,7 +269,7 @@ - (DWNavigationController *)contactsNavigationController { - (DWNavigationController *)menuNavigationController { if (!_menuNavigationController) { DWMainMenuViewController *menuController = - [[DWMainMenuViewController alloc] initWithBalanceDisplayOptions:self.homeModel.balanceDisplayOptions]; + [[DWMainMenuViewController alloc] init]; menuController.delegate = self; _menuNavigationController = [[DWNavigationController alloc] initWithRootViewController:menuController]; diff --git a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h index 04e6843b5..26a3188d1 100644 --- a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h +++ b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h @@ -23,7 +23,6 @@ NS_ASSUME_NONNULL_BEGIN @class DWMainMenuViewController; -@protocol DWBalanceDisplayOptionsProtocol; @protocol DWMainMenuViewControllerDelegate @@ -37,11 +36,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, weak) id delegate; -- (instancetype)initWithBalanceDisplayOptions:(id)balanceDisplayOptions; - - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; - (nullable instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; -- (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end diff --git a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m index 89b6e571a..0bf0baf2e 100644 --- a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m +++ b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m @@ -39,7 +39,6 @@ @interface DWMainMenuViewController () @property (nonatomic, strong) DWMainMenuContentView *view; -@property (nonatomic, strong) id balanceDisplayOptions; @end @@ -47,11 +46,9 @@ @implementation DWMainMenuViewController @dynamic view; -- (instancetype)initWithBalanceDisplayOptions:(id)balanceDisplayOptions { +- (instancetype)init { self = [super initWithNibName:nil bundle:nil]; if (self) { - _balanceDisplayOptions = balanceDisplayOptions; - self.title = NSLocalizedString(@"More", nil); } return self; @@ -108,7 +105,7 @@ - (void)mainMenuContentView:(DWMainMenuContentView *)view didSelectMenuItem:(id< break; } case DWMainMenuItemType_Security: { - DWSecurityMenuViewController *controller = [[DWSecurityMenuViewController alloc] initWithBalanceDisplayOptions:self.balanceDisplayOptions]; + DWSecurityMenuViewController *controller = [[DWSecurityMenuViewController alloc] init]; controller.delegate = self.delegate; [self.navigationController pushViewController:controller animated:YES]; diff --git a/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuModel.h b/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuModel.h index 7fc283333..def739bc0 100644 --- a/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuModel.h +++ b/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuModel.h @@ -21,8 +21,6 @@ NS_ASSUME_NONNULL_BEGIN -@protocol DWBalanceDisplayOptionsProtocol; - @interface DWSecurityMenuModel : NSObject @property (readonly, assign, nonatomic) BOOL hasTouchID; @@ -35,8 +33,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)setBiometricsEnabled:(BOOL)enabled completion:(void (^)(BOOL success))completion; -- (instancetype)initWithBalanceDisplayOptions:(id)balanceDisplayOptions; -- (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end diff --git a/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuModel.m b/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuModel.m index 0713b851b..b687f0b87 100644 --- a/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuModel.m +++ b/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuModel.m @@ -21,7 +21,6 @@ #import #import "DWAdvancedSecurityModel.h" -#import "DWBalanceDisplayOptionsProtocol.h" #import "DWBiometricAuthModel.h" #import "DWGlobalOptions.h" @@ -53,17 +52,15 @@ - (instancetype)initWithTitle:(NSString *)title value:(uint64_t)value { @interface DWSecurityMenuModel () @property (assign, nonatomic) BOOL biometricsEnabled; -@property (readonly, strong, nonatomic) id balanceDisplayOptions; @property (readonly, nonatomic, strong) DWBiometricAuthModel *biometricAuthModel; @end @implementation DWSecurityMenuModel -- (instancetype)initWithBalanceDisplayOptions:(id)balanceDisplayOptions { +- (instancetype)init { self = [super init]; if (self) { - _balanceDisplayOptions = balanceDisplayOptions; _biometricAuthModel = [[DWBiometricAuthModel alloc] init]; @@ -91,7 +88,6 @@ - (BOOL)balanceHidden { - (void)setBalanceHidden:(BOOL)balanceHidden { [DWGlobalOptions sharedInstance].balanceHidden = balanceHidden; - self.balanceDisplayOptions.balanceHidden = balanceHidden; } - (void)changePinContinueBlock:(void (^)(BOOL allowed))continueBlock { diff --git a/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuViewController.h b/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuViewController.h index b1bec840a..90e86e7ad 100644 --- a/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuViewController.h +++ b/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuViewController.h @@ -21,17 +21,15 @@ NS_ASSUME_NONNULL_BEGIN -@protocol DWBalanceDisplayOptionsProtocol; @interface DWSecurityMenuViewController : UIViewController @property (nullable, nonatomic, weak) id delegate; -- (instancetype)initWithBalanceDisplayOptions:(id)balanceDisplayOptions; +- (instancetype)init; - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; - (nullable instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; -- (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end diff --git a/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuViewController.m b/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuViewController.m index 6fd8024c9..bcb78e8cc 100644 --- a/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuViewController.m +++ b/DashWallet/Sources/UI/Menu/Security/DWSecurityMenuViewController.m @@ -37,7 +37,6 @@ @interface DWSecurityMenuViewController () -@property (readonly, nonatomic, strong) id balanceDisplayOptions; @property (null_resettable, nonatomic, strong) DWSecurityMenuModel *model; @property (nonatomic, strong) DWFormTableViewController *formController; @@ -45,11 +44,10 @@ @interface DWSecurityMenuViewController () )balanceDisplayOptions { +- (instancetype)init { self = [super initWithNibName:nil bundle:nil]; if (self) { - _balanceDisplayOptions = balanceDisplayOptions; - + self.title = NSLocalizedString(@"Security", nil); self.hidesBottomBarWhenPushed = YES; } @@ -58,7 +56,7 @@ - (instancetype)initWithBalanceDisplayOptions:(id - -#import "DWBalanceDisplayOptionsProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWBalanceDisplayOptionsStub : NSObject - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWBalanceDisplayOptionsStub.m b/DashWallet/Sources/UI/Onboarding/Stubs/DWBalanceDisplayOptionsStub.m deleted file mode 100644 index 122221534..000000000 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWBalanceDisplayOptionsStub.m +++ /dev/null @@ -1,39 +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 "DWBalanceDisplayOptionsStub.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation DWBalanceDisplayOptionsStub - -@synthesize balanceHidden = _balanceHidden; - -- (instancetype)init { - self = [super init]; - if (self) { - _balanceHidden = NO; - } - return self; -} - -- (void)hideBalanceIfNeeded { -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m index dfca3af0b..aa8de2517 100644 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m +++ b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m @@ -17,7 +17,6 @@ #import "DWHomeModelStub.h" -#import "DWBalanceDisplayOptionsStub.h" #import "DWDashPayModel.h" #import "DWEnvironment.h" #import "DWPayModelStub.h" @@ -34,15 +33,12 @@ @interface DWHomeModelStub () @property (readonly, nonatomic, strong) DWTransactionListDataProviderStub *dataProvider; -@property (nullable, nonatomic, strong) DWBalanceModel *balanceModel; - @property (readonly, nonatomic, strong) DWTransactionListDataSource *dataSource; @end @implementation DWHomeModelStub -@synthesize balanceDisplayOptions = _balanceDisplayOptions; @synthesize displayMode = _displayMode; @synthesize payModel = _payModel; @synthesize receiveModel = _receiveModel; @@ -63,7 +59,6 @@ - (instancetype)init { _dashPayModel = [[DWDashPayModel alloc] init]; // TODO: DP consider using stub #endif /* DASHPAY_ENABLED */ _payModel = [[DWPayModelStub alloc] init]; - _balanceDisplayOptions = [[DWBalanceDisplayOptionsStub alloc] init]; _allowedToShowReclassifyYourTransactions = NO; [self updateBalance]; @@ -142,7 +137,6 @@ - (void)walletBalanceDidChangeNotification { } - (void)updateBalance { - self.balanceModel = [[DWBalanceModel alloc] initWith:42 * DUFFS]; } - (void)reloadTxDataSource { @@ -152,18 +146,6 @@ - (void)reloadTxDataSource { [self.updatesObserver homeModel:self didUpdateDataSource:self.dataSource shouldAnimate:NO]; } -- (NSString *)supplementaryAmountString { - return [self.balanceModel fiatAmountString]; -} - -- (NSString *)mainAmountString { - return [self.balanceModel mainAmountString]; -} - -- (BOOL)isBalanceHidden { - return self.balanceDisplayOptions.balanceHidden; -} - @end NS_ASSUME_NONNULL_END From 2648f85173f8e8cd5b5d4c634d7a665f5579fe4b Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 29 May 2023 14:36:18 +0700 Subject: [PATCH 17/38] chore: Code cleanup --- DashWallet/Sources/Models/Uphold/DWUpholdAPIProvider.m | 2 +- DashWallet/Sources/UI/Home/Models/DWHomeModel.m | 2 +- DashWallet/Sources/UI/Onboarding/Stubs/DWTransactionStub.m | 2 +- DashWallet/Sources/UI/Setup/DWSetupViewController.m | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DashWallet/Sources/Models/Uphold/DWUpholdAPIProvider.m b/DashWallet/Sources/Models/Uphold/DWUpholdAPIProvider.m index efeecdecf..5bbaa5fc5 100644 --- a/DashWallet/Sources/Models/Uphold/DWUpholdAPIProvider.m +++ b/DashWallet/Sources/Models/Uphold/DWUpholdAPIProvider.m @@ -84,7 +84,7 @@ - (DWUpholdCancellationToken)upholdAuthorizedRequest:(HTTPRequest *)httpRequest #pragma mark - API Provider -static NSSet *FiatCurrencyCodes() { +static NSSet *FiatCurrencyCodes(void) { return [NSSet setWithObjects: @"ARS", @"AUD", @"BRL", @"CAD", @"DKK", @"AED", @"EUR", @"HKD", @"INR", @"ILS", @"KES", @"MXN", @"NZD", @"NOK", @"PHP", @"PLN", @"GBP", @"SGD", @"SEK", @"CHF", @"USD", @"JPY", @"CNY", nil]; diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index 5ef9497fd..a288fbd12 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWHomeModel () +@interface DWHomeModel () @property (nonatomic, strong) dispatch_queue_t queue; @property (strong, nonatomic) DSReachabilityManager *reachability; diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWTransactionStub.m b/DashWallet/Sources/UI/Onboarding/Stubs/DWTransactionStub.m index ba1df11db..7cd1b8b0c 100644 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWTransactionStub.m +++ b/DashWallet/Sources/UI/Onboarding/Stubs/DWTransactionStub.m @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN -static UInt256 RandomUInt256() { +static UInt256 RandomUInt256(void) { return ((UInt256){.u64 = { ((uint64_t)arc4random() << 32) | (uint64_t)arc4random(), ((uint64_t)arc4random() << 32) | (uint64_t)arc4random(), diff --git a/DashWallet/Sources/UI/Setup/DWSetupViewController.m b/DashWallet/Sources/UI/Setup/DWSetupViewController.m index 8b7639a6d..39039eda1 100644 --- a/DashWallet/Sources/UI/Setup/DWSetupViewController.m +++ b/DashWallet/Sources/UI/Setup/DWSetupViewController.m @@ -34,7 +34,8 @@ @interface DWSetupViewController () + DWRecoverViewControllerDelegate, + DWBackupInfoViewControllerDelegate> @property (nonatomic, assign) BOOL initialAnimationCompleted; From 50667ca0670eb86c20f1b0b30a955667e3bff9cf Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 29 May 2023 14:36:52 +0700 Subject: [PATCH 18/38] refactor: Refresh CN state from HomeModel instead of HomeViewController --- .../Sources/UI/Home/DWHomeViewController.m | 38 +------------------ .../Sources/UI/Home/Models/DWHomeModel.m | 15 ++++++++ .../UI/Home/Protocols/DWHomeProtocol.h | 1 + 3 files changed, 18 insertions(+), 36 deletions(-) diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController.m b/DashWallet/Sources/UI/Home/DWHomeViewController.m index 3c78e18e7..d1b7ce007 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController.m @@ -35,7 +35,6 @@ @interface DWHomeViewController () @property (strong, nonatomic) DWHomeView *view; -@property (nullable, nonatomic, strong) id syncStateObserver; @end @@ -46,10 +45,6 @@ @implementation DWHomeViewController - (void)dealloc { DSLog(@"☠️ %@", NSStringFromClass(self.class)); - - if (self.syncStateObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:self.syncStateObserver]; - } } - (void)loadView { @@ -90,7 +85,7 @@ - (void)viewDidAppear:(BOOL)animated { [self.model registerForPushNotifications]; [self showReclassifyYourTransactionsIfPossibleWithTransaction:self.model.allDataSource.items.firstObject]; - [self checkCrowdNodeState]; + [self.model checkCrowdNodeState]; } - (void)viewWillDisappear:(BOOL)animated { @@ -218,36 +213,7 @@ - (void)presentTransactionDetails:(DSTransaction *)transaction { [self presentViewController:nvc animated:YES completion:nil]; } -- (void)checkCrowdNodeState { - //TODO: Rewrite w/o DWSyncStateChangedNotification -// if (self.model.syncModel.state == DWSyncModelState_SyncDone) { -// [CrowdNodeObjcWrapper restoreState]; -// -// if ([CrowdNodeObjcWrapper isInterrupted]) { -// // Continue signup -// [CrowdNodeObjcWrapper continueInterrupted]; -// } -// } -// else { -// NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; -// self.syncStateObserver = [notificationCenter addObserverForName:DWSyncStateChangedNotification -// object:nil -// queue:nil -// usingBlock:^(NSNotification *note) { -// BOOL isSynced = self.model.syncModel.state == DWSyncModelState_SyncDone; -// -// if (isSynced) { -// if (self.syncStateObserver) { -// // Only need to observe once -// [[NSNotificationCenter defaultCenter] removeObserver:self.syncStateObserver]; -// self.syncStateObserver = nil; -// } -// -// [self checkCrowdNodeState]; -// } -// }]; -// } -} + @end diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index a288fbd12..e901ca15f 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -278,6 +278,18 @@ - (void)walletDidWipe { #endif /* DASHPAY_ENABLED */ } +- (void)checkCrowdNodeState { + if (SyncingActivityMonitor.shared.state == SyncingActivityMonitorStateSyncDone) { + [CrowdNodeObjcWrapper restoreState]; + + if ([CrowdNodeObjcWrapper isInterrupted]) { + // Continue signup + [CrowdNodeObjcWrapper continueInterrupted]; + } + } +} + + #pragma mark - DWShortcutsModelDataSource - (BOOL)shouldShowCreateUserNameButton { @@ -522,6 +534,7 @@ - (void)syncingActivityMonitorProgressDidChange:(double)progress {} - (void)syncingActivityMonitorStateDidChangeWithPreviousState:(enum SyncingActivityMonitorState)previousState state:(enum SyncingActivityMonitorState)state { BOOL isSynced = state == SyncingActivityMonitorStateSyncDone; + if (isSynced) { [self.dashPayModel updateUsernameStatus]; @@ -529,6 +542,8 @@ - (void)syncingActivityMonitorStateDidChangeWithPreviousState:(enum SyncingActiv [self.receiveModel updateReceivingInfo]; [[DWDashPayContactsUpdater sharedInstance] beginUpdating]; } + + [self checkCrowdNodeState]; } [self updateBalance]; diff --git a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h index 0f1f90504..0d6497628 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h @@ -69,6 +69,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)walletDidWipe; - (void)retrySyncing; +- (void)checkCrowdNodeState; @end NS_ASSUME_NONNULL_END From aea1fd9bedbc073d8cd4e2ee31c13ab599bb0797 Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 30 May 2023 16:27:39 +0700 Subject: [PATCH 19/38] refactor: Implement Tabbar Controller using UITabbarController --- DashWallet.xcodeproj/project.pbxproj | 38 +- .../SyncingActivityMonitor.swift | 2 +- ...iewController+DWSecureWalletDelegateImpl.m | 1 + .../Sources/UI/Home/DWHomeViewController.h | 9 +- .../DWHomeViewControllerDelegate.h} | 17 +- .../Home/Syncing Views/Model/SyncModel.swift | 2 +- .../Syncing Views/Sync View/SyncView.swift | 2 +- .../Home Balance View/BalanceModel.swift | 40 +- .../Home Balance View/HomeBalanceView.swift | 16 +- .../Sources/UI/Home/Views/HomeView.swift | 2 +- .../Home/Views/Shortcuts/ShortcutsView.swift | 2 +- .../UI/Main/DWMainTabbarViewController.h | 46 -- .../UI/Main/DWMainTabbarViewController.m | 377 ---------------- .../UI/Main/MainTabbarController.swift | 411 ++++++++++++++---- .../Sources/UI/Main/Views/DWPaymentsButton.m | 22 - .../Sources/UI/Main/Views/DWTabBarButton.h | 39 -- .../Sources/UI/Main/Views/DWTabBarButton.m | 84 ---- .../Sources/UI/Main/Views/DWTabBarView.h | 51 --- .../Sources/UI/Main/Views/DWTabBarView.m | 246 ----------- .../Sources/UI/Main/Views/PaymentButton.swift | 17 + .../UI/Menu/Main/DWMainMenuViewController.h | 10 +- .../Main/DWMainMenuViewControllerDelegate.h} | 19 +- .../Controllers/DWDemoAppRootViewController.m | 3 +- .../DWDemoMainTabbarViewController.m | 64 --- .../DemoMainTabbarViewController.swift | 47 ++ .../UI/Payments/PaymentsViewController.swift | 11 +- .../RootNavigation/DWAppRootViewController.m | 16 +- .../Sources/UI/Setup/DWSetupViewController.m | 1 - DashWallet/dashwallet-Bridging-Header.h | 24 +- 29 files changed, 478 insertions(+), 1141 deletions(-) rename DashWallet/Sources/UI/{Main/Views/DWPaymentsButton.h => Home/DWHomeViewControllerDelegate.h} (60%) delete mode 100644 DashWallet/Sources/UI/Main/DWMainTabbarViewController.h delete mode 100644 DashWallet/Sources/UI/Main/DWMainTabbarViewController.m delete mode 100644 DashWallet/Sources/UI/Main/Views/DWPaymentsButton.m delete mode 100644 DashWallet/Sources/UI/Main/Views/DWTabBarButton.h delete mode 100644 DashWallet/Sources/UI/Main/Views/DWTabBarButton.m delete mode 100644 DashWallet/Sources/UI/Main/Views/DWTabBarView.h delete mode 100644 DashWallet/Sources/UI/Main/Views/DWTabBarView.m rename DashWallet/Sources/UI/{Onboarding/Controllers/DWDemoMainTabbarViewController.h => Menu/Main/DWMainMenuViewControllerDelegate.h} (50%) delete mode 100644 DashWallet/Sources/UI/Onboarding/Controllers/DWDemoMainTabbarViewController.m create mode 100644 DashWallet/Sources/UI/Onboarding/Controllers/DemoMainTabbarViewController.swift diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 3e4308336..7a8fc6175 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -119,7 +119,6 @@ 2A10EB412358D2A900C38B61 /* ResetWalletInfo.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A10EB402358D2A900C38B61 /* ResetWalletInfo.storyboard */; }; 2A10EB442358D2CA00C38B61 /* DWResetWalletInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A10EB432358D2CA00C38B61 /* DWResetWalletInfoViewController.m */; }; 2A11F59F2194BD6200E7B563 /* DWDataMigrationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A11F59E2194BD6200E7B563 /* DWDataMigrationManager.m */; }; - 2A12E61923ABC7D3001CAF58 /* DWDemoMainTabbarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A12E61823ABC7D3001CAF58 /* DWDemoMainTabbarViewController.m */; }; 2A1AE78E23F4668B00179A6E /* DWUsernameHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1AE78D23F4668B00179A6E /* DWUsernameHeaderView.m */; }; 2A1AE79223F468CD00179A6E /* DWPlanetarySystemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1AE79123F468CD00179A6E /* DWPlanetarySystemView.m */; }; 2A1AF6DE23C7681B00442AF5 /* DWShortcutCollectionViewCell~ipad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A1AF6DC23C7681B00442AF5 /* DWShortcutCollectionViewCell~ipad.xib */; }; @@ -143,7 +142,6 @@ 2A2CD72522FA05DD008C7BC9 /* DWPressableButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD72422FA05DD008C7BC9 /* DWPressableButton.m */; }; 2A307CBF22E8A44200A18347 /* DWButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A307CBE22E8A44200A18347 /* DWButton.m */; }; 2A36A88B2350A05B0014DC60 /* DWBackupSeedPhraseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A36A88A2350A05B0014DC60 /* DWBackupSeedPhraseViewController.m */; }; - 2A392565234CD21300316EA6 /* DWTabBarButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A392564234CD21300316EA6 /* DWTabBarButton.m */; }; 2A392568234CFE9D00316EA6 /* NSAttributedString+DWHighlightText.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A392567234CFE9D00316EA6 /* NSAttributedString+DWHighlightText.m */; }; 2A3CCEF9242BB1B900300AF8 /* DWRegistrationCompletedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3CCEF8242BB1B900300AF8 /* DWRegistrationCompletedViewController.m */; }; 2A3CCEFC242BB1DD00300AF8 /* DWDPAvatarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3CCEFB242BB1DD00300AF8 /* DWDPAvatarView.m */; }; @@ -173,7 +171,6 @@ 2A4431E922D738C0009BAF7F /* DWSeedPhraseModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431E822D738C0009BAF7F /* DWSeedPhraseModel.m */; }; 2A4663032279DC2F0027533B /* DashWalletScreenshotsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4663022279DC2F0027533B /* DashWalletScreenshotsUITests.swift */; }; 2A46630D2279DD7D0027533B /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A46630C2279DD7D0027533B /* SnapshotHelper.swift */; }; - 2A4E1D5325056297008AC53F /* DWPaymentsButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E1D5225056297008AC53F /* DWPaymentsButton.m */; }; 2A4E531422E9F0A200E5168A /* DWStartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB231D12196E25D00A6E7E6 /* DWStartViewController.m */; }; 2A4E531522E9F0A200E5168A /* DWStartModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB231D62196E5CF00A6E7E6 /* DWStartModel.m */; }; 2A4E531A22EA382B00E5168A /* SyncView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E531922EA382B00E5168A /* SyncView.xib */; }; @@ -318,9 +315,7 @@ 2A919F9C24A4DCAD0018C9A3 /* DWDPSmallContactView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A919F9B24A4DCAD0018C9A3 /* DWDPSmallContactView.m */; }; 2A919F9F24A65CE00018C9A3 /* DWDPAmountContactView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A919F9E24A65CE00018C9A3 /* DWDPAmountContactView.m */; }; 2A951CE423D1B92C00602824 /* DWBaseContactsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A951CE323D1B92C00602824 /* DWBaseContactsModel.m */; }; - 2A9CEBA822E1D5A200A50237 /* DWMainTabbarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBA722E1D5A200A50237 /* DWMainTabbarViewController.m */; }; 2A9CEBAD22E1DA4000A50237 /* DWAppRootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBAC22E1DA4000A50237 /* DWAppRootViewController.m */; }; - 2A9CEBB522E1EAC900A50237 /* DWTabBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBB422E1EAC900A50237 /* DWTabBarView.m */; }; 2A9CEBB922E1FA1000A50237 /* DWHomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBB822E1FA1000A50237 /* DWHomeViewController.m */; }; 2A9D72AC249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9D72AB249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.m */; }; 2A9E7DC723F6928C00CDA1EE /* DWTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DC623F6928C00CDA1EE /* DWTextField.m */; }; @@ -689,6 +684,7 @@ C917024129D462C6008C034D /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C917024029D462C6008C034D /* PayViewController.swift */; }; C91E919729FBACE6003E7883 /* ExtendedPublicKeysModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91E919629FBACE6003E7883 /* ExtendedPublicKeysModel.swift */; }; C91E91AE29FFC8A1003E7883 /* ExtendedPublicKeysViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91E91AD29FFC8A1003E7883 /* ExtendedPublicKeysViewController.swift */; }; + C94946E12A25F037008A678D /* DemoMainTabbarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94946E02A25F037008A678D /* DemoMainTabbarViewController.swift */; }; C94F5E8829D3E7E30034FD57 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C94F5E8729D3E7E30034FD57 /* GoogleService-Info.plist */; }; C94F5E8A29D3FCCF0034FD57 /* ShortcutAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8929D3FCCF0034FD57 /* ShortcutAction.swift */; }; C94F5E8C29D3FEC10034FD57 /* ShortcutsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8B29D3FEC10034FD57 /* ShortcutsModel.swift */; }; @@ -961,8 +957,6 @@ 2A11F59D2194BD6200E7B563 /* DWDataMigrationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDataMigrationManager.h; sourceTree = ""; }; 2A11F59E2194BD6200E7B563 /* DWDataMigrationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDataMigrationManager.m; sourceTree = ""; }; 2A12E61623AB9B8D001CAF58 /* DWDemoDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDemoDelegate.h; sourceTree = ""; }; - 2A12E61723ABC7D3001CAF58 /* DWDemoMainTabbarViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDemoMainTabbarViewController.h; sourceTree = ""; }; - 2A12E61823ABC7D3001CAF58 /* DWDemoMainTabbarViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDemoMainTabbarViewController.m; sourceTree = ""; }; 2A12E61A23ABC8E1001CAF58 /* DWExtendedContainerViewController+DWProtected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWExtendedContainerViewController+DWProtected.h"; sourceTree = ""; }; 2A1AE78C23F4668B00179A6E /* DWUsernameHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUsernameHeaderView.h; sourceTree = ""; }; 2A1AE78D23F4668B00179A6E /* DWUsernameHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUsernameHeaderView.m; sourceTree = ""; }; @@ -1010,8 +1004,6 @@ 2A36A8892350A05B0014DC60 /* DWBackupSeedPhraseViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBackupSeedPhraseViewController.h; sourceTree = ""; }; 2A36A88A2350A05B0014DC60 /* DWBackupSeedPhraseViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBackupSeedPhraseViewController.m; sourceTree = ""; }; 2A36A88C2350A18A0014DC60 /* DWPreviewSeedPhraseViewController+DWProtected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWPreviewSeedPhraseViewController+DWProtected.h"; sourceTree = ""; }; - 2A392563234CD21300316EA6 /* DWTabBarButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTabBarButton.h; sourceTree = ""; }; - 2A392564234CD21300316EA6 /* DWTabBarButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWTabBarButton.m; sourceTree = ""; }; 2A392566234CFE9D00316EA6 /* NSAttributedString+DWHighlightText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+DWHighlightText.h"; sourceTree = ""; }; 2A392567234CFE9D00316EA6 /* NSAttributedString+DWHighlightText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+DWHighlightText.m"; sourceTree = ""; }; 2A3CCEF7242BB1B900300AF8 /* DWRegistrationCompletedViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRegistrationCompletedViewController.h; sourceTree = ""; }; @@ -1066,8 +1058,6 @@ 2A4663042279DC2F0027533B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2A46630C2279DD7D0027533B /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; 2A46630E2279DF490027533B /* DashWalletScreenshotsUITests-Briding-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DashWalletScreenshotsUITests-Briding-Header.h"; sourceTree = ""; }; - 2A4E1D5125056297008AC53F /* DWPaymentsButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPaymentsButton.h; sourceTree = ""; }; - 2A4E1D5225056297008AC53F /* DWPaymentsButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWPaymentsButton.m; sourceTree = ""; }; 2A4E531922EA382B00E5168A /* SyncView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SyncView.xib; sourceTree = ""; }; 2A4E531B22EA49FE00E5168A /* DWProgressView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWProgressView.h; sourceTree = ""; }; 2A4E531C22EA49FE00E5168A /* DWProgressView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWProgressView.m; sourceTree = ""; }; @@ -1361,12 +1351,8 @@ 2A951CE223D1B92C00602824 /* DWBaseContactsModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBaseContactsModel.h; sourceTree = ""; }; 2A951CE323D1B92C00602824 /* DWBaseContactsModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBaseContactsModel.m; sourceTree = ""; }; 2A9CEBA422E1D26500A50237 /* DWSecureWalletDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSecureWalletDelegate.h; sourceTree = ""; }; - 2A9CEBA622E1D5A200A50237 /* DWMainTabbarViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWMainTabbarViewController.h; sourceTree = ""; }; - 2A9CEBA722E1D5A200A50237 /* DWMainTabbarViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWMainTabbarViewController.m; sourceTree = ""; }; 2A9CEBAB22E1DA4000A50237 /* DWAppRootViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWAppRootViewController.h; sourceTree = ""; }; 2A9CEBAC22E1DA4000A50237 /* DWAppRootViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWAppRootViewController.m; sourceTree = ""; }; - 2A9CEBB322E1EAC900A50237 /* DWTabBarView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTabBarView.h; sourceTree = ""; }; - 2A9CEBB422E1EAC900A50237 /* DWTabBarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWTabBarView.m; sourceTree = ""; }; 2A9CEBB722E1FA1000A50237 /* DWHomeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeViewController.h; sourceTree = ""; }; 2A9CEBB822E1FA1000A50237 /* DWHomeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWHomeViewController.m; sourceTree = ""; }; 2A9D72A9249A0E7800F79CD8 /* DWDPNewIncomingRequestItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPNewIncomingRequestItem.h; sourceTree = ""; }; @@ -1945,6 +1931,9 @@ C917024029D462C6008C034D /* PayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; C91E919629FBACE6003E7883 /* ExtendedPublicKeysModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedPublicKeysModel.swift; sourceTree = ""; }; C91E91AD29FFC8A1003E7883 /* ExtendedPublicKeysViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedPublicKeysViewController.swift; sourceTree = ""; }; + C94946DE2A25EDA8008A678D /* DWHomeViewControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWHomeViewControllerDelegate.h; sourceTree = ""; }; + C94946DF2A25EE24008A678D /* DWMainMenuViewControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWMainMenuViewControllerDelegate.h; sourceTree = ""; }; + C94946E02A25F037008A678D /* DemoMainTabbarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoMainTabbarViewController.swift; sourceTree = ""; }; C94F5E8729D3E7E30034FD57 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; C94F5E8929D3FCCF0034FD57 /* ShortcutAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutAction.swift; sourceTree = ""; }; C94F5E8B29D3FEC10034FD57 /* ShortcutsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsModel.swift; sourceTree = ""; }; @@ -3114,6 +3103,7 @@ 2A7A7BB32347928B00451078 /* Views */, 2A7A7BB02347927700451078 /* DWMainMenuViewController.h */, 2A7A7BB12347927700451078 /* DWMainMenuViewController.m */, + C94946DF2A25EE24008A678D /* DWMainMenuViewControllerDelegate.h */, ); path = Main; sourceTree = ""; @@ -3687,8 +3677,6 @@ isa = PBXGroup; children = ( 2A9CEBB222E1EAAA00A50237 /* Views */, - 2A9CEBA622E1D5A200A50237 /* DWMainTabbarViewController.h */, - 2A9CEBA722E1D5A200A50237 /* DWMainTabbarViewController.m */, C9F451E42A0B986E00825057 /* MainTabbarController.swift */, ); path = Main; @@ -3707,12 +3695,6 @@ 2A9CEBB222E1EAAA00A50237 /* Views */ = { isa = PBXGroup; children = ( - 2A9CEBB322E1EAC900A50237 /* DWTabBarView.h */, - 2A9CEBB422E1EAC900A50237 /* DWTabBarView.m */, - 2A392563234CD21300316EA6 /* DWTabBarButton.h */, - 2A392564234CD21300316EA6 /* DWTabBarButton.m */, - 2A4E1D5125056297008AC53F /* DWPaymentsButton.h */, - 2A4E1D5225056297008AC53F /* DWPaymentsButton.m */, C9F451E62A0BA16400825057 /* PaymentButton.swift */, ); path = Views; @@ -3739,6 +3721,7 @@ 2A1B7DC223266C8400BA8C6A /* DWHomeViewController+DWSecureWalletDelegateImpl.m */, 2A3DC86F23972331004B3DBA /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.h */, 2A3DC87023972331004B3DBA /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.m */, + C94946DE2A25EDA8008A678D /* DWHomeViewControllerDelegate.h */, ); path = Home; sourceTree = ""; @@ -4016,8 +3999,7 @@ 2AB3417623A92978004E37A7 /* DWDemoAdvancedSecurityViewController.m */, 2AB3417123A926C9004E37A7 /* DWDemoAppRootViewController.h */, 2AB3417223A926C9004E37A7 /* DWDemoAppRootViewController.m */, - 2A12E61723ABC7D3001CAF58 /* DWDemoMainTabbarViewController.h */, - 2A12E61823ABC7D3001CAF58 /* DWDemoMainTabbarViewController.m */, + C94946E02A25F037008A678D /* DemoMainTabbarViewController.swift */, ); path = Controllers; sourceTree = ""; @@ -6441,7 +6423,6 @@ 477F50102950A55A003C7508 /* Coinbase+Error.swift in Sources */, 2A63003F2327B4BB00827825 /* DWPaymentOutput+DWView.m in Sources */, 2ACD53EE234C9D8E00650AD3 /* UIView+DWRecursiveSubview.m in Sources */, - 2A9CEBA822E1D5A200A50237 /* DWMainTabbarViewController.m in Sources */, 11860923297598B400279FCC /* AddressStatus.swift in Sources */, C91E919729FBACE6003E7883 /* ExtendedPublicKeysModel.swift in Sources */, 4751CAD5297024EA00F63AC4 /* OrderPreviewViewController.swift in Sources */, @@ -6519,7 +6500,6 @@ C9F42FAB29DC1098001BC549 /* ReceiveContentView.swift in Sources */, 47838B7528FFD1D10003E8AB /* AmountView.swift in Sources */, 4759D512292FD6F3002F20DC /* DWBasePayViewController.m in Sources */, - 2A12E61923ABC7D3001CAF58 /* DWDemoMainTabbarViewController.m in Sources */, 1193FF3629602835004EA8D7 /* CrowdNodeTransferModel.swift in Sources */, 2A44312622CCC14F009BAF7F /* DWSetPinViewController.m in Sources */, 47F4B6C7294842DF00AED4C9 /* ConfirmOrderController.swift in Sources */, @@ -6747,7 +6727,6 @@ 2A3CCEF9242BB1B900300AF8 /* DWRegistrationCompletedViewController.m in Sources */, 2ADC9D712462D4AD001D7C0D /* DWUserProfileHeaderView.m in Sources */, 47305872295C971F004641DA /* UIViewController+AlertPresenting.swift in Sources */, - 2A9CEBB522E1EAC900A50237 /* DWTabBarView.m in Sources */, 2A858A0F237EE89C0097A7B5 /* DSWatchTransactionDataObject.m in Sources */, 47FA3B0229364991008D58DC /* HTTPClient.swift in Sources */, 2A8B9E6F2302A9C200FF8653 /* DWPasteboardAddressExtractor.m in Sources */, @@ -6783,6 +6762,7 @@ 11E47BAD28EB3A7D0097CFA0 /* CrowdNode+Constants.swift in Sources */, 47F006022971B1FF0029EB10 /* CurrencyExchanger.swift in Sources */, 2A5BD5922451D68300688A8D /* DWMinLengthUsernameValidationRule.m in Sources */, + C94946E12A25F037008A678D /* DemoMainTabbarViewController.swift in Sources */, 11AE3DD62997AA36000856EE /* MessageStatus.swift in Sources */, 2AD1CEAB22E18F1800C99324 /* DWGlobalOptions.m in Sources */, 2A4430E722CBB6EC009BAF7F /* AppDelegate.m in Sources */, @@ -6848,7 +6828,6 @@ 472CEE012924AA6D00656B48 /* PointOfUseListEmptyResultsView.swift in Sources */, C94F5E8C29D3FEC10034FD57 /* ShortcutsModel.swift in Sources */, 2A6300452328D07500827825 /* DWLockPinInputView.m in Sources */, - 2A392565234CD21300316EA6 /* DWTabBarButton.m in Sources */, 2A0C69AC23125074001B8C90 /* UIView+DWHUD.m in Sources */, 2A8B9E6822FFE4CC00FF8653 /* DWPayOptionModel.m in Sources */, 2A7AF36924826681001D74F9 /* DWDPRespondedIncomingRequestObject.m in Sources */, @@ -6971,7 +6950,6 @@ 47CF46A1296540EF0067B6EE /* AccountService.swift in Sources */, 47C6E6E7291A90B3003FEDF2 /* ListHandlerView.swift in Sources */, 2A7A7BCD2347F01B00451078 /* DWSecurityMenuViewController.m in Sources */, - 2A4E1D5325056297008AC53F /* DWPaymentsButton.m in Sources */, 11BD738128E7356100A34022 /* CrowdNode.swift in Sources */, 47AE8B9F28BFAD8200490F5E /* SQLite+ExloreDash.swift in Sources */, 2A4E534B22F03A9E00E5168A /* DWFilterHeaderView.m in Sources */, diff --git a/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift b/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift index 12efa2d2b..7b80c5f2a 100644 --- a/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift +++ b/DashWallet/Sources/Application/Syncyng Activity Monitor/SyncingActivityMonitor.swift @@ -71,7 +71,7 @@ class SyncingActivityMonitor: NSObject, NetworkReachabilityHandling { guard oldValue != state else { return } - + NotificationCenter.default.post(name: .syncStateChangedNotification, object: nil, userInfo: [ kSyncStateChangedFromStateKey: oldValue, diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController+DWSecureWalletDelegateImpl.m b/DashWallet/Sources/UI/Home/DWHomeViewController+DWSecureWalletDelegateImpl.m index cd5e5424e..66764fe82 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController+DWSecureWalletDelegateImpl.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController+DWSecureWalletDelegateImpl.m @@ -16,6 +16,7 @@ // #import "DWHomeViewController+DWSecureWalletDelegateImpl.h" +#import "dashwallet-Swift.h" NS_ASSUME_NONNULL_BEGIN diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController.h b/DashWallet/Sources/UI/Home/DWHomeViewController.h index 4aed260aa..6c17b5b47 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController.h +++ b/DashWallet/Sources/UI/Home/DWHomeViewController.h @@ -16,20 +16,13 @@ // #import "DWBasePayViewController.h" - #import "DWHomeProtocol.h" #import "DWWipeDelegate.h" -#import "dashwallet-Swift.h" NS_ASSUME_NONNULL_BEGIN @class DWHomeViewController; - -@protocol DWHomeViewControllerDelegate - -- (void)showPaymentsControllerWithActivePage:(DWPaymentsViewControllerIndex)pageIndex; - -@end +@protocol DWHomeViewControllerDelegate; @interface DWHomeViewController : DWBasePayViewController diff --git a/DashWallet/Sources/UI/Main/Views/DWPaymentsButton.h b/DashWallet/Sources/UI/Home/DWHomeViewControllerDelegate.h similarity index 60% rename from DashWallet/Sources/UI/Main/Views/DWPaymentsButton.h rename to DashWallet/Sources/UI/Home/DWHomeViewControllerDelegate.h index a6021b41f..1506eee11 100644 --- a/DashWallet/Sources/UI/Main/Views/DWPaymentsButton.h +++ b/DashWallet/Sources/UI/Home/DWHomeViewControllerDelegate.h @@ -1,6 +1,6 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// +// Created by PT +// Copyright © 2023 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. @@ -15,14 +15,15 @@ // limitations under the License. // -#import +#import -NS_ASSUME_NONNULL_BEGIN +#ifndef DWHomeViewControllerDelegate_h +#define DWHomeViewControllerDelegate_h -@interface DWPaymentsButton : UIButton +@protocol DWHomeViewControllerDelegate -@property (nonatomic, assign) BOOL opened; +- (void)showPaymentsControllerWithActivePage:(NSInteger)pageIndex; @end -NS_ASSUME_NONNULL_END +#endif /* DWHomeViewControllerDelegate_h */ diff --git a/DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift b/DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift index 551da4ae3..ad77aba47 100644 --- a/DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift +++ b/DashWallet/Sources/UI/Home/Syncing Views/Model/SyncModel.swift @@ -28,7 +28,7 @@ protocol SyncModel { var progressDidChange: ((Double) -> ())? { get set } var progress: Double { get } - + func forceStartSyncingActivity() } diff --git a/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift b/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift index cfe50b6e5..b9e02e1d9 100644 --- a/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift +++ b/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift @@ -129,7 +129,7 @@ final class SyncView: UIView { @IBAction func retryButtonAction(_ sender: Any) { model.forceStartSyncingActivity() - + delegate?.syncViewRetryButtonAction(self) } diff --git a/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift b/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift index c6cfd08b0..917a94f1c 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift @@ -17,23 +17,25 @@ import Foundation +// MARK: - BalanceModel + final class BalanceModel { private(set) var state: SyncingActivityMonitor.State - + private(set) var value: UInt64 = 0 var isBalanceHidden: Bool - + var balanceDidChange: (() -> ())? - + init() { isBalanceHidden = DWGlobalOptions.sharedInstance().balanceHidden state = SyncingActivityMonitor.shared.state - + SyncingActivityMonitor.shared.add(observer: self) - + reloadBalance() - - NotificationCenter.default.addObserver(self, selector: #selector(self.applicationWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) } func hideBalanceIfNeeded() { @@ -41,42 +43,44 @@ final class BalanceModel { isBalanceHidden = true } } - + @objc func applicationWillEnterForeground(_ notification: NSNotification) { hideBalanceIfNeeded() } - + private func reloadBalance() { let balanceValue = DWEnvironment.sharedInstance().currentWallet.balance - - if (balanceValue > value && + + if balanceValue > value && value > 0 && UIApplication.shared.applicationState != .background && - SyncingActivityMonitor.shared.progress > 0.995) { + SyncingActivityMonitor.shared.progress > 0.995 { UIDevice.current.dw_playCoinSound() } value = balanceValue - + let options = DWGlobalOptions.sharedInstance() - if (balanceValue > 0 + if balanceValue > 0 && options.walletNeedsBackup - && (options.balanceChangedDate == nil)) { + && (options.balanceChangedDate == nil) { options.balanceChangedDate = Date() } options.userHasBalance = balanceValue > 0 - + balanceDidChange?() } - + deinit { NotificationCenter.default.removeObserver(self) SyncingActivityMonitor.shared.remove(observer: self) } } +// MARK: BalanceViewDataSource + extension BalanceModel: BalanceViewDataSource { var mainAmountString: String { value.formattedDashAmount @@ -87,6 +91,8 @@ extension BalanceModel: BalanceViewDataSource { } } +// MARK: SyncingActivityMonitorObserver + extension BalanceModel: SyncingActivityMonitorObserver { func syncingActivityMonitorProgressDidChange(_ progress: Double) { // NOP diff --git a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift index 1e7606268..46e6ffc6c 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift @@ -56,16 +56,16 @@ final class HomeBalanceView: UIView { } private let model = BalanceModel() - + override init(frame: CGRect) { super.init(frame: frame) - + commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) - + commonInit() } @@ -73,7 +73,7 @@ final class HomeBalanceView: UIView { model.hideBalanceIfNeeded() hideBalance(model.isBalanceHidden) } - + func reloadView() { var titleString = "" @@ -104,8 +104,6 @@ final class HomeBalanceView: UIView { } private func commonInit() { - - Bundle.main.loadNibNamed("HomeBalanceView", owner: self, options: nil) contentView.translatesAutoresizingMaskIntoConstraints = false addSubview(contentView) @@ -139,7 +137,7 @@ final class HomeBalanceView: UIView { balanceView.tint = .white balanceView.dataSource = model - + let isBalanceHidden = isBalanceHidden hidingView.alpha = isBalanceHidden ? 1.0 : 0.0 amountsView.alpha = isBalanceHidden ? 0.0 : 1.0 @@ -147,10 +145,10 @@ final class HomeBalanceView: UIView { NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChangeNotification(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) - + reloadView() reloadData() - + model.balanceDidChange = { [weak self] in self?.reloadView() self?.reloadData() diff --git a/DashWallet/Sources/UI/Home/Views/HomeView.swift b/DashWallet/Sources/UI/Home/Views/HomeView.swift index 78fb68a51..8bbe43fe1 100644 --- a/DashWallet/Sources/UI/Home/Views/HomeView.swift +++ b/DashWallet/Sources/UI/Home/Views/HomeView.swift @@ -74,7 +74,7 @@ final class HomeView: UIView, DWHomeModelUpdatesObserver, DWDPRegistrationErrorR func hideBalanceIfNeeded() { headerView?.balanceView.hideBalanceIfNeeded() } - + override func layoutSubviews() { super.layoutSubviews() diff --git a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift index 93bce5890..41406b389 100644 --- a/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift +++ b/DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift @@ -147,7 +147,7 @@ class ShortcutsView: UIView { private func updateCellSizeForContentSizeCategory(_ contentSizeCategory: UIContentSizeCategory, initialSetup: Bool) { var cellSize = cellSize(for: contentSizeCategory) cellSize.height = ceil(cellSize.height) // This fixes the autolayout issue when the size of the cell is higher than the collection view itself - + collectionViewHeightConstraint.constant = cellSize.height setNeedsUpdateConstraints() diff --git a/DashWallet/Sources/UI/Main/DWMainTabbarViewController.h b/DashWallet/Sources/UI/Main/DWMainTabbarViewController.h deleted file mode 100644 index 35c962be3..000000000 --- a/DashWallet/Sources/UI/Main/DWMainTabbarViewController.h +++ /dev/null @@ -1,46 +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 "DWExtendedContainerViewController.h" - -#import "DWDemoDelegate.h" -#import "DWHomeProtocol.h" -#import "DWWipeDelegate.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWHomeModel; - -@interface DWMainTabbarViewController : DWExtendedContainerViewController - -@property (nonatomic, strong) id homeModel; -@property (nullable, nonatomic, weak) id delegate; - -@property (nonatomic, assign) BOOL demoMode; -@property (nullable, nonatomic, weak) id demoDelegate; - -- (void)performScanQRCodeAction; -- (void)performPayToURL:(NSURL *)url; - -- (void)handleFile:(NSData *)file; - -- (void)openPaymentsScreen; -- (void)closePaymentsScreen; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Main/DWMainTabbarViewController.m b/DashWallet/Sources/UI/Main/DWMainTabbarViewController.m deleted file mode 100644 index 97b477956..000000000 --- a/DashWallet/Sources/UI/Main/DWMainTabbarViewController.m +++ /dev/null @@ -1,377 +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 "DWMainTabbarViewController.h" - -#import "DWContactsViewController.h" -#import "DWHomeViewController.h" -#import "DWMainMenuViewController.h" -#import "DWModalUserProfileViewController.h" -#import "DWTabBarView.h" -#import "DWUIKit.h" -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -static NSTimeInterval const ANIMATION_DURATION = 0.35; - -@interface DWMainTabbarViewController () - -@property (nullable, nonatomic, strong) UIView *contentView; -@property (nullable, nonatomic, strong) DWTabBarView *tabBarView; -@property (nullable, nonatomic, strong) NSLayoutConstraint *tabBarBottomConstraint; -@property (nullable, nonatomic, strong) NSLayoutConstraint *contentBottomConstraint; - -@property (null_resettable, nonatomic, strong) DWNavigationController *homeNavigationController; -@property (null_resettable, nonatomic, strong) DWNavigationController *contactsNavigationController; -@property (null_resettable, nonatomic, strong) DWNavigationController *menuNavigationController; -@property (nonatomic, weak) DWHomeViewController *homeController; - -@end - -@implementation DWMainTabbarViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - - [self setupView]; - [self setupControllers]; -} - -- (UIView *)containerView { - return self.contentView; -} - -#pragma mark - Public - -- (void)performScanQRCodeAction { - [self dismissViewControllerAnimated:false completion:nil]; - [self transitionToController:self.homeNavigationController - transitionType:DWContainerTransitionType_WithoutAnimation]; - [self.tabBarView updateSelectedTabButton:DWTabBarViewButtonType_Home]; - [self.homeController performScanQRCodeAction]; -} - -- (void)performPayToURL:(NSURL *)url { - [self dismissViewControllerAnimated:false completion:nil]; - [self transitionToController:self.homeNavigationController - transitionType:DWContainerTransitionType_WithoutAnimation]; - [self.tabBarView updateSelectedTabButton:DWTabBarViewButtonType_Home]; - [self.homeController performPayToURL:url]; -} - -- (void)handleFile:(NSData *)file { - [self dismissViewControllerAnimated:false completion:nil]; - [self transitionToController:self.homeNavigationController - transitionType:DWContainerTransitionType_WithoutAnimation]; - [self.tabBarView updateSelectedTabButton:DWTabBarViewButtonType_Home]; - [self.homeController handleFile:file]; -} - -- (void)openPaymentsScreen { - NSAssert(self.demoMode, @"Invalid usage. Should be used in Demo mode only"); - [self showPaymentsControllerWithActivePage:DWPaymentsViewControllerIndex_Pay]; -} - -- (void)closePaymentsScreen { - NSAssert(self.demoMode, @"Invalid usage. Should be used in Demo mode only"); - - [self tabBarViewDidClosePayments:self.tabBarView]; -} - -#pragma mark - DWTabBarViewDelegate - -- (void)tabBarView:(DWTabBarView *)tabBarView didTapButtonType:(DWTabBarViewButtonType)buttonType { - switch (buttonType) { - case DWTabBarViewButtonType_Home: { - if (self.currentController == self.homeNavigationController) { - return; - } - - [self transitionToController:self.homeNavigationController - transitionType:DWContainerTransitionType_WithoutAnimation]; - - break; - } - case DWTabBarViewButtonType_Contacts: { - if (self.currentController == self.contactsNavigationController) { - return; - } - - [self transitionToController:self.contactsNavigationController - transitionType:DWContainerTransitionType_WithoutAnimation]; - - break; - } - case DWTabBarViewButtonType_Others: { - if (self.currentController == self.menuNavigationController) { - return; - } - - [self transitionToController:self.menuNavigationController - transitionType:DWContainerTransitionType_WithoutAnimation]; - - break; - } - } - [tabBarView updateSelectedTabButton:buttonType]; -} - -- (void)tabBarViewDidOpenPayments:(DWTabBarView *)tabBarView { - [self showPaymentsControllerWithActivePage:DWPaymentsViewControllerIndex_None]; -} - -- (void)tabBarViewDidClosePayments:(DWTabBarView *)tabBarView { - - [self tabBarViewDidClosePayments:tabBarView completion:nil]; -} - -/// helper -- (void)tabBarViewDidClosePayments:(DWTabBarView *)tabBarView completion:(void (^_Nullable)(void))completion { - [tabBarView setPaymentsButtonOpened:NO]; - - if (![self.currentController.topController isKindOfClass:[DWPaymentsViewController class]]) { - self.tabBarView.userInteractionEnabled = YES; - - if (completion) - completion(); - return; - } - - tabBarView.userInteractionEnabled = NO; - - [self.currentController.topController dismissViewControllerAnimated:YES - completion:^{ - self.tabBarView.userInteractionEnabled = YES; - if (completion) { - completion(); - } - }]; -} - -#pragma mark - DWPaymentsViewControllerDelegate - -- (void)paymentsViewControllerWantsToImportPrivateKey:(DWPaymentsViewController *)controller { - // Make sure we enable tabbar before showing the scanner - _tabBarView.userInteractionEnabled = YES; - [_tabBarView setPaymentsButtonOpened:NO]; - - [controller dismissViewControllerAnimated:YES - completion:^{ - [self performScanQRCodeAction]; - }]; -} - -- (void)paymentsViewControllerDidCancel:(DWPaymentsViewController *)controller { - [self tabBarViewDidClosePayments:self.tabBarView]; -} - -- (void)paymentsViewControllerDidFinishPayment:(DWPaymentsViewController *)controller - contact:(nullable id)contact { - [self tabBarViewDidClosePayments:self.tabBarView - completion:^{ - if (!contact) { - return; - } - - DWModalUserProfileViewController *profile = - [[DWModalUserProfileViewController alloc] initWithItem:contact - payModel:self.homeModel.payModel - dataProvider:self.homeModel.getDataProvider]; - [self presentViewController:profile animated:YES completion:nil]; - }]; -} - -#pragma mark - DWHomeViewControllerDelegate - -- (void)homeViewControllerShowReceivePayment:(DWHomeViewController *)controller { - [self showPaymentsControllerWithActivePage:DWPaymentsViewControllerIndex_Receive]; -} - -#pragma mark - DWWipeDelegate - -- (void)didWipeWallet { - [self.delegate didWipeWallet]; -} - -#pragma mark - DWMainMenuViewControllerDelegate - -- (void)mainMenuViewControllerImportPrivateKey:(DWMainMenuViewController *)controller { - [self performScanQRCodeAction]; -} - -- (void)mainMenuViewControllerOpenHomeScreen:(DWMainMenuViewController *)controller { - [self transitionToController:self.homeNavigationController - transitionType:DWContainerTransitionType_WithoutAnimation]; - [self.tabBarView updateSelectedTabButton:DWTabBarViewButtonType_Home]; -} - -#pragma mark - UINavigationControllerDelegate - -- (void)navigationController:(UINavigationController *)navigationController - willShowViewController:(UIViewController *)viewController - animated:(BOOL)animated { - [self setTabBarHiddenAnimated:viewController.hidesBottomBarWhenPushed animated:YES]; -} - -- (void)navigationController:(UINavigationController *)navigationController - didShowViewController:(UIViewController *)viewController - animated:(BOOL)animated { - [self setTabBarHiddenAnimated:viewController.hidesBottomBarWhenPushed animated:NO]; -} - -#pragma mark - Private - -- (DWNavigationController *)homeNavigationController { - if (!_homeNavigationController) { - DWHomeViewController *homeController = [[DWHomeViewController alloc] init]; - homeController.model = self.homeModel; - homeController.delegate = self; - self.homeController = homeController; - - _homeNavigationController = [[DWNavigationController alloc] initWithRootViewController:homeController]; - _homeNavigationController.delegate = self; - } - - return _homeNavigationController; -} - -- (DWNavigationController *)contactsNavigationController { - if (!_contactsNavigationController) { - DWContactsViewController *contactsController = [[DWContactsViewController alloc] initWithPayModel:self.homeModel.payModel dataProvider:self.homeModel.getDataProvider]; - - _contactsNavigationController = [[DWNavigationController alloc] initWithRootViewController:contactsController]; - _contactsNavigationController.delegate = self; - } - - return _contactsNavigationController; -} - -- (DWNavigationController *)menuNavigationController { - if (!_menuNavigationController) { - DWMainMenuViewController *menuController = - [[DWMainMenuViewController alloc] init]; - menuController.delegate = self; - - _menuNavigationController = [[DWNavigationController alloc] initWithRootViewController:menuController]; - _menuNavigationController.delegate = self; - } - - return _menuNavigationController; -} - -- (void)setupView { - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - UIView *contentView = [[UIView alloc] initWithFrame:CGRectZero]; - contentView.translatesAutoresizingMaskIntoConstraints = NO; - contentView.backgroundColor = self.view.backgroundColor; - [self.view addSubview:contentView]; - self.contentView = contentView; - - DWTabBarView *tabBarView = [[DWTabBarView alloc] initWithFrame:CGRectZero]; - tabBarView.translatesAutoresizingMaskIntoConstraints = NO; - tabBarView.delegate = self; - [self.view addSubview:tabBarView]; - self.tabBarView = tabBarView; - - self.contentBottomConstraint = [contentView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]; - self.tabBarBottomConstraint = [tabBarView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]; - - [NSLayoutConstraint activateConstraints:@[ - [contentView.topAnchor constraintEqualToAnchor:self.view.topAnchor], - [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], - - [tabBarView.topAnchor constraintEqualToAnchor:contentView.bottomAnchor], - [tabBarView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [tabBarView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], - self.tabBarBottomConstraint, - ]]; -} - -- (void)showPaymentsControllerWithActivePage:(DWPaymentsViewControllerIndex)pageIndex { - self.tabBarView.userInteractionEnabled = NO; - [self.tabBarView setPaymentsButtonOpened:YES]; - - id homeModel = self.homeModel; - NSParameterAssert(homeModel); - id receiveModel = homeModel.receiveModel; - id payModel = homeModel.payModel; - id dataProvider = [homeModel getDataProvider]; - - DWPaymentsViewController *controller = [DWPaymentsViewController controllerWithReceiveModel:receiveModel - payModel:payModel - dataProvider:dataProvider]; - controller.delegate = self; - controller.currentState = pageIndex; - controller.demoMode = self.demoMode; - controller.demoDelegate = self.demoDelegate; - DWNavigationController *navigationController = - [[DWNavigationController alloc] initWithRootViewController:controller]; - navigationController.delegate = self; - navigationController.modalInPresentation = YES; - - if (self.demoMode) { - [self.demoDelegate presentModalController:navigationController sender:self]; - } - else { - [self.currentController.topController presentViewController:navigationController - animated:YES - completion:^{ - self.tabBarView.userInteractionEnabled = YES; - }]; - } -} - -- (void)setupControllers { - DWNavigationController *navigationController = self.homeNavigationController; - [self transitionToController:navigationController]; -} - -- (void)setTabBarHiddenAnimated:(BOOL)hidden animated:(BOOL)animated { - if (hidden) { - self.tabBarBottomConstraint.active = NO; - self.contentBottomConstraint.active = YES; - } - else { - self.contentBottomConstraint.active = NO; - self.tabBarBottomConstraint.active = YES; - } - - const CGFloat alpha = hidden ? 0.0 : 1.0; - - if (self.tabBarView.alpha == alpha) { - return; - } - - [UIView animateWithDuration:animated ? ANIMATION_DURATION : 0.0 - animations:^{ - [self.view layoutIfNeeded]; - - self.tabBarView.alpha = alpha; - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Main/MainTabbarController.swift b/DashWallet/Sources/UI/Main/MainTabbarController.swift index bbfdfc51e..ea3328991 100644 --- a/DashWallet/Sources/UI/Main/MainTabbarController.swift +++ b/DashWallet/Sources/UI/Main/MainTabbarController.swift @@ -1,101 +1,328 @@ -//// -//// Created by PT -//// Copyright © 2023 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 +// Created by PT +// Copyright © 2023 Dash Core Group. All rights reserved. // -// final class MainTabbarController: UITabBarController { -// static let kAnimationDuration: TimeInterval = 0.35 +// 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 // -// weak var homeController: DWHomeViewController? -// } +// https://opensource.org/licenses/MIT // -// class DWMainTabbarViewController: UITabBarController, DWHomeViewControllerDelegate, UINavigationControllerDelegate, DWWipeDelegate, DWMainMenuViewControllerDelegate { +// 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. // -// var isDemoMode: Bool = false + +import UIKit + +// MARK: - MainTabbarTabs + +private enum MainTabbarTabs: Int, CaseIterable { + case home + case payment + case more +} + +extension MainTabbarTabs { + var isEmpty: Bool { + self == .payment + } + + var icon: UIImage { + let name: String + + switch self { + case .home: + name = "tabbar_home_icon" + case .payment: + return UIImage() + case .more: + name = "tabbar_other_icon" + } + + return UIImage(named: name)! + } +} + +// MARK: - MainTabbarController + +@objc +class MainTabbarController: UITabBarController { + static let kAnimationDuration: TimeInterval = 0.35 + + weak var homeController: DWHomeViewController? + // weak var contactsNavigationController: DWContacts? + weak var menuNavigationController: DWMainMenuViewController? + + // TODO: Refactor this and send notification about wiped wallet instead of chaining the delegate + @objc + weak var wipeDelegate: DWWipeDelegate? + + private var paymentButton: PaymentButton! + + @objc + var isDemoMode = false + + @objc + weak var demoDelegate: DWDemoDelegate? + + // TODO: Move it out from here and initialize the model inside home view controller + @objc + var homeModel: DWHomeProtocol! + + @objc + init(homeModel: DWHomeProtocol) { + super.init(nibName: nil, bundle: nil) + + self.homeModel = homeModel + configureControllers() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Actions + + @objc + private func paymentButtonAction() { + showPaymentsController(withActivePage: .none) + } + + // MARK: Life Cycle + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + tabBar.addSubview(paymentButton) + } + + override func viewDidLoad() { + super.viewDidLoad() + + configureHierarchy() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // Add Payment Button again to make sure it's at the top + tabBar.addSubview(paymentButton) + } +} + +// MARK: - Private +extension MainTabbarController { + private func configureControllers() { + var viewControllers: [UIViewController] = [] + + // Home + var item = UITabBarItem(title: nil, image: MainTabbarTabs.home.icon, tag: 0) + item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0) + + let homeVC = DWHomeViewController() + homeVC.delegate = self + homeVC.model = homeModel + + var nvc = BaseNavigationController(rootViewController: homeVC) + nvc.tabBarItem = item + viewControllers.append(nvc) + + // Payment + item = UITabBarItem(title: "", image: UIImage(), tag: 1) + item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0) + + let vc = EmptyController() + vc.tabBarItem = item + viewControllers.append(vc) + + // More + item = UITabBarItem(title: nil, image: MainTabbarTabs.more.icon, tag: 2) + item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0) + let menuVC = DWMainMenuViewController() + menuVC.delegate = self + + nvc = BaseNavigationController(rootViewController: menuVC) + nvc.tabBarItem = item + viewControllers.append(nvc) + + self.viewControllers = viewControllers + } + + private func configureHierarchy() { + paymentButton = PaymentButton() + paymentButton.translatesAutoresizingMaskIntoConstraints = false + paymentButton.addTarget(self, action: #selector(paymentButtonAction), for: .touchUpInside) + tabBar.addSubview(paymentButton) + + NSLayoutConstraint.activate([ + paymentButton.centerXAnchor.constraint(equalTo: tabBar.centerXAnchor), + paymentButton.topAnchor.constraint(equalTo: tabBar.topAnchor, constant: 4), + + paymentButton.widthAnchor.constraint(equalToConstant: PaymentButton.kCenterCircleSize), + paymentButton.heightAnchor.constraint(equalToConstant: PaymentButton.kCenterCircleSize), + ]) + + tabBar.barTintColor = .dw_background() + tabBar.tintColor = .dw_dashBlue() + tabBar.unselectedItemTintColor = .dw_tabbarInactiveButton() + } + + private func closePayments(completion: (() -> Void)? = nil) { + paymentButton.isOpened = false + + guard let currentController = selectedViewController, + currentController.topController() is PaymentsViewController else { + tabBar.isUserInteractionEnabled = true + completion?() + return + } + + tabBar.isUserInteractionEnabled = false + + currentController.topController().dismiss(animated: true) { + self.tabBar.isUserInteractionEnabled = true + completion?() + } + } + +// private func contactsNavigationController() -> DWNavigationController { +// if contactsNavigationController == nil { +// let contactsController = DWContactsViewController(payModel: homeModel.payModel, dataProvider: homeModel.getDataProvider) // +// contactsNavigationController = DWNavigationController(rootViewController: contactsController) +// contactsNavigationController.delegate = self +// } // -//// var homeNavigationController: DWNavigationController! -//// var contactsNavigationController: DWNavigationController! -//// var menuNavigationController: DWNavigationController! -//// -// -// func performScanQRCodeAction() { -// dismiss(animated: false, completion: nil) -// -//// transitionToController(homeNavigationController, transitionType: .withoutAnimation) -//// tabBarView?.updateSelectedTabButton(.home) -//// homeController?.performScanQRCodeAction() -// } -// -// func performPayToURL(_ url: URL) { -// dismiss(animated: false, completion: nil) -//// transitionToController(homeNavigationController, transitionType: .withoutAnimation) -//// tabBarView?.updateSelectedTabButton(.home) -//// homeController?.performPayToURL(url) -// } -// -// func handleFile(_ file: Data) { -// dismiss(animated: false, completion: nil) -//// transitionToController(homeNavigationController, transitionType: .withoutAnimation) -//// tabBarView?.updateSelectedTabButton(.home) -//// homeController?.handleFile(file) -// } -// -// -// -// -// override func viewDidLoad() { -// super.viewDidLoad() -// -// configureHierarchy() -// } -// } -// -// private extension DWMainTabbarViewController { -// func configureHierarchy() { -// +// return contactsNavigationController! // } -// } -// -//// MARK: Demo mode -// extension DWMainTabbarViewController { -// func openPaymentsScreen() { -// assert(isDemoMode, "Invalid usage. Should be used in Demo mode only") -// //showPaymentsController(withActivePage: .pay) -// } -// -// func closePaymentsScreen() { -// assert(isDemoMode, "Invalid usage. Should be used in Demo mode only") -// //tabBarViewDidClosePayments(tabBarView) -// } -// } -// -// extension DWMainTabbarViewController: PaymentsViewControllerDelegate { -// func paymentsViewControllerWantsToImportPrivateKey(_ controller: PaymentsViewController) { -// -// } -// -// func paymentsViewControllerDidCancel(_ controller: PaymentsViewController) { -// -// } -// -// func paymentsViewControllerDidFinishPayment(_ controller: PaymentsViewController, contact: DWDPBasicUserItem?) { -// -// } -// +} + +// MARK: - Public +extension MainTabbarController { + @objc + public func performScanQRCodeAction() { + dismiss(animated: false, completion: nil) + selectedIndex = MainTabbarTabs.home.rawValue + homeController?.performScanQRCodeAction() + } + + @objc + public func performPay(to url: URL) { + dismiss(animated: false, completion: nil) + selectedIndex = MainTabbarTabs.home.rawValue + homeController?.performPay(to: url) + } + + @objc + public func handleFile(_ file: Data) { + dismiss(animated: false, completion: nil) + selectedIndex = MainTabbarTabs.home.rawValue + homeController?.handleFile(file) + } + + @objc + public func openPaymentsScreen() { + assert(isDemoMode, "Invalid usage. Should be used in Demo mode only") + showPaymentsController(withActivePage: .pay) + } + + @objc + public func closePaymentsScreen() { + assert(isDemoMode, "Invalid usage. Should be used in Demo mode only") + closePayments() + } +} + +// MARK: DWMainMenuViewControllerDelegate + +extension MainTabbarController: DWMainMenuViewControllerDelegate { + func mainMenuViewControllerImportPrivateKey(_ controller: DWMainMenuViewController) { + performScanQRCodeAction() + } + + func mainMenuViewControllerOpenHomeScreen(_ controller: DWMainMenuViewController) { + selectedIndex = MainTabbarTabs.home.rawValue + } +} + +// MARK: DWWipeDelegate + +extension MainTabbarController: DWWipeDelegate { + func didWipeWallet() { + wipeDelegate?.didWipeWallet() + } +} + +// MARK: PaymentsViewControllerDelegate + +extension MainTabbarController: PaymentsViewControllerDelegate { + func paymentsViewControllerWantsToImportPrivateKey(_ controller: PaymentsViewController) { + // Make sure we enable tabbar before showing the scanner + tabBar.isUserInteractionEnabled = true + paymentButton.isOpened = false + + controller.dismiss(animated: true) { + self.performScanQRCodeAction() + } + } + + func paymentsViewControllerDidCancel(_ controller: PaymentsViewController) { + closePayments() + } + + func paymentsViewControllerDidFinishPayment(_ controller: PaymentsViewController, contact: DWDPBasicUserItem?) { + closePayments { + // TODO: DashPay +// guard let contact else { +// return +// } // -// } +// let profile = DWModalUserProfileViewController(item: contact, +// payModel: self.homeModel.payModel, +// dataProvider: self.homeModel.getDataProvider) +// self.present(profile, animated: true, completion: nil) + } + } +} + +// MARK: DWHomeViewControllerDelegate + +extension MainTabbarController: DWHomeViewControllerDelegate { + func showPaymentsController(withActivePage pageIndex: NSInteger) { + showPaymentsController(withActivePage: PaymentsViewControllerState(rawValue: pageIndex)!) + } + + func showPaymentsController(withActivePage pageIndex: PaymentsViewControllerState) { + tabBar.isUserInteractionEnabled = false + paymentButton.isOpened = true + + let receiveModel = DWReceiveModel() + let payModel = DWPayModel() + + let controller = PaymentsViewController.controller(withReceiveModel: receiveModel, + payModel: payModel) + + controller.delegate = self + controller.currentState = pageIndex + controller.demoMode = isDemoMode + controller.demoDelegate = demoDelegate + + let navigationController = BaseNavigationController(rootViewController: controller) + navigationController.isModalInPresentation = true + + if isDemoMode { + demoDelegate?.presentModalController(navigationController, sender: self) + } else { + selectedViewController?.topController().present(navigationController, animated: true) { + self.tabBar.isUserInteractionEnabled = true + } + } + } +} + +// MARK: - EmptyController + +private final class EmptyController: UIViewController { } diff --git a/DashWallet/Sources/UI/Main/Views/DWPaymentsButton.m b/DashWallet/Sources/UI/Main/Views/DWPaymentsButton.m deleted file mode 100644 index c3d45af64..000000000 --- a/DashWallet/Sources/UI/Main/Views/DWPaymentsButton.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2020 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 "DWPaymentsButton.h" - -@implementation DWPaymentsButton - -@end diff --git a/DashWallet/Sources/UI/Main/Views/DWTabBarButton.h b/DashWallet/Sources/UI/Main/Views/DWTabBarButton.h deleted file mode 100644 index afd7b940b..000000000 --- a/DashWallet/Sources/UI/Main/Views/DWTabBarButton.h +++ /dev/null @@ -1,39 +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 - -typedef NS_ENUM(NSUInteger, DWTabBarButtonType) { - DWTabBarButtonType_Home, - DWTabBarButtonType_Contacts, - DWTabBarButtonType_Discover, - DWTabBarButtonType_Others, -}; - -@interface DWTabBarButton : UIControl - -- (instancetype)initWithType:(DWTabBarButtonType)type; - -- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Main/Views/DWTabBarButton.m b/DashWallet/Sources/UI/Main/Views/DWTabBarButton.m deleted file mode 100644 index dac5c8678..000000000 --- a/DashWallet/Sources/UI/Main/Views/DWTabBarButton.m +++ /dev/null @@ -1,84 +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 "DWTabBarButton.h" - -#import - -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -static UIColor *ActiveButtonColor(void) { - return [UIColor dw_dashBlueColor]; -} - -static UIColor *InactiveButtonColor(void) { - return [UIColor dw_tabbarInactiveButtonColor]; -} - - -@interface DWTabBarButton () - -@property (readonly, strong, nonatomic) UIImageView *iconImageView; - -@end - -@implementation DWTabBarButton - -- (instancetype)initWithType:(DWTabBarButtonType)type { - self = [super initWithFrame:CGRectZero]; - if (self) { - UIImage *image = nil; - switch (type) { - case DWTabBarButtonType_Home: - image = [UIImage imageNamed:@"tabbar_home_icon"]; - break; - case DWTabBarButtonType_Contacts: - image = [UIImage imageNamed:@"tabbar_contacts_icon"]; - break; - case DWTabBarButtonType_Discover: - image = [UIImage imageNamed:@"tabbar_discover_icon"]; - break; - case DWTabBarButtonType_Others: - image = [UIImage imageNamed:@"tabbar_other_icon"]; - break; - } - - UIImage *activeImage = [image ds_imageWithTintColor:ActiveButtonColor()]; - UIImage *inactiveImage = [image ds_imageWithTintColor:InactiveButtonColor()]; - - UIImageView *iconImageView = [[UIImageView alloc] initWithImage:inactiveImage - highlightedImage:activeImage]; - iconImageView.frame = self.bounds; - iconImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - iconImageView.contentMode = UIViewContentModeCenter; - [self addSubview:iconImageView]; - _iconImageView = iconImageView; - } - return self; -} - -- (void)setSelected:(BOOL)selected { - [super setSelected:selected]; - - self.iconImageView.highlighted = selected; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Main/Views/DWTabBarView.h b/DashWallet/Sources/UI/Main/Views/DWTabBarView.h deleted file mode 100644 index 9f061a6ba..000000000 --- a/DashWallet/Sources/UI/Main/Views/DWTabBarView.h +++ /dev/null @@ -1,51 +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 - -typedef NS_ENUM(NSUInteger, DWTabBarViewButtonType) { - DWTabBarViewButtonType_Home, - DWTabBarViewButtonType_Contacts, - DWTabBarViewButtonType_Others, -}; - -@class DWTabBarView; - -@protocol DWTabBarViewDelegate - -- (void)tabBarView:(DWTabBarView *)tabBarView didTapButtonType:(DWTabBarViewButtonType)buttonType; -- (void)tabBarViewDidOpenPayments:(DWTabBarView *)tabBarView; -- (void)tabBarViewDidClosePayments:(DWTabBarView *)tabBarView; - -@end - -@interface DWTabBarView : UIView - -@property (nullable, nonatomic, weak) id delegate; - -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE; - -- (void)setPaymentsButtonOpened:(BOOL)opened; -- (void)updateSelectedTabButton:(DWTabBarViewButtonType)type; - -- (void)togglePaymentsOpenState; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Main/Views/DWTabBarView.m b/DashWallet/Sources/UI/Main/Views/DWTabBarView.m deleted file mode 100644 index 872ee4aee..000000000 --- a/DashWallet/Sources/UI/Main/Views/DWTabBarView.m +++ /dev/null @@ -1,246 +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 "DWTabBarView.h" - -#import "DWPaymentsButton.h" -#import "DWSharedUIConstants.h" -#import "DWTabBarButton.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -static CGFloat const DW_TABBAR_HEIGHT = 64.0; -static CGFloat const TABBAR_HEIGHT_LARGE = 77.0; -static CGFloat const TABBAR_BORDER_WIDTH = 1.0; -static CGFloat const CENTER_CIRCLE_SIZE = 47.0; - -@interface DWTabBarView () - -@property (nonatomic, strong) CALayer *topLineLayer; - -@property (nonatomic, copy) NSArray *buttons; -@property (nonatomic, strong) DWTabBarButton *homeButton; -@property (nonatomic, strong) DWTabBarButton *contactsButton; -@property (nonatomic, strong) DWPaymentsButton *paymentsButton; -@property (nonatomic, strong) DWTabBarButton *discoverButton; -@property (nonatomic, strong) DWTabBarButton *othersButton; - -@end - -@implementation DWTabBarView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_backgroundColor]; - self.clipsToBounds = NO; - - CALayer *topLineLayer = [CALayer layer]; - topLineLayer.backgroundColor = [UIColor dw_tabbarBorderColor].CGColor; - [self.layer addSublayer:topLineLayer]; - _topLineLayer = topLineLayer; - - NSMutableArray *buttons = [NSMutableArray array]; - - { - DWTabBarButton *button = [[DWTabBarButton alloc] initWithType:DWTabBarButtonType_Home]; - [button addTarget:self - action:@selector(tabBarButtonAction:) - forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:button]; - [buttons addObject:button]; - _homeButton = button; - -#if SNAPSHOT - button.accessibilityIdentifier = @"tabbar_home_button"; -#endif /* SNAPSHOT */ - } - - // { - // DWTabBarButton *button = [[DWTabBarButton alloc] initWithType:DWTabBarButtonType_Contacts]; - // [button addTarget:self - // action:@selector(tabBarButtonAction:) - // forControlEvents:UIControlEventTouchUpInside]; - // [self addSubview:button]; - // [buttons addObject:button]; - // _contactsButton = button; - // } - - { - DWPaymentsButton *button = [[DWPaymentsButton alloc] initWithFrame:CGRectZero]; - button.backgroundColor = [UIColor dw_dashBlueColor]; - [button setImage:[UIImage imageNamed:@"tabbar_pay_button"] forState:UIControlStateNormal]; - button.layer.cornerRadius = CENTER_CIRCLE_SIZE / 2.0; - button.layer.masksToBounds = YES; - [button addTarget:self - action:@selector(paymentsButtonAction:) - forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:button]; - [buttons addObject:button]; - _paymentsButton = button; - -#if SNAPSHOT - button.accessibilityIdentifier = @"tabbar_payments_button"; -#endif /* SNAPSHOT */ - } - - // { - // DWTabBarButton *button = [[DWTabBarButton alloc] initWithType:DWTabBarButtonType_Discover]; - // [button addTarget:self - // action:@selector(tabBarButtonAction:) - // forControlEvents:UIControlEventTouchUpInside]; - // [self addSubview:button]; - // [buttons addObject:button]; - // _discoverButton = button; - // } - - { - DWTabBarButton *button = [[DWTabBarButton alloc] initWithType:DWTabBarButtonType_Others]; - [button addTarget:self - action:@selector(tabBarButtonAction:) - forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:button]; - [buttons addObject:button]; - _othersButton = button; - -#if SNAPSHOT - button.accessibilityIdentifier = @"tabbar_menu_button"; -#endif /* SNAPSHOT */ - } - - _buttons = [buttons copy]; - - [self updateSelectedTabButton:DWTabBarViewButtonType_Home]; - } - return self; -} - -- (CGSize)intrinsicContentSize { - return CGSizeMake(UIViewNoIntrinsicMetric, - DEVICE_HAS_HOME_INDICATOR ? TABBAR_HEIGHT_LARGE : DW_TABBAR_HEIGHT); -} - -- (void)layoutSubviews { - [super layoutSubviews]; - - NSAssert(self.buttons.count > 0, @"Invalid state"); - - const CGSize size = self.bounds.size; - const CGFloat buttonWidth = size.width / self.buttons.count; - CGFloat x = 0.0; - for (UIButton *button in self.buttons) { - if (button != self.paymentsButton) { - button.frame = CGRectMake(x, 0.0, buttonWidth, MIN(DW_TABBAR_HEIGHT, size.height)); - } - - x += buttonWidth; - } - - self.topLineLayer.frame = CGRectMake(0, 0, size.width, TABBAR_BORDER_WIDTH); - - self.paymentsButton.frame = CGRectMake((size.width - CENTER_CIRCLE_SIZE) / 2.0, - (size.height - CENTER_CIRCLE_SIZE) / 2.0 - 5.0, - CENTER_CIRCLE_SIZE, - CENTER_CIRCLE_SIZE); -} - -- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection { - [super traitCollectionDidChange:previousTraitCollection]; - - self.backgroundColor = [UIColor dw_backgroundColor]; - - UIColor *borderColor = [UIColor dw_tabbarBorderColor]; - self.topLineLayer.backgroundColor = borderColor.CGColor; -} - -- (void)setPaymentsButtonOpened:(BOOL)opened { - self.paymentsButton.opened = opened; -} - -- (void)togglePaymentsOpenState { - [self paymentsButtonAction:self.paymentsButton]; -} - -#pragma mark - Actions - -- (void)paymentsButtonAction:(DWPaymentsButton *)sender { - if (sender.opened == NO) { - [self.delegate tabBarViewDidOpenPayments:self]; - } - else { - [self.delegate tabBarViewDidClosePayments:self]; - } -} - -- (void)tabBarButtonAction:(UIView *)sender { - if (self.paymentsButton.opened) { - [self.delegate tabBarViewDidClosePayments:self]; - } - else { - DWTabBarViewButtonType type; - if (sender == self.homeButton) { - type = DWTabBarViewButtonType_Home; - } - else if (sender == self.contactsButton) { - type = DWTabBarViewButtonType_Contacts; - } - else if (sender == self.othersButton) { - type = DWTabBarViewButtonType_Others; - } - else if (sender == self.discoverButton) { - // TODO: DP fix me - type = DWTabBarViewButtonType_Contacts; - } - else { - type = DWTabBarViewButtonType_Home; - NSAssert(NO, @"Invalid sender"); - } - - [self.delegate tabBarView:self - didTapButtonType:type]; - } -} - -- (void)updateSelectedTabButton:(DWTabBarViewButtonType)type { - self.othersButton.selected = NO; - self.contactsButton.selected = NO; - self.discoverButton.selected = NO; - self.homeButton.selected = NO; - - switch (type) { - case DWTabBarViewButtonType_Home: { - self.homeButton.selected = YES; - - break; - } - case DWTabBarViewButtonType_Contacts: { - self.contactsButton.selected = YES; - - break; - } - case DWTabBarViewButtonType_Others: { - self.othersButton.selected = YES; - - break; - } - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Main/Views/PaymentButton.swift b/DashWallet/Sources/UI/Main/Views/PaymentButton.swift index 5236ba980..a9e0cb5a5 100644 --- a/DashWallet/Sources/UI/Main/Views/PaymentButton.swift +++ b/DashWallet/Sources/UI/Main/Views/PaymentButton.swift @@ -17,6 +17,23 @@ import UIKit +@objc(DWPaymentsButton) final class PaymentButton: UIButton { + static let kCenterCircleSize: CGFloat = 48.0 + + @objc var isOpened = false + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = .dw_dashBlue() + setImage(UIImage(named: "tabbar_pay_button")!, for: .normal) + layer.cornerRadius = PaymentButton.kCenterCircleSize / 2.0 + layer.masksToBounds = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } } diff --git a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h index 26a3188d1..8b3495960 100644 --- a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h +++ b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.h @@ -18,19 +18,11 @@ #import #import "DWWipeDelegate.h" -#import "dashwallet-Swift.h" NS_ASSUME_NONNULL_BEGIN @class DWMainMenuViewController; - -@protocol DWMainMenuViewControllerDelegate - -- (void)mainMenuViewControllerImportPrivateKey:(DWMainMenuViewController *)controller; -- (void)mainMenuViewControllerOpenHomeScreen:(DWMainMenuViewController *)controller; -- (void)showPaymentsControllerWithActivePage:(DWPaymentsViewControllerIndex)pageIndex; - -@end +@protocol DWMainMenuViewControllerDelegate; @interface DWMainMenuViewController : UIViewController diff --git a/DashWallet/Sources/UI/Onboarding/Controllers/DWDemoMainTabbarViewController.h b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewControllerDelegate.h similarity index 50% rename from DashWallet/Sources/UI/Onboarding/Controllers/DWDemoMainTabbarViewController.h rename to DashWallet/Sources/UI/Menu/Main/DWMainMenuViewControllerDelegate.h index 4399b76f4..8b6ce5183 100644 --- a/DashWallet/Sources/UI/Onboarding/Controllers/DWDemoMainTabbarViewController.h +++ b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewControllerDelegate.h @@ -1,6 +1,6 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 Dash Core Group. All rights reserved. +// +// Created by PT +// Copyright © 2023 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. @@ -15,12 +15,17 @@ // limitations under the License. // -#import "DWMainTabbarViewController.h" +#import + +#ifndef DWMainMenuViewControllerDelegate_h +#define DWMainMenuViewControllerDelegate_h -NS_ASSUME_NONNULL_BEGIN +@protocol DWMainMenuViewControllerDelegate -@interface DWDemoMainTabbarViewController : DWMainTabbarViewController +- (void)mainMenuViewControllerImportPrivateKey:(DWMainMenuViewController *)controller; +- (void)mainMenuViewControllerOpenHomeScreen:(DWMainMenuViewController *)controller; +- (void)showPaymentsControllerWithActivePage:(NSInteger)pageIndex; @end -NS_ASSUME_NONNULL_END +#endif /* DWMainMenuViewControllerDelegate_h */ diff --git a/DashWallet/Sources/UI/Onboarding/Controllers/DWDemoAppRootViewController.m b/DashWallet/Sources/UI/Onboarding/Controllers/DWDemoAppRootViewController.m index a66626282..0fca54792 100644 --- a/DashWallet/Sources/UI/Onboarding/Controllers/DWDemoAppRootViewController.m +++ b/DashWallet/Sources/UI/Onboarding/Controllers/DWDemoAppRootViewController.m @@ -17,7 +17,6 @@ #import "DWDemoAppRootViewController.h" -#import "DWDemoMainTabbarViewController.h" #import "DWRootModelStub.h" NS_ASSUME_NONNULL_BEGIN @@ -40,7 +39,7 @@ - (void)viewDidLoad { } + (Class)mainControllerClass { - return [DWDemoMainTabbarViewController class]; + return [DemoMainTabbarViewController class]; } #pragma mark - Demo Mode diff --git a/DashWallet/Sources/UI/Onboarding/Controllers/DWDemoMainTabbarViewController.m b/DashWallet/Sources/UI/Onboarding/Controllers/DWDemoMainTabbarViewController.m deleted file mode 100644 index 3671220d2..000000000 --- a/DashWallet/Sources/UI/Onboarding/Controllers/DWDemoMainTabbarViewController.m +++ /dev/null @@ -1,64 +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 "DWDemoMainTabbarViewController.h" - -#import "DWExtendedContainerViewController+DWProtected.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation DWDemoMainTabbarViewController - -- (void)hideModalControllerCompletion:(void (^)(void))completion { - UIViewController *modalController = self.modalController; - self.modalController = nil; - - if (!modalController) { - if (completion) { - completion(); - } - - return; - } - - [self.currentController beginAppearanceTransition:YES - animated:YES]; - - UIView *childView = modalController.view; - [modalController willMoveToParentViewController:nil]; - - [UIView animateWithDuration:self.transitionAnimationDuration - animations:^{ - childView.alpha = 0.0; - - [self setNeedsStatusBarAppearanceUpdate]; - } - completion:^(BOOL finished) { - [childView removeFromSuperview]; - [modalController removeFromParentViewController]; - - [self.currentController endAppearanceTransition]; - - if (completion) { - completion(); - } - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Onboarding/Controllers/DemoMainTabbarViewController.swift b/DashWallet/Sources/UI/Onboarding/Controllers/DemoMainTabbarViewController.swift new file mode 100644 index 000000000..408710945 --- /dev/null +++ b/DashWallet/Sources/UI/Onboarding/Controllers/DemoMainTabbarViewController.swift @@ -0,0 +1,47 @@ +// +// Created by PT +// Copyright © 2023 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 +final class DemoMainTabbarViewController: MainTabbarController { +// func hideModalController(completion: (() -> Void)?) { +// guard let modalController = self.modalController else { +// completion?() +// return +// } +// +// self.modalController = nil +// self.currentController.beginAppearanceTransition(true, animated: true) +// +// let childView = modalController.view +// modalController.willMove(toParentViewController: nil) +// +// UIView.animate(withDuration: self.transitionAnimationDuration, animations: { +// childView.alpha = 0.0 +// self.setNeedsStatusBarAppearanceUpdate() +// }) { finished in +// childView.removeFromSuperview() +// modalController.removeFromParentViewController() +// +// self.currentController.endAppearanceTransition() +// +// completion?() +// } +// } + +} diff --git a/DashWallet/Sources/UI/Payments/PaymentsViewController.swift b/DashWallet/Sources/UI/Payments/PaymentsViewController.swift index 6ca8e1651..5fdacf819 100644 --- a/DashWallet/Sources/UI/Payments/PaymentsViewController.swift +++ b/DashWallet/Sources/UI/Payments/PaymentsViewController.swift @@ -73,7 +73,6 @@ class PaymentsViewController: BaseViewController { private var receiveModel: DWReceiveModelProtocol! private var payModel: DWPayModelProtocol! - private var dataProvider: DWTransactionListDataProviderProtocol? private var payViewController: PayViewController! private var receiveViewController: ReceiveViewController! @@ -103,14 +102,16 @@ class PaymentsViewController: BaseViewController { super.viewDidAppear(animated) } + class func controller() -> PaymentsViewController { + sb("Payments").vc(PaymentsViewController.self) + } + @objc class func controller(withReceiveModel receiveModel: DWReceiveModelProtocol?, - payModel: DWPayModelProtocol?, - dataProvider: DWTransactionListDataProviderProtocol?) -> PaymentsViewController { - let controller = sb("Payments").vc(PaymentsViewController.self) + payModel: DWPayModelProtocol?) -> PaymentsViewController { + let controller = controller() controller.receiveModel = receiveModel controller.payModel = payModel - controller.dataProvider = dataProvider return controller } } diff --git a/DashWallet/Sources/UI/RootNavigation/DWAppRootViewController.m b/DashWallet/Sources/UI/RootNavigation/DWAppRootViewController.m index 3239dcac4..b1d5b18aa 100644 --- a/DashWallet/Sources/UI/RootNavigation/DWAppRootViewController.m +++ b/DashWallet/Sources/UI/RootNavigation/DWAppRootViewController.m @@ -21,7 +21,6 @@ #import #import "DWLockScreenViewController.h" -#import "DWMainTabbarViewController.h" #import "DWRootModel.h" #import "DWSetupViewController.h" #import "DWUIKit.h" @@ -40,7 +39,7 @@ @interface DWAppRootViewController () model; -@property (null_resettable, nonatomic, strong) DWMainTabbarViewController *mainController; +@property (null_resettable, nonatomic, strong) MainTabbarController *mainController; @property (nullable, nonatomic, strong) UIImageView *overlayImageView; @property (nonatomic, strong) UIWindow *lockWindow; @@ -70,7 +69,7 @@ - (instancetype)initWithModel:(id)model { #pragma mark - Public + (Class)mainControllerClass { - return [DWMainTabbarViewController class]; + return [MainTabbarController class]; } - (void)setLaunchingAsDeferredController { @@ -120,7 +119,7 @@ - (void)handleURL:(NSURL *)url { } else if ([action isKindOfClass:DWURLPayAction.class]) { NSURL *paymentURL = [(DWURLPayAction *)action paymentURL]; - [self.mainController performPayToURL:paymentURL]; + [self.mainController performPayTo:paymentURL]; } else { NSAssert(NO, @"Unhandled action", action); @@ -397,14 +396,13 @@ - (UIViewController *)setupController { return navigationController; } -- (DWMainTabbarViewController *)mainController { +- (MainTabbarController *)mainController { if (_mainController == nil) { id homeModel = self.model.homeModel; Class klass = [self.class mainControllerClass]; - DWMainTabbarViewController *controller = [[klass alloc] init]; - controller.homeModel = homeModel; - controller.delegate = self; - controller.demoMode = self.demoMode; + MainTabbarController *controller = [[MainTabbarController alloc] initWithHomeModel:self.model.homeModel]; + controller.wipeDelegate = self; + controller.isDemoMode = self.demoMode; controller.demoDelegate = self.demoDelegate; _mainController = controller; diff --git a/DashWallet/Sources/UI/Setup/DWSetupViewController.m b/DashWallet/Sources/UI/Setup/DWSetupViewController.m index 39039eda1..97d81a752 100644 --- a/DashWallet/Sources/UI/Setup/DWSetupViewController.m +++ b/DashWallet/Sources/UI/Setup/DWSetupViewController.m @@ -20,7 +20,6 @@ #import "DWBiometricAuthModel.h" #import "DWBiometricAuthViewController.h" #import "DWGlobalOptions.h" -#import "DWMainTabbarViewController.h" #import "DWPreviewSeedPhraseModel.h" #import "DWRecoverViewController.h" #import "DWSetPinModel.h" diff --git a/DashWallet/dashwallet-Bridging-Header.h b/DashWallet/dashwallet-Bridging-Header.h index 624a6e28a..a8d538531 100644 --- a/DashWallet/dashwallet-Bridging-Header.h +++ b/DashWallet/dashwallet-Bridging-Header.h @@ -37,13 +37,8 @@ static const bool _SNAPSHOT = 0; #import "DWNumberKeyboardInputViewAudioFeedback.h" #import "DWInputValidator.h" #import "DWAmountInputValidator.h" -#import "DWPaymentProcessor.h" #import "DWConfirmSendPaymentViewController.h" -#import "DWPaymentOutput.h" -#import "DWPaymentInput.h" -#import "DWPaymentInputBuilder.h" #import "DWLocalCurrencyViewController.h" -#import "DWPayModelProtocol.h" #import "DWDemoDelegate.h" #import "DWModalPopupTransition.h" #import "DWModalTransition.h" @@ -74,13 +69,13 @@ static const bool _SNAPSHOT = 0; #import "DWTransactionListDataProviderProtocol.h" #import "DWQuickReceiveViewController.h" #import "DWQRScanViewController.h" -#import "DWQRScanModel.h" #import "DWRequestAmountViewController.h" #import "UIViewController+DWShareReceiveInfo.h" #import "DWImportWalletInfoViewController.h" - -//MARK: Tabbar -#import "DWWipeDelegate.h" +#import "DWPaymentProcessor.h" +#import "DWPaymentOutput.h" +#import "DWPaymentInput.h" +#import "DWPaymentInputBuilder.h" //MARK: Uphold #import "DWUpholdTransactionObject.h" @@ -97,7 +92,7 @@ static const bool _SNAPSHOT = 0; #import #import "DWPhoneWCSessionManager.h" -//MARK: Platform +//MARK: DashPay #import "DWDPBasicUserItem.h" #import "DWDPAvatarView.h" #import "DWDPRegistrationStatus.h" @@ -106,6 +101,7 @@ static const bool _SNAPSHOT = 0; #import "DWDPRegistrationStatusTableViewCell.h" #import "DWDPRegistrationErrorRetryDelegate.h" #import "DWDPUserObject.h" +#import "DWModalUserProfileViewController.h" //MARK: CrowdNode #import "DWCheckbox.h" @@ -113,3 +109,11 @@ static const bool _SNAPSHOT = 0; #import "DWSeedPhraseModel.h" #import "UIImage+Utils.h" #import "NSData+Dash.h" + +//MARK: Tabbar +#import "DWHomeViewController.h" +#import "DWMainMenuViewController.h" +#import "DWWipeDelegate.h" +#import "DWPayModel.h" +#import "DWHomeViewControllerDelegate.h" +#import "DWMainMenuViewControllerDelegate.h" From c2cc7432b304266a81b826dc8f742f1089aa7158 Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 30 May 2023 16:43:58 +0700 Subject: [PATCH 20/38] fix: Demo mode --- DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m index aa8de2517..f9d322b62 100644 --- a/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m +++ b/DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m @@ -105,6 +105,9 @@ - (BOOL)isWalletEmpty { - (void)retrySyncing { } +- (void)checkCrowdNodeState { +} + - (void)registerForPushNotifications { } From f47371d1b7814da7323df2f640f7707f58e66161 Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 30 May 2023 17:37:26 +0700 Subject: [PATCH 21/38] fix(ui): Properly render payment button within tab bar --- .../Sources/Categories/UIDevice+Compatibility.swift | 4 +++- DashWallet/Sources/UI/Main/MainTabbarController.swift | 9 ++++++++- DashWallet/Sources/UI/Main/Views/PaymentButton.swift | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/DashWallet/Sources/Categories/UIDevice+Compatibility.swift b/DashWallet/Sources/Categories/UIDevice+Compatibility.swift index 05fbae537..d9071cfa7 100644 --- a/DashWallet/Sources/Categories/UIDevice+Compatibility.swift +++ b/DashWallet/Sources/Categories/UIDevice+Compatibility.swift @@ -29,7 +29,9 @@ extension UIDevice { static var isIphone6: Bool { isIphone && screenMaxLength <= 667.0 } static var isIphone6Plus: Bool { isIphone && screenMaxLength <= 736.0 } - static var hasHomeIndicator: Bool { (UIApplication.shared.delegate?.window??.safeAreaInsets.bottom ?? 0) > 0 } + static var hasHomeIndicator: Bool { + (UIApplication.shared.delegate?.window??.safeAreaInsets.bottom ?? 0) > 0 + } } diff --git a/DashWallet/Sources/UI/Main/MainTabbarController.swift b/DashWallet/Sources/UI/Main/MainTabbarController.swift index ea3328991..507290fc4 100644 --- a/DashWallet/Sources/UI/Main/MainTabbarController.swift +++ b/DashWallet/Sources/UI/Main/MainTabbarController.swift @@ -102,6 +102,7 @@ class MainTabbarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() + delegate = self configureHierarchy() } @@ -159,7 +160,7 @@ extension MainTabbarController { NSLayoutConstraint.activate([ paymentButton.centerXAnchor.constraint(equalTo: tabBar.centerXAnchor), - paymentButton.topAnchor.constraint(equalTo: tabBar.topAnchor, constant: 4), + paymentButton.topAnchor.constraint(equalTo: tabBar.topAnchor, constant: UIDevice.hasHomeIndicator ? 4 : 1), paymentButton.widthAnchor.constraint(equalToConstant: PaymentButton.kCenterCircleSize), paymentButton.heightAnchor.constraint(equalToConstant: PaymentButton.kCenterCircleSize), @@ -323,6 +324,12 @@ extension MainTabbarController: DWHomeViewControllerDelegate { } } +extension MainTabbarController: UITabBarControllerDelegate { + func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { + !(viewController is EmptyController) + } +} + // MARK: - EmptyController private final class EmptyController: UIViewController { } diff --git a/DashWallet/Sources/UI/Main/Views/PaymentButton.swift b/DashWallet/Sources/UI/Main/Views/PaymentButton.swift index a9e0cb5a5..538217221 100644 --- a/DashWallet/Sources/UI/Main/Views/PaymentButton.swift +++ b/DashWallet/Sources/UI/Main/Views/PaymentButton.swift @@ -19,7 +19,7 @@ import UIKit @objc(DWPaymentsButton) final class PaymentButton: UIButton { - static let kCenterCircleSize: CGFloat = 48.0 + static let kCenterCircleSize: CGFloat = 47.0 @objc var isOpened = false From 317758c96d677c6c0b43c38217e0026c76b538a8 Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 30 May 2023 18:12:46 +0700 Subject: [PATCH 22/38] fix(ui): Show 'Syncing' message without progress when the wallet tries to connect to the peer --- .../Home/Views/Cells/SyncingHeaderView.swift | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift b/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift index 499869c6a..28d688a7f 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift +++ b/DashWallet/Sources/UI/Home/Views/Cells/SyncingHeaderView.swift @@ -139,17 +139,20 @@ extension SyncingHeaderView { guard isSyncing else { return } - let percentString = String(format: "%0.1f%%", progress * 100.0) + + let string = NSMutableAttributedString() - let result = NSMutableAttributedString() - - let str1 = NSAttributedString(string: String(format: "%@ ", NSLocalizedString("Syncing", comment: "")), + let syncingString = NSAttributedString(string: String(format: "%@ ", NSLocalizedString("Syncing", comment: "")), attributes: [NSAttributedString.Key.font: UIFont.dw_font(forTextStyle: .body)]) - result.append(str1) - - let str2 = NSAttributedString(string: percentString, attributes: [NSAttributedString.Key.font: UIFont.dw_font(forTextStyle: .headline)]) - result.append(str2) + string.append(syncingString) - syncingButton.setAttributedTitle(result, for: .normal) + if DWEnvironment.sharedInstance().currentChainManager.peerManager.connected || progress > 0 { + let percentString = String(format: "%0.1f%%", progress * 100.0) + let progressString = NSAttributedString(string: percentString, + attributes: [NSAttributedString.Key.font: UIFont.dw_font(forTextStyle: .headline)]) + string.append(progressString) + } + + syncingButton.setAttributedTitle(string, for: .normal) } } From 48cabd872b8398e0e8de398e003730b081dde5e6 Mon Sep 17 00:00:00 2001 From: tikhop Date: Wed, 31 May 2023 16:14:07 +0700 Subject: [PATCH 23/38] chore: Bump up app version --- DashWallet.xcodeproj/project.pbxproj | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 7a8fc6175..eed0363c7 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -7428,7 +7428,7 @@ INFOPLIST_KEY_CFBundleDisplayName = Dash; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -7486,7 +7486,7 @@ INFOPLIST_KEY_CFBundleDisplayName = Dash; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "DashWallet/dashwallet-Bridging-Header.h"; @@ -7571,7 +7571,7 @@ EXCLUDED_ARCHS = ""; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -7593,7 +7593,7 @@ EXCLUDED_ARCHS = ""; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -7614,7 +7614,7 @@ EXCLUDED_ARCHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; SDKROOT = watchos; @@ -7637,7 +7637,7 @@ EXCLUDED_ARCHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; SDKROOT = watchos; @@ -7665,7 +7665,7 @@ INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -7690,7 +7690,7 @@ INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -7807,7 +7807,7 @@ INFOPLIST_KEY_CFBundleDisplayName = Dash; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "DashWallet/dashwallet-Bridging-Header.h"; @@ -7835,7 +7835,7 @@ INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -7887,7 +7887,7 @@ EXCLUDED_ARCHS = ""; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -7908,7 +7908,7 @@ EXCLUDED_ARCHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; SDKROOT = watchos; @@ -8024,7 +8024,7 @@ INFOPLIST_KEY_CFBundleDisplayName = Dash; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "DashWallet/dashwallet-Bridging-Header.h"; @@ -8051,7 +8051,7 @@ INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -8102,7 +8102,7 @@ EXCLUDED_ARCHS = ""; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -8123,7 +8123,7 @@ EXCLUDED_ARCHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 7.0.0; + MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; SDKROOT = watchos; From a818dc1dee657cae234a71e29840f0b7f7868de4 Mon Sep 17 00:00:00 2001 From: tikhop Date: Wed, 31 May 2023 16:15:49 +0700 Subject: [PATCH 24/38] fix(ui): Update the layout of the 'SyncView' with data at initialization step --- .../Sources/UI/Home/Syncing Views/Sync View/SyncView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift b/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift index b9e02e1d9..271431bba 100644 --- a/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift +++ b/DashWallet/Sources/UI/Home/Syncing Views/Sync View/SyncView.swift @@ -137,6 +137,7 @@ final class SyncView: UIView { super.awakeFromNib() commonInit() + updateView() } } From 91c8376e23d177a765e48d18c33c7fbf82f655c3 Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 13 Jun 2023 15:12:58 +0400 Subject: [PATCH 25/38] chore: Update uphold logo --- .../Uphold/uphold_logo.imageset/Contents.json | 12 ++++++------ .../Uphold/uphold_logo.imageset/logo.uphold.png | Bin 0 -> 799 bytes .../uphold_logo.imageset/logo.uphold@2x.png | Bin 0 -> 1568 bytes .../uphold_logo.imageset/logo.uphold@3x.png | Bin 0 -> 2326 bytes .../Uphold/uphold_logo.imageset/uphold_logo.png | Bin 17373 -> 0 bytes .../uphold_logo.imageset/uphold_logo@2x.png | Bin 44066 -> 0 bytes .../uphold_logo.imageset/uphold_logo@3x.png | Bin 75742 -> 0 bytes 7 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/logo.uphold.png create mode 100644 DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/logo.uphold@2x.png create mode 100644 DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/logo.uphold@3x.png delete mode 100644 DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/uphold_logo.png delete mode 100644 DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/uphold_logo@2x.png delete mode 100644 DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/uphold_logo@3x.png diff --git a/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/Contents.json b/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/Contents.json index ea144946b..232e1ca6e 100644 --- a/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/Contents.json +++ b/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/Contents.json @@ -1,23 +1,23 @@ { "images" : [ { + "filename" : "logo.uphold.png", "idiom" : "universal", - "filename" : "uphold_logo.png", "scale" : "1x" }, { + "filename" : "logo.uphold@2x.png", "idiom" : "universal", - "filename" : "uphold_logo@2x.png", "scale" : "2x" }, { + "filename" : "logo.uphold@3x.png", "idiom" : "universal", - "filename" : "uphold_logo@3x.png", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/logo.uphold.png b/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/logo.uphold.png new file mode 100644 index 0000000000000000000000000000000000000000..798b60d7c1c81dc798be54b69ebb01643e2e2008 GIT binary patch literal 799 zcmV+)1K|9LP)Qf(#c6N8Qn3H!0t)ezjV9$&3v~ z=-j^TU#x%?ftIIOQbO0uZ`ahB`CK;Y@)<7OfX~FIy97JBKh@Mu5OCrEhXiSeKzk0j zNK#xj&lPb+g}rQX!b#okc=8NauJ~Og%gM zTMKh@(Ay^={9JD@1F1Q1ds#{eI3s?=1bC*AYQY3JBt+qW;h~CXaqP%mFHLDzL4BoH z4Qn*gXAnSxL`!ucZd!J-f=I+S&3Z)SN30| z!N4wGxK_Tl1jb-0a)DpPN(I5f-Z2d(WiT%b6*KPTYiXJq*kS=wLb7T^IkCuOn^%Y3 zwW63YKV)6t0|II*mwHi65v8mNI}3dx4IL?8u)!EF(kUFkK(Ch~%;!EXC{+r|c2U4~gY%=*@dcGi=5S%D) dDAQrnk$;}odUV-ZBvb$Z002ovPDHLkV1kL(XJ-Ha literal 0 HcmV?d00001 diff --git a/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/logo.uphold@2x.png b/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/logo.uphold@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..42c9b349fe79c62f20fd933010e6d3874b0baf7f GIT binary patch literal 1568 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 literal 0 HcmV?d00001 diff --git a/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/uphold_logo.png b/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/uphold_logo.png deleted file mode 100644 index bda2430aec1e5c0736511bcec6dac2936400e249..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17373 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 diff --git a/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/uphold_logo@3x.png b/DashWallet/Resources/AppAssets.xcassets/Uphold/uphold_logo.imageset/uphold_logo@3x.png deleted file mode 100644 index f5c65d2c3a8aac51a6831af2b3584aa74061af9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75742 zcmV(?K-a&CP)+nyx5d%IUCcl4&%1r_TjAlN?aVh0O~C@Ly8EGP(i zBBG!Dv!j5D*uVygU5XSzQAFhvL_vCQ*S943pLZscWZT{D-QIEdGk4i3nasTNEFa-zWhY8y>t>jFN znTCb{l^@UdSOEv4C@Gt?1S|Zuz>GkcFb*AAn3iC|4Zjr3@W3sEBHN7kAgGrqrf>C& zXZ_SJ73ruXRhq|U<(s3v&8Ms&3S-hH(KHVNOg4AN8skJ=(+<*gHq?_vv2c)}mVgWh z8oJW zdaxBGi@nnO)1>()JuzWU!=K9x)=A#FFf#k&e}SBr2dpcL$v0*1b?+Ys0ka^WmkVb` zve>Kr-b*M$!oUw_=S~kY<-{ORQo-)ai;5WYGZwC3MZTV_Vak%YB3^v+kMFCh|AKIZ z&U~$Bv8;B}m>CW*BNAZaZ+In6j<|rrLZ%+mBS$}TW2r+XB9&w#T|o!@%IVzY%0Ak8 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 I_sJ~ee Date: Tue, 13 Jun 2023 17:15:31 +0400 Subject: [PATCH 26/38] feat: Update uphold logo size --- .../Auth/UpholdAuthStoryboard.storyboard | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/DashWallet/Sources/UI/Uphold/Auth/UpholdAuthStoryboard.storyboard b/DashWallet/Sources/UI/Uphold/Auth/UpholdAuthStoryboard.storyboard index 70739dc8a..b4cdefa9c 100644 --- a/DashWallet/Sources/UI/Uphold/Auth/UpholdAuthStoryboard.storyboard +++ b/DashWallet/Sources/UI/Uphold/Auth/UpholdAuthStoryboard.storyboard @@ -1,7 +1,9 @@ - + + - + + @@ -12,20 +14,24 @@ - + - + - - + + + + + + - + - + @@ -35,7 +41,7 @@ + @@ -129,7 +136,6 @@ - @@ -146,7 +152,7 @@ - + From 6e7815548b78b55af3bf638e19d5d1641af24abf Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 20 Jun 2023 14:08:59 +0400 Subject: [PATCH 27/38] feat(ui): Change Uphold Icon --- .../ServiceEntryPointModel.swift | 2 +- .../Transfer Amount/Views/ConverterView.swift | 3 +- .../Auth/UpholdAuthStoryboard.storyboard | 13 ++++---- .../Main/UpholdMainStoryboard.storyboard | 30 ++++++++++--------- .../Transfer/Model/UpholdAmountModel.swift | 2 +- Podfile.lock | 4 +-- 6 files changed, 28 insertions(+), 26 deletions(-) diff --git a/DashWallet/Sources/UI/Coinbase/ServiceOverview/ServiceEntryPointModel.swift b/DashWallet/Sources/UI/Coinbase/ServiceOverview/ServiceEntryPointModel.swift index f072bac2d..3b5dc52b8 100644 --- a/DashWallet/Sources/UI/Coinbase/ServiceOverview/ServiceEntryPointModel.swift +++ b/DashWallet/Sources/UI/Coinbase/ServiceOverview/ServiceEntryPointModel.swift @@ -77,7 +77,7 @@ extension Service { var entryIcon: String { switch self { case .coinbase: return "service.coinbase.square" - case .uphold: return "service.uphold.square" + case .uphold: return "uphold_logo" } } diff --git a/DashWallet/Sources/UI/Coinbase/Transfer Amount/Views/ConverterView.swift b/DashWallet/Sources/UI/Coinbase/Transfer Amount/Views/ConverterView.swift index 2a36de53b..76adcb670 100644 --- a/DashWallet/Sources/UI/Coinbase/Transfer Amount/Views/ConverterView.swift +++ b/DashWallet/Sources/UI/Coinbase/Transfer Amount/Views/ConverterView.swift @@ -350,9 +350,10 @@ final class SourceView: UIView { addSubview(stackView) imageView = UIImageView() + imageView.contentMode = .scaleAspectFit imageView.translatesAutoresizingMaskIntoConstraints = false imageView.layer.cornerRadius = 17 - imageView.backgroundColor = .dw_secondaryBackground() + imageView.backgroundColor = .clear stackView.addArrangedSubview(imageView) let labelStackView = UIStackView() diff --git a/DashWallet/Sources/UI/Uphold/Auth/UpholdAuthStoryboard.storyboard b/DashWallet/Sources/UI/Uphold/Auth/UpholdAuthStoryboard.storyboard index b4cdefa9c..ca5195f57 100644 --- a/DashWallet/Sources/UI/Uphold/Auth/UpholdAuthStoryboard.storyboard +++ b/DashWallet/Sources/UI/Uphold/Auth/UpholdAuthStoryboard.storyboard @@ -18,17 +18,16 @@ - + - - + + - - + - + @@ -150,9 +149,9 @@ + - diff --git a/DashWallet/Sources/UI/Uphold/Main/UpholdMainStoryboard.storyboard b/DashWallet/Sources/UI/Uphold/Main/UpholdMainStoryboard.storyboard index c732ca170..18846d1ad 100644 --- a/DashWallet/Sources/UI/Uphold/Main/UpholdMainStoryboard.storyboard +++ b/DashWallet/Sources/UI/Uphold/Main/UpholdMainStoryboard.storyboard @@ -1,7 +1,9 @@ - + + - + + @@ -17,17 +19,17 @@ - + - - + + - + @@ -125,7 +128,6 @@ - @@ -143,7 +145,7 @@ - + diff --git a/DashWallet/Sources/UI/Uphold/Transfer/Model/UpholdAmountModel.swift b/DashWallet/Sources/UI/Uphold/Transfer/Model/UpholdAmountModel.swift index fa3009a2f..f76c352d1 100644 --- a/DashWallet/Sources/UI/Uphold/Transfer/Model/UpholdAmountModel.swift +++ b/DashWallet/Sources/UI/Uphold/Transfer/Model/UpholdAmountModel.swift @@ -149,7 +149,7 @@ final class UpholdAmountModel: BaseAmountModel { extension UpholdAmountModel: ConverterViewDataSource { var fromItem: SourceViewDataProvider? { - ConverterViewSourceItem(image: .asset("service.uphold.square"), + ConverterViewSourceItem(image: .asset("uphold_logo"), title: "Uphold", balanceFormatted: card.formattedDashAmount, fiatBalanceFormatted: card.fiatBalanceFormatted) diff --git a/Podfile.lock b/Podfile.lock index 5dd312a11..5691e6816 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -699,7 +699,7 @@ PODS: - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - PromisesObjC (2.2.0) - - Protobuf (3.23.1) + - Protobuf (3.23.2) - SDWebImage (5.13.2): - SDWebImage/Core (= 5.13.2) - SDWebImage/Core (5.13.2) @@ -822,7 +822,7 @@ SPEC CHECKSUMS: Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - Protobuf: 3dd214698ddb7afd832da281c777b2e9facef401 + Protobuf: 5df4e9d2abd535fb675c68d171f6bc87983d27a6 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 SQLite.swift: 903bfa3bc9ab06345fdfbb578e34f47cfcf417da SQLiteMigrationManager.swift: 5383578f5bc8955c06695e8bf04835ee0e6673a8 From 4107cab45e1a853e17c8e9da80792f5e4b0de565 Mon Sep 17 00:00:00 2001 From: tikhop Date: Wed, 21 Jun 2023 20:57:33 +0400 Subject: [PATCH 28/38] feat: Reload Balance Model --- .../Home Balance View/BalanceModel.swift | 12 ++-- .../Home Balance View/HomeBalanceView.swift | 58 +++++++++---------- .../Home Header View/HomeHeaderView.swift | 1 - 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift b/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift index 917a94f1c..1cd186bca 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/BalanceModel.swift @@ -44,12 +44,7 @@ final class BalanceModel { } } - @objc - func applicationWillEnterForeground(_ notification: NSNotification) { - hideBalanceIfNeeded() - } - - private func reloadBalance() { + func reloadBalance() { let balanceValue = DWEnvironment.sharedInstance().currentWallet.balance if balanceValue > value && @@ -73,6 +68,11 @@ final class BalanceModel { balanceDidChange?() } + @objc + func applicationWillEnterForeground(_ notification: NSNotification) { + hideBalanceIfNeeded() + } + deinit { NotificationCenter.default.removeObserver(self) SyncingActivityMonitor.shared.remove(observer: self) diff --git a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift index 46e6ffc6c..1d784836a 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.swift @@ -74,33 +74,9 @@ final class HomeBalanceView: UIView { hideBalance(model.isBalanceHidden) } - func reloadView() { - var titleString = "" - - let isBalanceHidden = model.isBalanceHidden - - if !isBalanceHidden && state == .syncing { - titleString = NSLocalizedString("Syncing Balance", comment: "") - - titleLabel.alpha = 0.8 - titleLabel.layer.removeAllAnimations() - - UIView.animate(withDuration: 0.8, - delay:0.0, - options:[.allowUserInteraction, .curveEaseInOut, .autoreverse, .repeat], - animations: { self.titleLabel.alpha = 0.3 }, - completion: nil) - - } else { - titleLabel.layer.removeAllAnimations() - } - - titleLabel.text = titleString - hideBalance(isBalanceHidden) - } - public func reloadData() { - balanceView.reloadData() + model.reloadBalance() + reloadView() } private func commonInit() { @@ -147,14 +123,37 @@ final class HomeBalanceView: UIView { object: nil) reloadView() - reloadData() model.balanceDidChange = { [weak self] in - self?.reloadView() - self?.reloadData() + self?.balanceView.reloadData() } } + private func reloadView() { + var titleString = "" + + let isBalanceHidden = model.isBalanceHidden + + if !isBalanceHidden && state == .syncing { + titleString = NSLocalizedString("Syncing Balance", comment: "") + + titleLabel.alpha = 0.8 + titleLabel.layer.removeAllAnimations() + + UIView.animate(withDuration: 0.8, + delay:0.0, + options:[.allowUserInteraction, .curveEaseInOut, .autoreverse, .repeat], + animations: { self.titleLabel.alpha = 0.3 }, + completion: nil) + + } else { + titleLabel.layer.removeAllAnimations() + } + + titleLabel.text = titleString + hideBalance(isBalanceHidden) + } + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) @@ -188,7 +187,6 @@ final class HomeBalanceView: UIView { self.amountsView.alpha = hidden ? 0.0 : 1.0 self.tapToUnhideLabel.alpha = hidden ? 0.0 : 1.0 - self.reloadData() } } } diff --git a/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift index 8ec6700d7..45c211c44 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift @@ -138,7 +138,6 @@ final class HomeHeaderView: UIView { func reloadBalance() { let isSyncing = SyncingActivityMonitor.shared.state == .syncing - balanceView.reloadView() balanceView.reloadData() balanceView.state = isSyncing ? .syncing : .`default` } From 5bf86f3400fefc3bcc812813605d6d7b152a0aa0 Mon Sep 17 00:00:00 2001 From: tikhop Date: Wed, 21 Jun 2023 21:36:30 +0400 Subject: [PATCH 29/38] feat: Reset Coinbase account when wiping a wallet --- DashWallet/Sources/Application/App.swift | 1 + DashWallet/Sources/Models/Coinbase/Coinbase.swift | 13 ++++++++++++- .../Sources/UI/Setup/RecoverWallet/DWRecoverModel.m | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/DashWallet/Sources/Application/App.swift b/DashWallet/Sources/Application/App.swift index feea49529..41e2f5235 100644 --- a/DashWallet/Sources/Application/App.swift +++ b/DashWallet/Sources/Application/App.swift @@ -92,6 +92,7 @@ final class App { func cleanUp() { TxUserInfoDAOImpl.shared.deleteAll() AddressUserInfoDAOImpl.shared.deleteAll() + Coinbase.shared.reset() } } diff --git a/DashWallet/Sources/Models/Coinbase/Coinbase.swift b/DashWallet/Sources/Models/Coinbase/Coinbase.swift index 336b24080..953c66ded 100644 --- a/DashWallet/Sources/Models/Coinbase/Coinbase.swift +++ b/DashWallet/Sources/Models/Coinbase/Coinbase.swift @@ -40,6 +40,11 @@ class CoinbaseObjcWrapper: NSObject { static func start() { wrapped.initialize() } + + @objc + static func reset() { + wrapped.reset() + } } // MARK: - Coinbase @@ -59,11 +64,17 @@ class Coinbase { auth = CBAuth() accountService = AccountService(authInterop: auth) paymentMethodsService = PaymentMethods(authInterop: auth) - currencyExchanger.startExchangeRateFetching() + currencyExchanger.startExchangeRateFetching() prefetchData() } + func reset() { + Task { + try await signOut() + } + } + private func prefetchData() { Task { try await accountService.refreshAccount(kDashAccount) diff --git a/DashWallet/Sources/UI/Setup/RecoverWallet/DWRecoverModel.m b/DashWallet/Sources/UI/Setup/RecoverWallet/DWRecoverModel.m index af284b75d..843ba31d0 100644 --- a/DashWallet/Sources/UI/Setup/RecoverWallet/DWRecoverModel.m +++ b/DashWallet/Sources/UI/Setup/RecoverWallet/DWRecoverModel.m @@ -80,7 +80,7 @@ - (BOOL)phraseIsValid:(NSString *)phrase { } - (void)wipeWallet { - [DWApp cleanUp]; + [DWApp cleanUp]; //Send notificaiton [[DWEnvironment sharedInstance] clearAllWallets]; [[DWGlobalOptions sharedInstance] restoreToDefaults]; [[DWAppGroupOptions sharedInstance] restoreToDefaults]; From f8ca9b59c2da75774d9cd39e8ba2e3c0413b31d5 Mon Sep 17 00:00:00 2001 From: tikhop Date: Wed, 21 Jun 2023 22:50:34 +0400 Subject: [PATCH 30/38] feat: Auth user when showing recovery phrase from reminder --- .../BackupInfo/BackupInfoViewController.swift | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift b/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift index 735c25679..7b5451624 100644 --- a/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift +++ b/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift @@ -123,10 +123,26 @@ final class BackupInfoViewController: BaseViewController { @IBAction func backupButtonAction() { - let controller = DWBackupSeedPhraseViewController(model: seedPhraseModel) - controller.shouldCreateNewWalletOnScreenshot = shouldCreateNewWalletOnScreenshot - controller.delegate = delegate - navigationController?.pushViewController(controller, animated: true) + if type == .setup { + showSeedPhraseViewController() + } else { + DSAuthenticationManager.sharedInstance() + .authenticate(withPrompt: nil, + usingBiometricAuthentication: false, + alertIfLockout: true) { [weak self] authenticated, _, _ in + guard authenticated else { + return + } + + guard let self else { + return + } + + self.seedPhraseModel = DWPreviewSeedPhraseModel() + self.seedPhraseModel.getOrCreateNewWallet() + self.showSeedPhraseViewController() + } + } } @IBAction @@ -182,6 +198,13 @@ extension BackupInfoViewController { } } + private func showSeedPhraseViewController() { + let controller = DWBackupSeedPhraseViewController(model: seedPhraseModel) + controller.shouldCreateNewWalletOnScreenshot = shouldCreateNewWalletOnScreenshot + controller.delegate = delegate + navigationController?.pushViewController(controller, animated: true) + } + private func reloadCloseButton() { if isCloseButtonHidden { hideCloseButton() From 7d62b444dd64f2e20ca93d0cc033c360765029a6 Mon Sep 17 00:00:00 2001 From: tikhop Date: Wed, 21 Jun 2023 22:59:36 +0400 Subject: [PATCH 31/38] chore: Remove unused code --- .../SecureWallet/BackupInfo/BackupInfoViewController.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift b/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift index 7b5451624..a989de9c5 100644 --- a/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift +++ b/DashWallet/Sources/UI/Setup/SecureWallet/BackupInfo/BackupInfoViewController.swift @@ -145,11 +145,6 @@ final class BackupInfoViewController: BaseViewController { } } - @IBAction - func closeButtonAction() { - delegate?.secureWalletRoutineDidCanceled(self) - } - override func viewDidLoad() { super.viewDidLoad() From a6c0c59b37a3e2db0053fe68b7b0cca79efdb731 Mon Sep 17 00:00:00 2001 From: pankcuf Date: Thu, 29 Jun 2023 18:05:23 +0700 Subject: [PATCH 32/38] fix: legacy/basic operator public key display --- .../Models/DerivationPathKeysModel.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift index 5ff3327b9..46f222005 100644 --- a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift +++ b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift @@ -153,8 +153,16 @@ extension DerivationPathKeysModel { let address = derivationPath.address(at: index) return DerivationPathKeysItem(info: info, value: address) case .publicKey: - let publicKeyData = derivationPath.publicKeyData(at: index) - return DerivationPathKeysItem(info: info, value: publicKeyData.hexEncodedString()) + return autoreleasepool { + guard let phrase = wallet.seedPhraseIfAuthenticated() else { + return DerivationPathKeysItem(info: info, value: NSLocalizedString("Not available", comment: "")) + } + let seed = DSBIP39Mnemonic.sharedInstance()!.deriveKey(fromPhrase: phrase, withPassphrase: nil) + + let opaqueKey = self.derivationPath.privateKey(at: index, fromSeed: seed)! + let key = DSKeyManager.blsPublicKeySerialize(opaqueKey, legacy: false) + return DerivationPathKeysItem(info: info, value: key) + } case .privateKey: return autoreleasepool { guard let phrase = wallet.seedPhraseIfAuthenticated() else { From 4a4e0c859b97f459fa2d36a05ae43087325c0e42 Mon Sep 17 00:00:00 2001 From: pankcuf Date: Thu, 29 Jun 2023 18:05:48 +0700 Subject: [PATCH 33/38] chore: switch onto DashSync with DashSharedCore 0.4.4 --- DashSyncCurrentCommit | 2 +- Podfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DashSyncCurrentCommit b/DashSyncCurrentCommit index 913f2c8b1..44c89d5a2 100644 --- a/DashSyncCurrentCommit +++ b/DashSyncCurrentCommit @@ -1 +1 @@ -bfa1376ac66ddcf02a3de83d3507732a6a1c50a5 +8b44d120647b4c8869afaa913118b07fe13ebb3a diff --git a/Podfile.lock b/Podfile.lock index 5dd312a11..a3de31e0d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -588,11 +588,11 @@ PODS: - "!ProtoCompiler-gRPCPlugin (~> 1.0)" - DAPI-GRPC/Messages - gRPC-ProtoRPC - - DashSharedCore (0.4.0) + - DashSharedCore (0.4.4) - DashSync (0.1.0): - CocoaLumberjack (= 3.7.2) - DAPI-GRPC (= 0.22.0-dev.8) - - DashSharedCore (= 0.4.0) + - DashSharedCore (= 0.4.4) - DSDynamicOptions (= 0.1.2) - DWAlertController (= 0.2.1) - TinyCborObjc (= 0.4.6) @@ -699,7 +699,7 @@ PODS: - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - PromisesObjC (2.2.0) - - Protobuf (3.23.1) + - Protobuf (3.23.3) - SDWebImage (5.13.2): - SDWebImage/Core (= 5.13.2) - SDWebImage/Core (5.13.2) @@ -800,8 +800,8 @@ SPEC CHECKSUMS: CocoaImageHashing: 8656031d0899abe6c1c415827de43e9798189c53 CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da DAPI-GRPC: 138d62523bbfe7e88a39896f1053c0bc12390d9f - DashSharedCore: 4d583600745bc8b8ff32c3968d297fb1bd0e4218 - DashSync: 3ff50b1878685726b452638405d49d5802012732 + DashSharedCore: 2fc4af970b21cc06d423e3c1fe4efa49688065ad + DashSync: d9a3fd99460f4b8718f296b26573c50c0c262163 DSDynamicOptions: 347cc5d2c4e080eb3de6a86719ad3d861b82adfc DWAlertController: 5f4cd8adf90336331c054857f709f5f8d4b16a5b Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d @@ -822,7 +822,7 @@ SPEC CHECKSUMS: Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - Protobuf: 3dd214698ddb7afd832da281c777b2e9facef401 + Protobuf: d9c3d7e5a3574aa6a5d521f2d9f733c76d294be8 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 SQLite.swift: 903bfa3bc9ab06345fdfbb578e34f47cfcf417da SQLiteMigrationManager.swift: 5383578f5bc8955c06695e8bf04835ee0e6673a8 From 60023fb98613363b3c9d7e2084e0c748b9996940 Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 29 Jun 2023 15:16:02 +0400 Subject: [PATCH 34/38] feat: Support new explore dash database scheme where every column can be NULL --- .../Explore Dash/Model/Entites/ExplorePointOfUse.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift b/DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift index 5838c7503..eabd1351f 100644 --- a/DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift +++ b/DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift @@ -189,7 +189,7 @@ extension ExplorePointOfUse: RowDecodable { let longitude = row[ExplorePointOfUse.longitude] var website: String? - if var value = row[ExplorePointOfUse.website], !value.isEmpty { + if let value = row[ExplorePointOfUse.website], !value.isEmpty { if !value.hasPrefix("http") { website = "https://" + value } else { @@ -197,9 +197,9 @@ extension ExplorePointOfUse: RowDecodable { } } - let phone: String? = row[ExplorePointOfUse.phone]?.digits - let logoLocation = row[ExplorePointOfUse.logoLocation] - let coverImage: String? = row[ExplorePointOfUse.coverImage] + let phone: String? = try? row.get(ExplorePointOfUse.phone)?.digits + let logoLocation: String? = try? row.get(ExplorePointOfUse.logoLocation) + let coverImage: String? = try? row.get(ExplorePointOfUse.coverImage) let source: String? = row[ExplorePointOfUse.source] let category: Category From ce44533b9206d36e7ae10a60d4d5b442ebdf0176 Mon Sep 17 00:00:00 2001 From: tikhop Date: Thu, 29 Jun 2023 15:21:01 +0400 Subject: [PATCH 35/38] feat: New icon for ATMs without logo --- .../Contents.json | 23 ++++++++++++++++++ .../Icons & images.png | Bin 0 -> 919 bytes .../Icons & images@2x.png | Bin 0 -> 1566 bytes .../Icons & images@3x.png | Bin 0 -> 2375 bytes .../Model/Entites/ExplorePointOfUse.swift | 9 +++++++ .../Details/Views/PointOfUseDetailsView.swift | 2 +- .../List/Cells/PointOfUseItemCell.swift | 2 +- .../List/Views/ExploreMapAnnotationView.swift | 3 ++- 8 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 DashWallet/Resources/AppAssets.xcassets/Explore Dash/image.explore.dash.atm.item.logo.empty.imageset/Contents.json create mode 100644 DashWallet/Resources/AppAssets.xcassets/Explore Dash/image.explore.dash.atm.item.logo.empty.imageset/Icons & images.png create mode 100644 DashWallet/Resources/AppAssets.xcassets/Explore Dash/image.explore.dash.atm.item.logo.empty.imageset/Icons & images@2x.png create mode 100644 DashWallet/Resources/AppAssets.xcassets/Explore Dash/image.explore.dash.atm.item.logo.empty.imageset/Icons & images@3x.png diff --git a/DashWallet/Resources/AppAssets.xcassets/Explore Dash/image.explore.dash.atm.item.logo.empty.imageset/Contents.json b/DashWallet/Resources/AppAssets.xcassets/Explore Dash/image.explore.dash.atm.item.logo.empty.imageset/Contents.json new file mode 100644 index 000000000..920cbb155 --- /dev/null +++ b/DashWallet/Resources/AppAssets.xcassets/Explore Dash/image.explore.dash.atm.item.logo.empty.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Icons & images.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Icons & images@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Icons & images@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DashWallet/Resources/AppAssets.xcassets/Explore Dash/image.explore.dash.atm.item.logo.empty.imageset/Icons & images.png b/DashWallet/Resources/AppAssets.xcassets/Explore Dash/image.explore.dash.atm.item.logo.empty.imageset/Icons & images.png new file mode 100644 index 0000000000000000000000000000000000000000..61f386ab9171591c0c85fcc67f9197a3f36edf59 GIT binary patch literal 919 zcmV;I18Dq-P)VaYmb6f)QdMk z>CeG#?axIpIf&Y$#9^sT8~E!A0Xb6NWa-;oZ#Ym74QicypIQ3^ z>suS8C(P#!9XsAd1Bx#f2OA8Y?uEV+o!}LVzdu7X{=$9$-G2prLIVf1H~LGqr?VM| z|M_0j7Jfe_+l>g=a#N~S=?-28k4RED2R}ZMC5L_T!W~&M;n1mG@YB0($P4ru2@X9g z{PoXeNo*{H>juKd&KK#T6o~j7Jt6=ek<3_kmEO*PSMcpppaQftSalF=ZiPxv7WvoB zH5gc#l!9XPwS-I|2Af);>+o5q1S)^_+!Bnxz8OM?&X@}3@@dDAT#TElZGo6jgmfYH zNN2Ys!9d&9qCO|$#KsSBgb*o}cD+EmzhO5f8h=S=deW>X_go?OD`mq?mP;kOlW;dv z0D-0*QBmo4NU@A>o9poS%Uf_HC$A@ILo<;O02{Vi>_l#T2sc-L?U$2(*<2SqhqJkHxT4DOy z1qw9^xVsdN6}e)^KNdEX`}B4?Ysv19#WlT1bD8&eUOv4#hnaS(I}Iu$x8>0>7Y&dg twY>9T15#Lw{5VnKAVDo~#&jb!@EKP!V#%S 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 literal 0 HcmV?d00001 diff --git a/DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift b/DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift index eabd1351f..3496fdf33 100644 --- a/DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift +++ b/DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift @@ -80,6 +80,15 @@ extension ExplorePointOfUse { return Int64.max } } + + var emptyLogoImageName: String { + switch category { + case .merchant(_), .unknown: + return "image.explore.dash.wts.item.logo.empty" + case .atm: + return "image.explore.dash.atm.item.logo.empty" + } + } } // MARK: - ExplorePointOfUse.Atm diff --git a/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift b/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift index 6aff41db1..bc3e18112 100644 --- a/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift +++ b/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift @@ -149,7 +149,7 @@ extension PointOfUseDetailsView { if let str = merchant.logoLocation, let url = URL(string: str) { logoImageView.sd_setImage(with: url, completed: nil) } else { - logoImageView.image = UIImage(named: "image.explore.dash.wts.item.logo.empty") + logoImageView.image = UIImage(named: merchant.emptyLogoImageName) } let subStackView = UIStackView() diff --git a/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift b/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift index ff1447a42..ed2037e66 100644 --- a/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift +++ b/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift @@ -44,7 +44,7 @@ class PointOfUseItemCell: UITableViewCell { if let urlString = pointOfUse.logoLocation, let url = URL(string: urlString) { logoImageView.sd_setImage(with: url) } else { - logoImageView.image = UIImage(named:"image.explore.dash.wts.item.logo.empty") + logoImageView.image = UIImage(named: pointOfUse.emptyLogoImageName) } } diff --git a/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapAnnotationView.swift b/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapAnnotationView.swift index 5c69d5c90..44cbe8afc 100644 --- a/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapAnnotationView.swift +++ b/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapAnnotationView.swift @@ -64,7 +64,7 @@ final class ExploreMapAnnotationView: MKAnnotationView { if let str = pointOfUse.logoLocation, let url = URL(string: str) { imageView.sd_setImage(with: url, completed: nil) } else { - imageView.image = UIImage(named: "image.explore.dash.wts.item.logo.empty") + imageView.image = UIImage(named: pointOfUse.emptyLogoImageName) } } @@ -77,6 +77,7 @@ final class ExploreMapAnnotationView: MKAnnotationView { imageView.layer.masksToBounds = true imageView.layer.borderColor = UIColor.white.cgColor imageView.layer.borderWidth = 2 + imageView.contentMode = .scaleAspectFit addSubview(imageView) imageView.frame = bounds From 08185175001c1ecbeb25b9981976061b8c61fd5c Mon Sep 17 00:00:00 2001 From: pankcuf Date: Tue, 4 Jul 2023 03:16:35 +0700 Subject: [PATCH 36/38] chore: replace --- DashSyncCurrentCommit | 2 +- .../Models/DerivationPathKeysModel.swift | 21 +++++++++---------- Podfile.lock | 8 +++---- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/DashSyncCurrentCommit b/DashSyncCurrentCommit index 44c89d5a2..bcca17ced 100644 --- a/DashSyncCurrentCommit +++ b/DashSyncCurrentCommit @@ -1 +1 @@ -8b44d120647b4c8869afaa913118b07fe13ebb3a +91d9adca3aefc899630c0d962c8e3f8b72e99d90 diff --git a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift index 46f222005..dabe96633 100644 --- a/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift +++ b/DashWallet/Sources/UI/Menu/Tools/Masternode Keys/DerivationPathKeys/Models/DerivationPathKeysModel.swift @@ -163,6 +163,16 @@ extension DerivationPathKeysModel { let key = DSKeyManager.blsPublicKeySerialize(opaqueKey, legacy: false) return DerivationPathKeysItem(info: info, value: key) } + case .publicKeyLegacy: + return autoreleasepool { + guard let phrase = wallet.seedPhraseIfAuthenticated() else { + return DerivationPathKeysItem(info: info, value: NSLocalizedString("Not available", comment: "")) + } + let seed = DSBIP39Mnemonic.sharedInstance()!.deriveKey(fromPhrase: phrase, withPassphrase: nil) + let opaqueKey = self.derivationPath.privateKey(at: index, fromSeed: seed)! + let key = DSKeyManager.blsPublicKeySerialize(opaqueKey, legacy: true) + return DerivationPathKeysItem(info: info, value: key) + } case .privateKey: return autoreleasepool { guard let phrase = wallet.seedPhraseIfAuthenticated() else { @@ -207,17 +217,6 @@ extension DerivationPathKeysModel { return DerivationPathKeysItem(info: info, value: data.base64EncodedString()) } - case .publicKeyLegacy: - return autoreleasepool { - guard let phrase = wallet.seedPhraseIfAuthenticated() else { - return DerivationPathKeysItem(info: info, value: NSLocalizedString("Not available", comment: "")) - } - let seed = DSBIP39Mnemonic.sharedInstance()!.deriveKey(fromPhrase: phrase, withPassphrase: nil) - - let opaqueKey = self.derivationPath.privateKey(at: index, fromSeed: seed)! - let key = DSKeyManager.blsPublicKeySerialize(opaqueKey, legacy: true) - return DerivationPathKeysItem(info: info, value: key) - } } } } diff --git a/Podfile.lock b/Podfile.lock index a3de31e0d..10b9af6b6 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -588,11 +588,11 @@ PODS: - "!ProtoCompiler-gRPCPlugin (~> 1.0)" - DAPI-GRPC/Messages - gRPC-ProtoRPC - - DashSharedCore (0.4.4) + - DashSharedCore (0.4.5) - DashSync (0.1.0): - CocoaLumberjack (= 3.7.2) - DAPI-GRPC (= 0.22.0-dev.8) - - DashSharedCore (= 0.4.4) + - DashSharedCore (= 0.4.5) - DSDynamicOptions (= 0.1.2) - DWAlertController (= 0.2.1) - TinyCborObjc (= 0.4.6) @@ -800,8 +800,8 @@ SPEC CHECKSUMS: CocoaImageHashing: 8656031d0899abe6c1c415827de43e9798189c53 CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da DAPI-GRPC: 138d62523bbfe7e88a39896f1053c0bc12390d9f - DashSharedCore: 2fc4af970b21cc06d423e3c1fe4efa49688065ad - DashSync: d9a3fd99460f4b8718f296b26573c50c0c262163 + DashSharedCore: 6e9af4b23b9ec40ae33040fb9ae4d12b320c70ec + DashSync: a00e5103f25ea8f62632f9c763902df7353b0468 DSDynamicOptions: 347cc5d2c4e080eb3de6a86719ad3d861b82adfc DWAlertController: 5f4cd8adf90336331c054857f709f5f8d4b16a5b Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d From caa65af3a1169ad863b8589076078f9a784b4667 Mon Sep 17 00:00:00 2001 From: pankcuf Date: Tue, 4 Jul 2023 16:40:50 +0700 Subject: [PATCH 37/38] fix: use platformNodeKeysWallet --- DashSyncCurrentCommit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DashSyncCurrentCommit b/DashSyncCurrentCommit index 5dac5055a..683562bad 100644 --- a/DashSyncCurrentCommit +++ b/DashSyncCurrentCommit @@ -1 +1 @@ -9187e626a9d6e997561bb89f6c3f776a405626f4 +925670f0fe63d570d1c504d9e759a883a0883e3a From 4176e4d4a702ea03d8d8370bfd64ee894c6d0091 Mon Sep 17 00:00:00 2001 From: pankcuf Date: Fri, 7 Jul 2023 13:08:42 +0700 Subject: [PATCH 38/38] chore: bump up to DashSync release version v2.4.3 --- DashSyncCurrentCommit | 2 +- Podfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DashSyncCurrentCommit b/DashSyncCurrentCommit index 683562bad..4e0f41691 100644 --- a/DashSyncCurrentCommit +++ b/DashSyncCurrentCommit @@ -1 +1 @@ -925670f0fe63d570d1c504d9e759a883a0883e3a +3bfe78b0722d5a0ca895c0bb24600ccd343d124e diff --git a/Podfile.lock b/Podfile.lock index 10b9af6b6..0e5df03cc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -699,7 +699,7 @@ PODS: - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - PromisesObjC (2.2.0) - - Protobuf (3.23.3) + - Protobuf (3.23.4) - SDWebImage (5.13.2): - SDWebImage/Core (= 5.13.2) - SDWebImage/Core (5.13.2) @@ -822,7 +822,7 @@ SPEC CHECKSUMS: Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - Protobuf: d9c3d7e5a3574aa6a5d521f2d9f733c76d294be8 + Protobuf: c6bc59bbab3d38a71c67f62d7cb7ca8f8ea4eca1 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 SQLite.swift: 903bfa3bc9ab06345fdfbb578e34f47cfcf417da SQLiteMigrationManager.swift: 5383578f5bc8955c06695e8bf04835ee0e6673a8