From c5ec0cacd49dbaf6639c5c29153262f7019bc9ed Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 13 Jun 2023 13:56:12 +0400 Subject: [PATCH 001/123] feat: Prepare DashPay target --- DashPay/dashpay-info.plist | 231 ++ DashWallet.xcodeproj/project.pbxproj | 2002 ++++++++++++++++- .../xcshareddata/xcschemes/dashpay.xcscheme | 79 + Podfile | 23 + Podfile.lock | 2 +- 5 files changed, 2305 insertions(+), 32 deletions(-) create mode 100644 DashPay/dashpay-info.plist create mode 100644 DashWallet.xcodeproj/xcshareddata/xcschemes/dashpay.xcscheme diff --git a/DashPay/dashpay-info.plist b/DashPay/dashpay-info.plist new file mode 100644 index 000000000..7fd17f473 --- /dev/null +++ b/DashPay/dashpay-info.plist @@ -0,0 +1,231 @@ + + + + + BGTaskSchedulerPermittedIdentifiers + + org.dashcore.dashsync.backgroundblocksync + + CALL_BACK_URL_SCHEME + $(CALL_BACK_URL_SCHEME) + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Dash + CFBundleDocumentTypes + + + CFBundleTypeIconFiles + + CFBundleTypeName + Dash Payment Request + LSHandlerRank + Default + LSItemContentTypes + + dash.paymentrequest + + + + CFBundleTypeIconFiles + + CFBundleTypeName + Dash Payment + LSHandlerRank + Default + LSItemContentTypes + + dash.payment + + + + CFBundleTypeIconFiles + + CFBundleTypeName + Dash Payment ACK + LSHandlerRank + Default + LSItemContentTypes + + dash.paymentack + + + + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + com.anypay + CFBundleURLSchemes + + pay + + + + CFBundleTypeRole + Editor + CFBundleURLName + dash + CFBundleURLSchemes + + dash + + + + CFBundleTypeRole + Editor + CFBundleURLName + org.dashfoundation.dash + CFBundleURLSchemes + + dashwallet + + + + CFBundleTypeRole + Editor + CFBundleURLName + dashid + CFBundleURLSchemes + + dashid + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + CLIENT_ID + $(CLIENT_ID) + CLIENT_SECRET + $(CLIENT_SECRET) + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + dashcontrol + dashdirect + + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + + NFCReaderUsageDescription + Used to interact with NFC payment stations. + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSCameraUsageDescription + Used to scan barcodes + NSFaceIDUsageDescription + Used to authorize payments + NSLocationAlwaysAndWhenInUseUsageDescription + We will use your location to determine where you are. Your location will never be recorded or shared with anyone. + NSLocationWhenInUseUsageDescription + We will use your location to determine where you are. Your location will never be recorded or shared with anyone. + NSPhotoLibraryAddUsageDescription + Used to save media data + NSPhotoLibraryUsageDescription + Used to save media data + REDIRECT_URI + $(REDIRECT_URI) + UIBackgroundModes + + fetch + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + UTImportedTypeDeclarations + + + UTTypeConformsTo + + public.data + + UTTypeDescription + Dash Payment Request + UTTypeIdentifier + dash.paymentrequest + UTTypeTagSpecification + + public.mime-type + application/dash-paymentrequest + + + + UTTypeConformsTo + + public.data + + UTTypeDescription + Dash Payment + UTTypeIdentifier + dash.payment + UTTypeTagSpecification + + public.mime-type + application/dash-payment + + + + UTTypeConformsTo + + public.data + + UTTypeDescription + Dash Payment ACK + UTTypeIdentifier + dash.paymentack + UTTypeTagSpecification + + public.mime-type + application/dash-paymentack + + + + com.apple.developer.nfc.readersession.iso7816.select-identifiers + + A0000002471001 + 00000000000000 + + + diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index eed0363c7..6b8aab8d9 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 48; + objectVersion = 53; objects = { /* Begin PBXBuildFile section */ @@ -648,6 +648,8 @@ 75D5F3CE191EC270004AB296 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 75D5F3CD191EC270004AB296 /* main.m */; }; 75E83CF61B5F997A0038FB70 /* coinflip.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 75E83CF51B5F997A0038FB70 /* coinflip.aiff */; }; 7EE06A55AFE483FA7824B06E /* libPods-DashWalletTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BEAE29AA65F429DD9EF1A1A7 /* libPods-DashWalletTests.a */; }; + A90D08EA4AA9019A2D806A9C /* libPods-dashwallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17427514C25A58AB4AEDF999 /* libPods-dashwallet.a */; }; + AEC28D84403A194A6C7D8C5A /* libPods-dashpay.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CE89DF632BC53160BB8FBED1 /* libPods-dashpay.a */; }; BA4A72671BE11AFA00E39C01 /* BRAWWeakTimerTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4A72661BE11AFA00E39C01 /* BRAWWeakTimerTarget.swift */; }; BA54D3CE1B2EA74000C9CB28 /* TodayExtensionAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BA54D3CD1B2EA74000C9CB28 /* TodayExtensionAssets.xcassets */; }; BA6645961BD924EB007A6BB1 /* BRAWTransactionRowControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA6645951BD924EB007A6BB1 /* BRAWTransactionRowControl.swift */; }; @@ -691,6 +693,698 @@ C94F5E8E29D404850034FD57 /* ShortcutCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8D29D404850034FD57 /* ShortcutCell.swift */; }; C94F5E9029D4060A0034FD57 /* ShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8F29D4060A0034FD57 /* ShortcutsView.swift */; }; C9903A5429E6A5F600535A4E /* KeysOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9903A5329E6A5F600535A4E /* KeysOverviewViewController.swift */; }; + C9D2C6942A320AA000D15901 /* DWReceiveModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3416123A81E8B004E37A7 /* DWReceiveModelStub.m */; }; + C9D2C6952A320AA000D15901 /* DWBaseAdvancedSecurityModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3417E23A92A2A004E37A7 /* DWBaseAdvancedSecurityModel.m */; }; + C9D2C6962A320AA000D15901 /* DWBaseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE6D22DA357D00C99324 /* DWBaseViewController.m */; }; + C9D2C6972A320AA000D15901 /* DWBiometricAuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44314622CF82D9009BAF7F /* DWBiometricAuthViewController.m */; }; + C9D2C6982A320AA000D15901 /* CrowdNodeWebService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11517C86294B0FED004FC7BF /* CrowdNodeWebService.swift */; }; + C9D2C6992A320AA000D15901 /* DWStartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB231D12196E25D00A6E7E6 /* DWStartViewController.m */; }; + C9D2C69A2A320AA000D15901 /* CrowdNodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11517C822949E6A3004FC7BF /* CrowdNodeAPI.swift */; }; + C9D2C69B2A320AA000D15901 /* DWStartModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB231D62196E5CF00A6E7E6 /* DWStartModel.m */; }; + C9D2C69C2A320AA000D15901 /* DWAdvancedSecurityModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3417923A929B6004E37A7 /* DWAdvancedSecurityModelStub.m */; }; + C9D2C69D2A320AA000D15901 /* Foundation+Bitcoin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709C32028818D5400B4BD48 /* Foundation+Bitcoin.swift */; }; + C9D2C69E2A320AA000D15901 /* AtmDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BBC28C1305E00490F5E /* AtmDetailsView.swift */; }; + C9D2C69F2A320AA000D15901 /* DWPlaceholderFormTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE3A2230FF4600956D5F /* DWPlaceholderFormTableViewCell.m */; }; + C9D2C6A02A320AA000D15901 /* DWTitleDetailCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69F12316B93F001B8C90 /* DWTitleDetailCellView.m */; }; + C9D2C6A12A320AA000D15901 /* AmountInputControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661B728FE907300028A8D /* AmountInputControl.swift */; }; + C9D2C6A22A320AA000D15901 /* DWSegmentSliderFormTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F640B238D5BBB00A9B505 /* DWSegmentSliderFormTableViewCell.m */; }; + C9D2C6A32A320AA000D15901 /* ApiCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B8449328F27C460082770C /* ApiCode.swift */; }; + C9D2C6A42A320AA000D15901 /* CoinbaseEntryPointModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B91290701470003E8AB /* CoinbaseEntryPointModel.swift */; }; + C9D2C6A52A320AA000D15901 /* SyncingAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451EA2A0BF10B00825057 /* SyncingAlertViewController.swift */; }; + C9D2C6A62A320AA000D15901 /* DWDashPayConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EEFF2419310C002C32F3 /* DWDashPayConstants.m */; }; + C9D2C6A72A320AA000D15901 /* CrowdNodeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B8449528F5B9F80082770C /* CrowdNodeResponse.swift */; }; + C9D2C6A82A320AA000D15901 /* FileManager+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BB128BFF61A00490F5E /* FileManager+DashWallet.swift */; }; + C9D2C6A92A320AA000D15901 /* PointOfUseItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BCB28C1305E00490F5E /* PointOfUseItemCell.swift */; }; + C9D2C6AA2A320AA000D15901 /* DSTransaction+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47083B3129892D770010AF71 /* DSTransaction+DashWallet.swift */; }; + C9D2C6AB2A320AA000D15901 /* DWConfirmUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B66323CF09000016F221 /* DWConfirmUsernameViewController.m */; }; + C9D2C6AC2A320AA000D15901 /* PayableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42F9E29DA82E5001BC549 /* PayableViewController.swift */; }; + C9D2C6AD2A320AA000D15901 /* HairlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1428C6378E00490F5E /* HairlineView.swift */; }; + C9D2C6AE2A320AA000D15901 /* DWInitialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E9423A3F75F006A2A59 /* DWInitialViewController.m */; }; + C9D2C6AF2A320AA000D15901 /* PortalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751136B28D9A3DB00223B77 /* PortalViewController.swift */; }; + C9D2C6B02A320AA000D15901 /* DWSettingsMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BD52348CB6600451078 /* DWSettingsMenuViewController.m */; }; + C9D2C6B12A320AA000D15901 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1D28C7491C00490F5E /* AboutViewController.swift */; }; + C9D2C6B22A320AA000D15901 /* UISpringTimingParameters+DWInit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69D92314727F001B8C90 /* UISpringTimingParameters+DWInit.m */; }; + C9D2C6B32A320AA000D15901 /* DWDPNewIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9D72AB249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.m */; }; + C9D2C6B42A320AA000D15901 /* DWToolsMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BDC2348DC0A00451078 /* DWToolsMenuModel.m */; }; + C9D2C6B52A320AA000D15901 /* DWNotificationsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3CA202B247E4AF300158074 /* DWNotificationsViewController.m */; }; + C9D2C6B62A320AA000D15901 /* NumberKeyboardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661AC28F972BD00028A8D /* NumberKeyboardButton.swift */; }; + C9D2C6B72A320AA000D15901 /* IsDefaultEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11AE3DD72997C599000856EE /* IsDefaultEmail.swift */; }; + C9D2C6B82A320AA000D15901 /* DWHomeViewController+DWSecureWalletDelegateImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7DC223266C8400BA8C6A /* DWHomeViewController+DWSecureWalletDelegateImpl.m */; }; + C9D2C6B92A320AA000D15901 /* DWNotificationsProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CB42493D87D00F4A689 /* DWNotificationsProvider.m */; }; + C9D2C6BA2A320AA000D15901 /* DWUpholdAccountObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFF292233E60F00956D5F /* DWUpholdAccountObject.m */; }; + C9D2C6BB2A320AA000D15901 /* DWFormTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE2E2230FF4600956D5F /* DWFormTableViewController.m */; }; + C9D2C6BC2A320AA000D15901 /* WalletKeysOverviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909614C29EFF7D600002D82 /* WalletKeysOverviewModel.swift */; }; + C9D2C6BD2A320AA000D15901 /* UIAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751137428DAF28800223B77 /* UIAssembly.swift */; }; + C9D2C6BE2A320AA000D15901 /* DWLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BAC28BFAE6700490F5E /* DWLocationManager.swift */; }; + C9D2C6BF2A320AA000D15901 /* FetchingNextPageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BCE28C1305E00490F5E /* FetchingNextPageCell.swift */; }; + C9D2C6C02A320AA000D15901 /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47081196298CF20B003FCA3D /* Transaction.swift */; }; + C9D2C6C12A320AA000D15901 /* AllMerchantLocationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BB728C1305E00490F5E /* AllMerchantLocationsViewController.swift */; }; + C9D2C6C22A320AA000D15901 /* DWPaymentOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD84C23180E7E00A96B62 /* DWPaymentOutput.m */; }; + C9D2C6C32A320AA000D15901 /* DWPayModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3415D23A8133A004E37A7 /* DWPayModelStub.m */; }; + C9D2C6C42A320AA000D15901 /* DWRecoverContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFF0230531DA00C475EB /* DWRecoverContentView.m */; }; + C9D2C6C52A320AA000D15901 /* DWSeedPhraseViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE8F22DD070000C99324 /* DWSeedPhraseViewLayout.m */; }; + C9D2C6C62A320AA000D15901 /* Coinbase+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477F500F2950A55A003C7508 /* Coinbase+Error.swift */; }; + C9D2C6C72A320AA000D15901 /* DWPaymentOutput+DWView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A63003E2327B4BB00827825 /* DWPaymentOutput+DWView.m */; }; + C9D2C6C82A320AA000D15901 /* UIView+DWRecursiveSubview.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACD53ED234C9D8E00650AD3 /* UIView+DWRecursiveSubview.m */; }; + C9D2C6C92A320AA000D15901 /* AddressStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11860922297598B400279FCC /* AddressStatus.swift */; }; + C9D2C6CA2A320AA000D15901 /* ExtendedPublicKeysModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91E919629FBACE6003E7883 /* ExtendedPublicKeysModel.swift */; }; + C9D2C6CB2A320AA000D15901 /* OrderPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CAD3297022B300F63AC4 /* OrderPreviewViewController.swift */; }; + C9D2C6CC2A320AA000D15901 /* ActivePaymentMethodView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478C982E294305D800FAA0F0 /* ActivePaymentMethodView.swift */; }; + C9D2C6CD2A320AA000D15901 /* BasicInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CDEEC62949DC38008AE06D /* BasicInfoController.swift */; }; + C9D2C6CE2A320AA000D15901 /* DWPreviewSeedPhraseModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CEA722E0C8C800C99324 /* DWPreviewSeedPhraseModel.m */; }; + C9D2C6CF2A320AA000D15901 /* DWModalPresentationAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69CC23142F90001B8C90 /* DWModalPresentationAnimation.m */; }; + C9D2C6D02A320AA000D15901 /* UINavigationController+CrowdNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110C679929227948006B580C /* UINavigationController+CrowdNode.swift */; }; + C9D2C6D12A320AA000D15901 /* DWReceiveModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E792302E67400FF8653 /* DWReceiveModel.m */; }; + C9D2C6D22A320AA000D15901 /* TransactionWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D092905413F00D406C1 /* TransactionWrapper.swift */; }; + C9D2C6D32A320AA000D15901 /* Transactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 479DBDDC2995168C00F30AF1 /* Transactions.swift */; }; + C9D2C6D42A320AA000D15901 /* DWRecoverTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFFA2305464C00C475EB /* DWRecoverTextView.m */; }; + C9D2C6D52A320AA000D15901 /* TxUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709C314287EA11900B4BD48 /* TxUserInfo.swift */; }; + C9D2C6D62A320AA000D15901 /* ExtendedPublicKeysViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91E91AD29FFC8A1003E7883 /* ExtendedPublicKeysViewController.swift */; }; + C9D2C6D72A320AA000D15901 /* DWContactsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3172480E35A001D74F9 /* DWContactsFetchedDataSource.m */; }; + C9D2C6D82A320AA000D15901 /* AtmListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BDD28C1305E00490F5E /* AtmListViewController.swift */; }; + C9D2C6D92A320AA000D15901 /* TxDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A5145F2848F75B005A8E3E /* TxDetailViewController.swift */; }; + C9D2C6DA2A320AA000D15901 /* NSAttributedString+Builder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13E9299E5396006903F1 /* NSAttributedString+Builder.swift */; }; + C9D2C6DB2A320AA000D15901 /* DWPreviewSeedPhraseContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE7922DB5F5200C99324 /* DWPreviewSeedPhraseContentView.m */; }; + C9D2C6DC2A320AA000D15901 /* DWPhoneWCSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A0C237EE89B0097A7B5 /* DWPhoneWCSessionManager.m */; }; + C9D2C6DD2A320AA000D15901 /* NumberKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661AA28F9707A00028A8D /* NumberKeyboard.swift */; }; + C9D2C6DE2A320AA000D15901 /* DWSeedPhraseTitledView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431E522D73617009BAF7F /* DWSeedPhraseTitledView.m */; }; + C9D2C6DF2A320AA000D15901 /* UICollectionView+DWDPItemDequeue.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF38824829AF2001D74F9 /* UICollectionView+DWDPItemDequeue.m */; }; + C9D2C6E02A320AA000D15901 /* CrowdNodeTopUpTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D0B2907C61400D406C1 /* CrowdNodeTopUpTx.swift */; }; + C9D2C6E12A320AA000D15901 /* BackupInfoItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB329DD86FB001BC549 /* BackupInfoItemView.swift */; }; + C9D2C6E22A320AA000D15901 /* DWContactsSearchInfoHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2D42476886D0001624F /* DWContactsSearchInfoHeaderView.m */; }; + C9D2C6E32A320AA000D15901 /* AccountCreatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118A1392291AA21B002641E4 /* AccountCreatingController.swift */; }; + C9D2C6E42A320AA000D15901 /* UIView+DWReuseHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E534322F02BC300E5168A /* UIView+DWReuseHelper.m */; }; + C9D2C6E52A320AA000D15901 /* DWExtendedContainerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E9B23A53B8E006A2A59 /* DWExtendedContainerViewController.m */; }; + C9D2C6E62A320AA000D15901 /* CrowdNodeEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11517C842949E6F0004FC7BF /* CrowdNodeEndpoint.swift */; }; + C9D2C6E72A320AA000D15901 /* ServiceEntryPointModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F71317428F4365C0072F454 /* ServiceEntryPointModel.swift */; }; + C9D2C6E82A320AA000D15901 /* ExploreDash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8B9428BFACA100490F5E /* ExploreDash.swift */; }; + C9D2C6E92A320AA000D15901 /* DWToolsMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BDF2348DC1900451078 /* DWToolsMenuViewController.m */; }; + C9D2C6EA2A320AA000D15901 /* DWUsernamePendingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B66A23CF0F390016F221 /* DWUsernamePendingViewController.m */; }; + C9D2C6EB2A320AA000D15901 /* UpholdAmountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4789D22E2981067600BAFEFA /* UpholdAmountModel.swift */; }; + C9D2C6EC2A320AA000D15901 /* DWModalInteractiveTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69BC23140779001B8C90 /* DWModalInteractiveTransition.m */; }; + C9D2C6ED2A320AA000D15901 /* DWRequestsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5622487E16400B52F14 /* DWRequestsViewController.m */; }; + C9D2C6EE2A320AA000D15901 /* DWUpholdMainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE712230FF4600956D5F /* DWUpholdMainViewController.m */; }; + C9D2C6EF2A320AA000D15901 /* DWDPUserObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36224825A0C001D74F9 /* DWDPUserObject.m */; }; + C9D2C6F02A320AA000D15901 /* UIImage+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = BAA4843B1B3EFFAF0075C749 /* UIImage+Utils.m */; }; + C9D2C6F12A320AA000D15901 /* CNCreateAccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47083B3529893E700010AF71 /* CNCreateAccountCell.swift */; }; + C9D2C6F22A320AA000D15901 /* DWSetupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4430ED22CBBAC9009BAF7F /* DWSetupViewController.m */; }; + C9D2C6F32A320AA000D15901 /* ExploreDatabaseConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BA928BFAE5800490F5E /* ExploreDatabaseConnection.swift */; }; + C9D2C6F42A320AA000D15901 /* WithdrawalLimitsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C4B296C51B500788E18 /* WithdrawalLimitsController.swift */; }; + C9D2C6F52A320AA000D15901 /* CoinbaseTransactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA928C896BC000427E7 /* CoinbaseTransactionResponse.swift */; }; + C9D2C6F62A320AA000D15901 /* DWIncomingFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3142480DA51001D74F9 /* DWIncomingFetchedDataSource.m */; }; + C9D2C6F72A320AA000D15901 /* TxDetailModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709C31A288020DE00B4BD48 /* TxDetailModel.swift */; }; + C9D2C6F82A320AA000D15901 /* NewAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BD737D28E6BD7400A34022 /* NewAccountViewController.swift */; }; + C9D2C6F92A320AA000D15901 /* PointOfUseListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD728C1305E00490F5E /* PointOfUseListModel.swift */; }; + C9D2C6FA2A320AA000D15901 /* NSPredicate+DWFullTextSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2CE247585C10001624F /* NSPredicate+DWFullTextSearch.m */; }; + C9D2C6FB2A320AA000D15901 /* TxReclassifyTransactionsWhereToChangeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F2C6832860513900C2B774 /* TxReclassifyTransactionsWhereToChangeViewController.swift */; }; + C9D2C6FC2A320AA000D15901 /* DWLocalCurrencyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7C15234B763600451078 /* DWLocalCurrencyViewController.m */; }; + C9D2C6FD2A320AA000D15901 /* BadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451FC2A0CC4A300825057 /* BadgeView.swift */; }; + C9D2C6FE2A320AA000D15901 /* HomeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451F42A0CAC9400825057 /* HomeHeaderView.swift */; }; + C9D2C6FF2A320AA000D15901 /* ProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451F82A0CB08900825057 /* ProgressView.swift */; }; + C9D2C7002A320AA000D15901 /* DWAmountInputValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2120E92214566A009906DC /* DWAmountInputValidator.m */; }; + C9D2C7012A320AA000D15901 /* ExplorePointOfUseListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD228C1305E00490F5E /* ExplorePointOfUseListViewController.swift */; }; + C9D2C7022A320AA000D15901 /* CrowdNodeWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 117ECC7D29742A42003D798E /* CrowdNodeWebViewController.swift */; }; + C9D2C7032A320AA000D15901 /* UIViewController+DWDisplayError.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE4736A241BC5E300804DD4 /* UIViewController+DWDisplayError.m */; }; + C9D2C7042A320AA000D15901 /* BRAppleWatchData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A08237EE89B0097A7B5 /* BRAppleWatchData.m */; }; + C9D2C7052A320AA000D15901 /* DWRequestAmountContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD85C231939FE00A96B62 /* DWRequestAmountContentView.m */; }; + C9D2C7062A320AA000D15901 /* DWOnboardingModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3416E23A8DCFB004E37A7 /* DWOnboardingModel.m */; }; + C9D2C7072A320AA000D15901 /* AmountInputTypeSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B7628FFFFAD0003E8AB /* AmountInputTypeSwitcher.swift */; }; + C9D2C7082A320AA000D15901 /* DWPlaceholderFormCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE3D2230FF4600956D5F /* DWPlaceholderFormCellModel.m */; }; + C9D2C7092A320AA000D15901 /* DWOverlapControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E4E22FED47E00FF8653 /* DWOverlapControl.m */; }; + C9D2C70A2A320AA000D15901 /* CoinsToAddressTxFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D052905200300D406C1 /* CoinsToAddressTxFilter.swift */; }; + C9D2C70B2A320AA000D15901 /* DWDemoAppRootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3417223A926C9004E37A7 /* DWDemoAppRootViewController.m */; }; + C9D2C70C2A320AA000D15901 /* DWUpholdLogoutTutorialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE652230FF4600956D5F /* DWUpholdLogoutTutorialViewController.m */; }; + C9D2C70D2A320AA000D15901 /* CBAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF46A429654E190067B6EE /* CBAccount.swift */; }; + C9D2C70E2A320AA000D15901 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AF180429070B720025803E /* Types.swift */; }; + C9D2C70F2A320AA000D15901 /* DWConfirmPaymentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69EA2316B344001B8C90 /* DWConfirmPaymentViewController.m */; }; + C9D2C7102A320AA000D15901 /* DWDPGenericItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF33F2481A1B2001D74F9 /* DWDPGenericItemView.m */; }; + C9D2C7112A320AA000D15901 /* Coinbase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4774DCDA28F3FA9C008CF87D /* Coinbase.swift */; }; + C9D2C7122A320AA000D15901 /* SyncModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452072A11F28600825057 /* SyncModel.swift */; }; + C9D2C7132A320AA000D15901 /* ReceiveContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FAA29DC1098001BC549 /* ReceiveContentView.swift */; }; + C9D2C7142A320AA000D15901 /* AmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B7428FFD1D10003E8AB /* AmountView.swift */; }; + C9D2C7152A320AA000D15901 /* DWBasePayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD85423191C8500A96B62 /* DWBasePayViewController.m */; }; + C9D2C7162A320AA000D15901 /* CrowdNodeTransferModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193FF3529602835004EA8D7 /* CrowdNodeTransferModel.swift */; }; + C9D2C7172A320AA000D15901 /* DWSetPinViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44312522CCC14F009BAF7F /* DWSetPinViewController.m */; }; + C9D2C7182A320AA000D15901 /* ConfirmOrderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F4B6C6294842DF00AED4C9 /* ConfirmOrderController.swift */; }; + C9D2C7192A320AA000D15901 /* DWUsernameHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1AE78D23F4668B00179A6E /* DWUsernameHeaderView.m */; }; + C9D2C71A2A320AA000D15901 /* DWDashPayContactsActions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3982482E32E001D74F9 /* DWDashPayContactsActions.m */; }; + C9D2C71B2A320AA000D15901 /* DWResetWalletInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A10EB432358D2CA00C38B61 /* DWResetWalletInfoViewController.m */; }; + C9D2C71C2A320AA000D15901 /* DWLockScreenModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD46231232A286000C71557 /* DWLockScreenModel.m */; }; + C9D2C71D2A320AA000D15901 /* DWVerifySeedPhraseContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE8922DC9C6F00C99324 /* DWVerifySeedPhraseContentView.m */; }; + C9D2C71E2A320AA000D15901 /* Coinbase+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2A2E1293DCCEA00938DB7 /* Coinbase+Constants.swift */; }; + C9D2C71F2A320AA000D15901 /* PointOfUseListSearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C0628C2274200490F5E /* PointOfUseListSearchCell.swift */; }; + C9D2C7202A320AA000D15901 /* ServiceDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4774DCE028F44BA3008CF87D /* ServiceDataProvider.swift */; }; + C9D2C7212A320AA000D15901 /* PaymentButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E62A0BA16400825057 /* PaymentButton.swift */; }; + C9D2C7222A320AA000D15901 /* DWPreviewSeedPhraseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431DA22D675CD009BAF7F /* DWPreviewSeedPhraseViewController.m */; }; + C9D2C7232A320AA000D15901 /* DashPayProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451FA2A0CC2A800825057 /* DashPayProfileView.swift */; }; + C9D2C7242A320AA000D15901 /* WKWebView+CrowdNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110D1780298BA9AF005BEB30 /* WKWebView+CrowdNode.swift */; }; + C9D2C7252A320AA000D15901 /* SyncingActivityMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA3AFE29350929008D58DC /* SyncingActivityMonitor.swift */; }; + C9D2C7262A320AA000D15901 /* EmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FAF29DC27F4001BC549 /* EmptyView.swift */; }; + C9D2C7272A320AA000D15901 /* DWConfirmSendPaymentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFCB9BD23BE3C0800FF59A6 /* DWConfirmSendPaymentViewController.m */; }; + C9D2C7282A320AA000D15901 /* DWQRScanModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC92C891FEB0B8B008CAEE0 /* DWQRScanModel.m */; }; + C9D2C7292A320AA000D15901 /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3DC87023972331004B3DBA /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.m */; }; + C9D2C72A2A320AA000D15901 /* SyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451F62A0CAE1300825057 /* SyncView.swift */; }; + C9D2C72B2A320AA000D15901 /* SuccessfulOperationStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47522F4F2927CB9000EE143E /* SuccessfulOperationStatusViewController.swift */; }; + C9D2C72C2A320AA000D15901 /* CustodialSwapsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E94B99296D7F99000FE68E /* CustodialSwapsModel.swift */; }; + C9D2C72D2A320AA000D15901 /* DWLocalCurrencyTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7C1F234B79B700451078 /* DWLocalCurrencyTableViewCell.m */; }; + C9D2C72E2A320AA000D15901 /* DWRecoverModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFFD2305763F00C475EB /* DWRecoverModel.m */; }; + C9D2C72F2A320AA000D15901 /* DWCenteredTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431C722D4D92A009BAF7F /* DWCenteredTableView.m */; }; + C9D2C7302A320AA000D15901 /* DWOnboardingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E6E23A26663006A2A59 /* DWOnboardingViewController.m */; }; + C9D2C7312A320AA000D15901 /* DWUsernameValidationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DC923F6BD5200CDA1EE /* DWUsernameValidationView.m */; }; + C9D2C7322A320AA000D15901 /* CrowdNode+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C51296D620D00788E18 /* CrowdNode+UserDefaults.swift */; }; + C9D2C7332A320AA000D15901 /* DWDPAcceptedRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF37124826CDF001D74F9 /* DWDPAcceptedRequestNotificationObject.m */; }; + C9D2C7342A320AA000D15901 /* DWPinView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44313822CE2CB9009BAF7F /* DWPinView.m */; }; + C9D2C7352A320AA000D15901 /* DWUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DCD23F6C01A00CDA1EE /* DWUsernameValidationRule.m */; }; + C9D2C7362A320AA000D15901 /* DWBaseReceiveModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3416423A8213C004E37A7 /* DWBaseReceiveModel.m */; }; + C9D2C7372A320AA000D15901 /* DWSegmentSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F6411238D650200A9B505 /* DWSegmentSlider.m */; }; + C9D2C7382A320AA000D15901 /* CoinbasePlaceBuyOrderRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA528C896BC000427E7 /* CoinbasePlaceBuyOrderRequest.swift */; }; + C9D2C7392A320AA000D15901 /* CoinbaseAmount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFB128C896BC000427E7 /* CoinbaseAmount.swift */; }; + C9D2C73A2A320AA000D15901 /* DWDPRegistrationErrorTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFF01DD243F74BE003718DC /* DWDPRegistrationErrorTableViewCell.m */; }; + C9D2C73B2A320AA000D15901 /* SendAmountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B30D7D29102F6E0080C326 /* SendAmountModel.swift */; }; + C9D2C73C2A320AA000D15901 /* DWDecimalInputValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFF252233E5CF00956D5F /* DWDecimalInputValidator.m */; }; + C9D2C73D2A320AA000D15901 /* PaymentMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF46A729655E8E0067B6EE /* PaymentMethods.swift */; }; + C9D2C73E2A320AA000D15901 /* DWTitleDetailCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7D962322D0BC00BA8C6A /* DWTitleDetailCellModel.m */; }; + C9D2C73F2A320AA000D15901 /* BuyDashModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478C98282942DDB800FAA0F0 /* BuyDashModel.swift */; }; + C9D2C7402A320AA000D15901 /* MerchantListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD328C1305E00490F5E /* MerchantListViewController.swift */; }; + C9D2C7412A320AA000D15901 /* DWUpholdCardObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE012230FF2B00956D5F /* DWUpholdCardObject.m */; }; + C9D2C7422A320AA000D15901 /* DWIntrinsicCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD72122F9B571008C7BC9 /* DWIntrinsicCollectionView.m */; }; + C9D2C7432A320AA000D15901 /* DWRootModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E7E23A30609006A2A59 /* DWRootModelStub.m */; }; + C9D2C7442A320AA000D15901 /* DWDPIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3652482666C001D74F9 /* DWDPIncomingRequestObject.m */; }; + C9D2C7452A320AA000D15901 /* UIDevice+DashWallet.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD6ED22F48159008C7BC9 /* UIDevice+DashWallet.m */; }; + C9D2C7462A320AA000D15901 /* CBAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF46AB2965B65B0067B6EE /* CBAccountManager.swift */; }; + C9D2C7472A320AA000D15901 /* FailedOperationStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47522F512927CBEE00EE143E /* FailedOperationStatusViewController.swift */; }; + C9D2C7482A320AA000D15901 /* UIPanGestureRecognizer+DWProjected.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69C22314126D001B8C90 /* UIPanGestureRecognizer+DWProjected.m */; }; + C9D2C7492A320AA000D15901 /* DWCurrencyObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA87CF926E5681100F0CEA6 /* DWCurrencyObject.m */; }; + C9D2C74A2A320AA000D15901 /* ShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8F29D4060A0034FD57 /* ShortcutsView.swift */; }; + C9D2C74B2A320AA000D15901 /* DWFormSectionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE3E2230FF4600956D5F /* DWFormSectionModel.m */; }; + C9D2C74C2A320AA000D15901 /* DWActionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = FB43BC77211CD15500BC9879 /* DWActionButton.m */; }; + C9D2C74D2A320AA000D15901 /* CoinbasePlaceBuyOrderResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA628C896BC000427E7 /* CoinbasePlaceBuyOrderResponse.swift */; }; + C9D2C74E2A320AA000D15901 /* ExploreMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BDC28C1305E00490F5E /* ExploreMapView.swift */; }; + C9D2C74F2A320AA000D15901 /* DWSeedPhraseRow.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE9322DD078600C99324 /* DWSeedPhraseRow.m */; }; + C9D2C7502A320AA000D15901 /* ServiceOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F71317828F436ED0072F454 /* ServiceOverviewViewController.swift */; }; + C9D2C7512A320AA000D15901 /* AccountListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CAC3296EFE9500F63AC4 /* AccountListModel.swift */; }; + C9D2C7522A320AA000D15901 /* DWDPSearchItemsFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF37F248280CE001D74F9 /* DWDPSearchItemsFactory.m */; }; + C9D2C7532A320AA000D15901 /* DWRecoverWalletCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3DC86D239717C2004B3DBA /* DWRecoverWalletCommand.m */; }; + C9D2C7542A320AA000D15901 /* DWSwitcherFormTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE322230FF4600956D5F /* DWSwitcherFormTableViewCell.m */; }; + C9D2C7552A320AA000D15901 /* DWPaymentInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C699B23104588001B8C90 /* DWPaymentInput.m */; }; + C9D2C7562A320AA000D15901 /* DWBaseContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE9549C23D0C4F4003612B3 /* DWBaseContactsViewController.m */; }; + C9D2C7572A320AA000D15901 /* OnlineAccountDetailsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114D16B529812730009A124C /* OnlineAccountDetailsController.swift */; }; + C9D2C7582A320AA000D15901 /* UIApplication+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E82A0BDAE700825057 /* UIApplication+DashWallet.swift */; }; + C9D2C7592A320AA000D15901 /* OnlineAccountInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110D1783298E68A8005BEB30 /* OnlineAccountInfoController.swift */; }; + C9D2C75A2A320AA000D15901 /* PaymentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A50F372912D9DC00C70123 /* PaymentController.swift */; }; + C9D2C75B2A320AA000D15901 /* CALayer+MBAnimationPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADB396B242615C200A6F898 /* CALayer+MBAnimationPersistence.m */; }; + C9D2C75C2A320AA000D15901 /* UIView+DWAnimations.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDEF2230FF1A00956D5F /* UIView+DWAnimations.m */; }; + C9D2C75D2A320AA000D15901 /* VerifiedSuccessfullyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114573A32949B221009DCF27 /* VerifiedSuccessfullyViewController.swift */; }; + C9D2C75E2A320AA000D15901 /* KeyboardHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193FF3829606151004EA8D7 /* KeyboardHeader.swift */; }; + C9D2C75F2A320AA000D15901 /* DWUpholdOTPViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE542230FF4600956D5F /* DWUpholdOTPViewController.m */; }; + C9D2C7602A320AA000D15901 /* DWAppGroupOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E7C23034AC100FF8653 /* DWAppGroupOptions.m */; }; + C9D2C7612A320AA000D15901 /* DWTransactionListDataItemObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913EA323A799F3006A2A59 /* DWTransactionListDataItemObject.m */; }; + C9D2C7622A320AA000D15901 /* DWUpholdAPIProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE002230FF2B00956D5F /* DWUpholdAPIProvider.m */; }; + C9D2C7632A320AA000D15901 /* DWConfirmUsernameContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EEFA2417E30F002C32F3 /* DWConfirmUsernameContentView.m */; }; + C9D2C7642A320AA000D15901 /* BasePageSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F2C67C28602D4F00C2B774 /* BasePageSheetViewController.swift */; }; + C9D2C7652A320AA000D15901 /* DWListCollectionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A827B7124B5CA1800A42042 /* DWListCollectionLayout.m */; }; + C9D2C7662A320AA000D15901 /* CrowdNodeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D0329051F9900D406C1 /* CrowdNodeRequest.swift */; }; + C9D2C7672A320AA000D15901 /* CoinbaseExchangeRateResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFAF28C896BC000427E7 /* CoinbaseExchangeRateResponse.swift */; }; + C9D2C7682A320AA000D15901 /* DWDPTxItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6688FC24BCB739008E10F0 /* DWDPTxItemView.m */; }; + C9D2C7692A320AA000D15901 /* DWConfirmPaymentContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69EE2316B7F5001B8C90 /* DWConfirmPaymentContentView.m */; }; + C9D2C76A2A320AA000D15901 /* CrowdNodePortalItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 117728A2297A7D24006F1553 /* CrowdNodePortalItem.swift */; }; + C9D2C76B2A320AA000D15901 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661B528FE75A700028A8D /* BaseViewController.swift */; }; + C9D2C76C2A320AA000D15901 /* AccountListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CABF296EFD2900F63AC4 /* AccountListController.swift */; }; + C9D2C76D2A320AA000D15901 /* DWUpholdTransactionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDFD2230FF2B00956D5F /* DWUpholdTransactionObject.m */; }; + C9D2C76E2A320AA000D15901 /* DWBaseSeedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AF26F3A230C0E4C007F9228 /* DWBaseSeedViewController.m */; }; + C9D2C76F2A320AA000D15901 /* DWProgressAnimator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E4C22FEBF7A00FF8653 /* DWProgressAnimator.mm */; }; + C9D2C7702A320AA000D15901 /* CrowdNodeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E47BA728EAE7AD0097CFA0 /* CrowdNodeModel.swift */; }; + C9D2C7712A320AA000D15901 /* UIDevice+Compatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4730586D295AB3BD004641DA /* UIDevice+Compatibility.swift */; }; + C9D2C7722A320AA000D15901 /* DWSeedWordView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431E022D7219E009BAF7F /* DWSeedWordView.m */; }; + C9D2C7732A320AA000D15901 /* DWSecurityStatusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7F3B1E238C651200DEA3EF /* DWSecurityStatusView.m */; }; + C9D2C7742A320AA000D15901 /* DWRequestsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E55F2487E13F00B52F14 /* DWRequestsContentViewController.m */; }; + C9D2C7752A320AA000D15901 /* DWAnimatedShapeLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8D0231D33EA00A96B62 /* DWAnimatedShapeLayer.m */; }; + C9D2C7762A320AA000D15901 /* DWUserProfileNavigationTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D702462D4AD001D7C0D /* DWUserProfileNavigationTitleView.m */; }; + C9D2C7772A320AA000D15901 /* DWTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DC623F6928C00CDA1EE /* DWTextField.m */; }; + C9D2C7782A320AA000D15901 /* DWScreenshotWarningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD267246AA6F10001624F /* DWScreenshotWarningViewController.m */; }; + C9D2C7792A320AA000D15901 /* ConvertCryptoOrderPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CACC297021EE00F63AC4 /* ConvertCryptoOrderPreviewController.swift */; }; + C9D2C77A2A320AA000D15901 /* TransactionDataItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4708119E2990F56F003FCA3D /* TransactionDataItem.swift */; }; + C9D2C77B2A320AA000D15901 /* DWSetPinModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44314222CF80D9009BAF7F /* DWSetPinModel.m */; }; + C9D2C77C2A320AA000D15901 /* DWVerifySeedPhraseModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE8C22DCB3B600C99324 /* DWVerifySeedPhraseModel.m */; }; + C9D2C77D2A320AA000D15901 /* ReceiveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FA529DC092B001BC549 /* ReceiveViewController.swift */; }; + C9D2C77E2A320AA000D15901 /* ConfirmationTransactionQRController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118B7A3E29865A3A00FBB6CC /* ConfirmationTransactionQRController.swift */; }; + C9D2C77F2A320AA000D15901 /* DWDashPayAnimationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADB396824223D9B00A6F898 /* DWDashPayAnimationView.m */; }; + C9D2C7802A320AA000D15901 /* DWDPOutgoingRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CBD24940EC200F4A689 /* DWDPOutgoingRequestNotificationObject.m */; }; + C9D2C7812A320AA000D15901 /* Numbers+Dash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E4B7B6292F85E800CE0EB6 /* Numbers+Dash.swift */; }; + C9D2C7822A320AA000D15901 /* PaymentMethodsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478C983129433C5700FAA0F0 /* PaymentMethodsController.swift */; }; + C9D2C7832A320AA000D15901 /* PointOfUseDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1228C5F0A600490F5E /* PointOfUseDetailsViewController.swift */; }; + C9D2C7842A320AA000D15901 /* PointOfUseLocationServicePopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1B28C6AA2400490F5E /* PointOfUseLocationServicePopup.swift */; }; + C9D2C7852A320AA000D15901 /* BalanceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13E7299E4EE7006903F1 /* BalanceModel.swift */; }; + C9D2C7862A320AA000D15901 /* TxReclassifyTransactionsInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F2C6812860319400C2B774 /* TxReclassifyTransactionsInfoViewController.swift */; }; + C9D2C7872A320AA000D15901 /* DWTransactionListDataProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E535B22F335C200E5168A /* DWTransactionListDataProvider.m */; }; + C9D2C7882A320AA000D15901 /* DWModalPopupTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7DA72323AF4300BA8C6A /* DWModalPopupTransition.m */; }; + C9D2C7892A320AA000D15901 /* DWAdvancedSecurityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7F3B1A238C646600DEA3EF /* DWAdvancedSecurityViewController.m */; }; + C9D2C78A2A320AA000D15901 /* DWInputUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B64323CDC0F50016F221 /* DWInputUsernameViewController.m */; }; + C9D2C78B2A320AA000D15901 /* OrderPreviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CAD2297022B300F63AC4 /* OrderPreviewModel.swift */; }; + C9D2C78C2A320AA000D15901 /* DWDPGenericImageItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34824823167001D74F9 /* DWDPGenericImageItemView.m */; }; + C9D2C78D2A320AA000D15901 /* UIView+DWFindConstraints.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FD5244DFEF100B9F679 /* UIView+DWFindConstraints.m */; }; + C9D2C78E2A320AA000D15901 /* DWModalPopupPresentationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7DA42323AC1F00BA8C6A /* DWModalPopupPresentationController.m */; }; + C9D2C78F2A320AA000D15901 /* CrowdNodeBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11517C89294B11DD004FC7BF /* CrowdNodeBalance.swift */; }; + C9D2C7902A320AA000D15901 /* String+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471DD1B9290A962B00E030C8 /* String+DashWallet.swift */; }; + C9D2C7912A320AA000D15901 /* DWBaseContactsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2C2247512BA0001624F /* DWBaseContactsContentViewController.m */; }; + C9D2C7922A320AA000D15901 /* DWUserProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE2F0F8245C16C8001DD722 /* DWUserProfileViewController.m */; }; + C9D2C7932A320AA000D15901 /* DWBaseModalViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69D623143B2F001B8C90 /* DWBaseModalViewController.m */; }; + C9D2C7942A320AA000D15901 /* DWSecurityMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BCF2348A34800451078 /* DWSecurityMenuModel.m */; }; + C9D2C7952A320AA000D15901 /* KeysOverviewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909615829F29C9200002D82 /* KeysOverviewCell.swift */; }; + C9D2C7962A320AA000D15901 /* UIViewController+DWShareReceiveInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD86D2319609400A96B62 /* UIViewController+DWShareReceiveInfo.m */; }; + C9D2C7972A320AA000D15901 /* OnlineAccountConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114D16B729828BCC009A124C /* OnlineAccountConfirmationController.swift */; }; + C9D2C7982A320AA000D15901 /* BaseResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B8B29068F110003E8AB /* BaseResponse.swift */; }; + C9D2C7992A320AA000D15901 /* DWDemoAdvancedSecurityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3417623A92978004E37A7 /* DWDemoAdvancedSecurityViewController.m */; }; + C9D2C79A2A320AA000D15901 /* TxListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474C7219298A803200475CA6 /* TxListTableViewCell.swift */; }; + C9D2C79B2A320AA000D15901 /* DWStretchyHeaderListCollectionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D6F2462D4AD001D7C0D /* DWStretchyHeaderListCollectionLayout.m */; }; + C9D2C79C2A320AA000D15901 /* TxWithinTimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C4F296D5A4700788E18 /* TxWithinTimePeriod.swift */; }; + C9D2C79D2A320AA000D15901 /* CoinbaseSwapeTradeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDF9F28C896BC000427E7 /* CoinbaseSwapeTradeRequest.swift */; }; + C9D2C79E2A320AA000D15901 /* UIFont+DWFont.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44311622CBF0B2009BAF7F /* UIFont+DWFont.m */; }; + C9D2C79F2A320AA000D15901 /* CustodialSwapsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E94B96296D7D5B000FE68E /* CustodialSwapsViewController.swift */; }; + C9D2C7A02A320AA000D15901 /* NSAttributedString+DWHighlightText.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A392567234CFE9D00316EA6 /* NSAttributedString+DWHighlightText.m */; }; + C9D2C7A12A320AA000D15901 /* CoinbaseInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751136E28D9B50E00223B77 /* CoinbaseInfoViewController.swift */; }; + C9D2C7A22A320AA000D15901 /* DWSelectorFormCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE2A2230FF4600956D5F /* DWSelectorFormCellModel.m */; }; + C9D2C7A32A320AA000D15901 /* DWVersionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FB2E5536218BA161003A1B7C /* DWVersionManager.m */; }; + C9D2C7A42A320AA000D15901 /* DWDPEstablishedContactNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF377248270A4001D74F9 /* DWDPEstablishedContactNotificationObject.m */; }; + C9D2C7A52A320AA000D15901 /* DWModalPresentationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69C523142AA4001B8C90 /* DWModalPresentationController.m */; }; + C9D2C7A62A320AA000D15901 /* PointOfUseInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1028C5430300490F5E /* PointOfUseInfoViewController.swift */; }; + C9D2C7A72A320AA000D15901 /* ActionButtonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661B328FDCF7800028A8D /* ActionButtonViewController.swift */; }; + C9D2C7A82A320AA000D15901 /* PortalServiceItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4774DCE428F4668B008CF87D /* PortalServiceItemCell.swift */; }; + C9D2C7A92A320AA000D15901 /* DWDPImageStatusCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF35524824F97001D74F9 /* DWDPImageStatusCell.m */; }; + C9D2C7AA2A320AA000D15901 /* WithdrawalConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111B8BFF299BD973004A4129 /* WithdrawalConfirmationController.swift */; }; + C9D2C7AB2A320AA000D15901 /* DWBaseFormTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BC82347E0D700451078 /* DWBaseFormTableViewCell.m */; }; + C9D2C7AC2A320AA000D15901 /* AccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CAC6296FAEBB00F63AC4 /* AccountCell.swift */; }; + C9D2C7AD2A320AA000D15901 /* DWTransactionListDataProviderStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913EA723A79AD2006A2A59 /* DWTransactionListDataProviderStub.m */; }; + C9D2C7AE2A320AA000D15901 /* DWUserSearchModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FCF2449F37A00B9F679 /* DWUserSearchModel.m */; }; + C9D2C7AF2A320AA000D15901 /* DWDPBasicCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34F2482374D001D74F9 /* DWDPBasicCell.m */; }; + C9D2C7B02A320AA000D15901 /* DWExploreHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BE128C1305E00490F5E /* DWExploreHeaderView.m */; }; + C9D2C7B12A320AA000D15901 /* CoinbaseBaseIDForCurrencyResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA028C896BC000427E7 /* CoinbaseBaseIDForCurrencyResponse.swift */; }; + C9D2C7B22A320AA000D15901 /* DWBaseActionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BE22348E85400451078 /* DWBaseActionButton.m */; }; + C9D2C7B32A320AA000D15901 /* SpecifyAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AC8412297822E000BD1B49 /* SpecifyAmountViewController.swift */; }; + C9D2C7B42A320AA000D15901 /* DWUpholdTransactionObject+DWView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFCB9C323BE76EC00FF59A6 /* DWUpholdTransactionObject+DWView.m */; }; + C9D2C7B52A320AA000D15901 /* DWBackupSeedPhraseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A36A88A2350A05B0014DC60 /* DWBackupSeedPhraseViewController.m */; }; + C9D2C7B62A320AA000D15901 /* CrowdNodeDepositTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114CFECF296469D9005F421B /* CrowdNodeDepositTx.swift */; }; + C9D2C7B72A320AA000D15901 /* DWUpholdViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE6A2230FF4600956D5F /* DWUpholdViewController.m */; }; + C9D2C7B82A320AA000D15901 /* DWNotificationsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF31B2480E6AD001D74F9 /* DWNotificationsModel.m */; }; + C9D2C7B92A320AA000D15901 /* DWIntrinsicTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFF072233E56E00956D5F /* DWIntrinsicTableView.m */; }; + C9D2C7BA2A320AA000D15901 /* DWURLActions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E6523A11DFE006A2A59 /* DWURLActions.m */; }; + C9D2C7BB2A320AA000D15901 /* KeysOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9903A5329E6A5F600535A4E /* KeysOverviewViewController.swift */; }; + C9D2C7BC2A320AA000D15901 /* AccountRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF469E296540E40067B6EE /* AccountRepository.swift */; }; + C9D2C7BD2A320AA000D15901 /* DWSharedUIConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E2822FB1C5D00FF8653 /* DWSharedUIConstants.m */; }; + C9D2C7BE2A320AA000D15901 /* BRAppleWatchTransactionData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A0E237EE89B0097A7B5 /* BRAppleWatchTransactionData.m */; }; + C9D2C7BF2A320AA000D15901 /* TerritoriesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A50F3D29192F8D00C70123 /* TerritoriesListViewController.swift */; }; + C9D2C7C02A320AA000D15901 /* HomeHeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F4520A2A1209D100825057 /* HomeHeaderModel.swift */; }; + C9D2C7C12A320AA000D15901 /* TerritoryListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C6E6DF2919578C003FEDF2 /* TerritoryListModel.swift */; }; + C9D2C7C22A320AA000D15901 /* DWUpholdConfirmTransferModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE5A2230FF4600956D5F /* DWUpholdConfirmTransferModel.m */; }; + C9D2C7C32A320AA000D15901 /* DWDPRegistrationStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFF01DA243F4559003718DC /* DWDPRegistrationStatus.m */; }; + C9D2C7C42A320AA000D15901 /* CurrencyExchanger_Objc.m in Sources */ = {isa = PBXBuildFile; fileRef = 472D13EC299E6579006903F1 /* CurrencyExchanger_Objc.m */; }; + C9D2C7C52A320AA000D15901 /* GiftCardInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C0D28C540C100490F5E /* GiftCardInfoViewController.swift */; }; + C9D2C7C62A320AA000D15901 /* Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471DD1B7290A92CD00E030C8 /* Tools.swift */; }; + C9D2C7C72A320AA000D15901 /* PortalModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478A2C7028DC554200AD1420 /* PortalModel.swift */; }; + C9D2C7C82A320AA000D15901 /* DWDateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF39D2482FE46001D74F9 /* DWDateFormatter.m */; }; + C9D2C7C92A320AA000D15901 /* BuyDashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478C98252942DC2700FAA0F0 /* BuyDashViewController.swift */; }; + C9D2C7CA2A320AA000D15901 /* DWModalDismissalAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69CF23143435001B8C90 /* DWModalDismissalAnimation.m */; }; + C9D2C7CB2A320AA000D15901 /* DWPressableButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD72422FA05DD008C7BC9 /* DWPressableButton.m */; }; + C9D2C7CC2A320AA000D15901 /* DWUpholdAuthURLNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E6A23A15419006A2A59 /* DWUpholdAuthURLNotification.m */; }; + C9D2C7CD2A320AA000D15901 /* UIViewController+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CDEECD294A3CF2008AE06D /* UIViewController+DashWallet.swift */; }; + C9D2C7CE2A320AA000D15901 /* CrowdNodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 470617D5299A671900DCC667 /* CrowdNodeCell.swift */; }; + C9D2C7CF2A320AA000D15901 /* MerchantListLocationOffCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD128C1305E00490F5E /* MerchantListLocationOffCell.swift */; }; + C9D2C7D02A320AA000D15901 /* DWExploreTestnetContentsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BE228C1305E00490F5E /* DWExploreTestnetContentsView.m */; }; + C9D2C7D12A320AA000D15901 /* DWMainMenuContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BB5234792A600451078 /* DWMainMenuContentView.m */; }; + C9D2C7D22A320AA000D15901 /* DWDPIncomingRequestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF35224823EC8001D74F9 /* DWDPIncomingRequestCell.m */; }; + C9D2C7D32A320AA000D15901 /* CrowdNodeAPIConfirmationTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1186092429759C4B00279FCC /* CrowdNodeAPIConfirmationTx.swift */; }; + C9D2C7D42A320AA000D15901 /* UIViewController+DWEmbedding.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8F23A31713006A2A59 /* UIViewController+DWEmbedding.m */; }; + C9D2C7D52A320AA000D15901 /* DWShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD71522F98FEA008C7BC9 /* DWShadowView.m */; }; + C9D2C7D62A320AA000D15901 /* CALayer+DWShadow.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD71222F97B65008C7BC9 /* CALayer+DWShadow.m */; }; + C9D2C7D72A320AA000D15901 /* MerchantDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BA328BFADD800490F5E /* MerchantDAO.swift */; }; + C9D2C7D82A320AA000D15901 /* ExploreDatabaseSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BA828BFAE5800490F5E /* ExploreDatabaseSyncManager.swift */; }; + C9D2C7D92A320AA000D15901 /* DWOnboardingCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3416823A8C479004E37A7 /* DWOnboardingCollectionViewCell.m */; }; + C9D2C7DA2A320AA000D15901 /* CrowdNodeErrorResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D0F290949B900D406C1 /* CrowdNodeErrorResponse.swift */; }; + C9D2C7DB2A320AA000D15901 /* DWHomeViewController+DWShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74F0002305C40F00C475EB /* DWHomeViewController+DWShortcuts.m */; }; + C9D2C7DC2A320AA000D15901 /* DWHomeModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8123A30623006A2A59 /* DWHomeModelStub.m */; }; + C9D2C7DD2A320AA000D15901 /* UISearchBar+DWAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D1B24603C4F001D7C0D /* UISearchBar+DWAdditions.m */; }; + C9D2C7DE2A320AA000D15901 /* UIViewController+DWTxFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74F0032305C59700C475EB /* UIViewController+DWTxFilter.m */; }; + C9D2C7DF2A320AA000D15901 /* DWProfileTxsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3AC24BE117300DB32DE /* DWProfileTxsFetchedDataSource.m */; }; + C9D2C7E02A320AA000D15901 /* TransactionFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 117ED4A728ED66F9006E3EE4 /* TransactionFilter.swift */; }; + C9D2C7E12A320AA000D15901 /* DWSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2BF24747F580001624F /* DWSearchViewController.m */; }; + C9D2C7E22A320AA000D15901 /* CrowdNodeWithdrawalReceivedTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C53296D6A2D00788E18 /* CrowdNodeWithdrawalReceivedTx.swift */; }; + C9D2C7E32A320AA000D15901 /* PointOfUseListSegmentedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C0228C1F0C600490F5E /* PointOfUseListSegmentedCell.swift */; }; + C9D2C7E42A320AA000D15901 /* DWURLRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E6723A14739006A2A59 /* DWURLRequestHandler.m */; }; + C9D2C7E52A320AA000D15901 /* CoinbaseAPIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFB528C896BC000427E7 /* CoinbaseAPIEndpoint.swift */; }; + C9D2C7E62A320AA000D15901 /* ConfirmOrderCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F4B6CC29485A8B00AED4C9 /* ConfirmOrderCells.swift */; }; + C9D2C7E72A320AA000D15901 /* CoinbaseUserAccountData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFAA28C896BC000427E7 /* CoinbaseUserAccountData.swift */; }; + C9D2C7E82A320AA000D15901 /* DWDPRegistrationDoneTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFF01E3243F8625003718DC /* DWDPRegistrationDoneTableViewCell.m */; }; + C9D2C7E92A320AA000D15901 /* AtmDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BA428BFADD900490F5E /* AtmDAO.swift */; }; + C9D2C7EA2A320AA000D15901 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 475AE2B82974348F009A1055 /* App.swift */; }; + C9D2C7EB2A320AA000D15901 /* DWPaymentProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69A2231048DA001B8C90 /* DWPaymentProcessor.m */; }; + C9D2C7EC2A320AA000D15901 /* DWNotificationsData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF32724814A17001D74F9 /* DWNotificationsData.m */; }; + C9D2C7ED2A320AA000D15901 /* SyncingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451F22A0C933700825057 /* SyncingHeaderView.swift */; }; + C9D2C7EE2A320AA000D15901 /* MinimumDepositBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114CFED1296489CD005F421B /* MinimumDepositBanner.swift */; }; + C9D2C7EF2A320AA000D15901 /* BalanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B8E2906D7340003E8AB /* BalanceView.swift */; }; + C9D2C7F02A320AA000D15901 /* DWQRScanStatusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD85A9E245881740045B480 /* DWQRScanStatusView.m */; }; + C9D2C7F12A320AA000D15901 /* DWURLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A741DC52363A06000840ADF /* DWURLParser.m */; }; + C9D2C7F22A320AA000D15901 /* DWDPContactObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36E24826737001D74F9 /* DWDPContactObject.m */; }; + C9D2C7F32A320AA000D15901 /* DWPaymentInputBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E6B23029D2B00FF8653 /* DWPaymentInputBuilder.m */; }; + C9D2C7F42A320AA000D15901 /* DWQRScanViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC92C831FEB0A6D008CAEE0 /* DWQRScanViewController.m */; }; + C9D2C7F52A320AA000D15901 /* DWSelectorFormTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE2C2230FF4600956D5F /* DWSelectorFormTableViewCell.m */; }; + C9D2C7F62A320AA000D15901 /* DWRegistrationCompletedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3CCEF8242BB1B900300AF8 /* DWRegistrationCompletedViewController.m */; }; + C9D2C7F72A320AA000D15901 /* DWUserProfileHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D6E2462D4AD001D7C0D /* DWUserProfileHeaderView.m */; }; + C9D2C7F82A320AA000D15901 /* UIViewController+AlertPresenting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47305871295C971F004641DA /* UIViewController+AlertPresenting.swift */; }; + C9D2C7F92A320AA000D15901 /* DSWatchTransactionDataObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A06237EE89B0097A7B5 /* DSWatchTransactionDataObject.m */; }; + C9D2C7FA2A320AA000D15901 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA3B0129364991008D58DC /* HTTPClient.swift */; }; + C9D2C7FB2A320AA000D15901 /* DWPasteboardAddressExtractor.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E6E2302A9C200FF8653 /* DWPasteboardAddressExtractor.m */; }; + C9D2C7FC2A320AA000D15901 /* ViewModel+Coinbase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477F501429531C07003C7508 /* ViewModel+Coinbase.swift */; }; + C9D2C7FD2A320AA000D15901 /* CNCreateAccountTxDetailsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478A9296299242EC0008C43E /* CNCreateAccountTxDetailsModel.swift */; }; + C9D2C7FE2A320AA000D15901 /* CBUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2A2EB293E618600938DB7 /* CBUser.swift */; }; + C9D2C7FF2A320AA000D15901 /* CBSecureTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2A2ED293E622700938DB7 /* CBSecureTokenService.swift */; }; + C9D2C8002A320AA000D15901 /* PayTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FA029DA95F5001BC549 /* PayTableViewCell.swift */; }; + C9D2C8012A320AA000D15901 /* DWTitleActionHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2C5247538A90001624F /* DWTitleActionHeaderView.m */; }; + C9D2C8022A320AA000D15901 /* DWDPTxObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3B424BF280A00DB32DE /* DWDPTxObject.m */; }; + C9D2C8032A320AA000D15901 /* ServiceOverviewTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F71317628F436920072F454 /* ServiceOverviewTableCell.swift */; }; + C9D2C8042A320AA000D15901 /* DWContactsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5522487D50200B52F14 /* DWContactsModel.m */; }; + C9D2C8052A320AA000D15901 /* CoinbaseEntryPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B86290670630003E8AB /* CoinbaseEntryPointViewController.swift */; }; + C9D2C8062A320AA000D15901 /* NSString+DWTextSize.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE7F22DC92BF00C99324 /* NSString+DWTextSize.m */; }; + C9D2C8072A320AA000D15901 /* PointOfUseListFiltersCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C0428C1F74A00490F5E /* PointOfUseListFiltersCell.swift */; }; + C9D2C8082A320AA000D15901 /* TransactionListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47081198298CF57D003FCA3D /* TransactionListDataSource.swift */; }; + C9D2C8092A320AA000D15901 /* DWAllowedCharactersUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD5942451DCAF00688A8D /* DWAllowedCharactersUsernameValidationRule.m */; }; + C9D2C80A2A320AA000D15901 /* DWMainMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BB12347927700451078 /* DWMainMenuViewController.m */; }; + C9D2C80B2A320AA000D15901 /* PointOfUseListFiltersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B7C290133610003E8AB /* PointOfUseListFiltersViewController.swift */; }; + C9D2C80C2A320AA000D15901 /* AllMerchantLocationsDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1928C6A21A00490F5E /* AllMerchantLocationsDataProvider.swift */; }; + C9D2C80D2A320AA000D15901 /* MainTabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E42A0B986E00825057 /* MainTabbarController.swift */; }; + C9D2C80E2A320AA000D15901 /* NumberFormatter+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B30D77290BFCA60080C326 /* NumberFormatter+DashWallet.swift */; }; + C9D2C80F2A320AA000D15901 /* DWNotificationsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CBA2494045C00F4A689 /* DWNotificationsFetchedDataSource.m */; }; + C9D2C8102A320AA000D15901 /* BaseAmountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AF18072907B7880025803E /* BaseAmountModel.swift */; }; + C9D2C8112A320AA000D15901 /* DWDPNewIncomingRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3742482703C001D74F9 /* DWDPNewIncomingRequestNotificationObject.m */; }; + C9D2C8122A320AA000D15901 /* SeedDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709C311287E78BD00B4BD48 /* SeedDB.swift */; }; + C9D2C8132A320AA000D15901 /* DWPinField.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8EF231ECDE000A96B62 /* DWPinField.m */; }; + C9D2C8142A320AA000D15901 /* DWCenteredScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CEA122DFC80900C99324 /* DWCenteredScrollView.m */; }; + C9D2C8152A320AA000D15901 /* DWDPEstablishedContactObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36B248266FB001D74F9 /* DWDPEstablishedContactObject.m */; }; + C9D2C8162A320AA000D15901 /* DWPhraseRepairViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9172C325233DC50024B4C5 /* DWPhraseRepairViewController.m */; }; + C9D2C8172A320AA000D15901 /* DWBiometricAuthModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44314B22CF8801009BAF7F /* DWBiometricAuthModel.m */; }; + C9D2C8182A320AA000D15901 /* DWNumberKeyboardInputViewAudioFeedback.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8EB231ECCF000A96B62 /* DWNumberKeyboardInputViewAudioFeedback.m */; }; + C9D2C8192A320AA000D15901 /* CrowdNode+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E47BAC28EB3A7D0097CFA0 /* CrowdNode+Constants.swift */; }; + C9D2C81A2A320AA000D15901 /* CurrencyExchanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F006012971B1FE0029EB10 /* CurrencyExchanger.swift */; }; + C9D2C81B2A320AA000D15901 /* DWMinLengthUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD5912451D68300688A8D /* DWMinLengthUsernameValidationRule.m */; }; + C9D2C81C2A320AA000D15901 /* DemoMainTabbarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94946E02A25F037008A678D /* DemoMainTabbarViewController.swift */; }; + C9D2C81D2A320AA000D15901 /* MessageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11AE3DD52997AA36000856EE /* MessageStatus.swift */; }; + C9D2C81E2A320AA000D15901 /* DWGlobalOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CEAA22E18F1800C99324 /* DWGlobalOptions.m */; }; + C9D2C81F2A320AA000D15901 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4430E622CBB6EC009BAF7F /* AppDelegate.m */; }; + C9D2C8202A320AA000D15901 /* SendReceivePageController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C917023E29D44E0B008C034D /* SendReceivePageController.swift */; }; + C9D2C8212A320AA000D15901 /* DWSettingsMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BD82348CB7300451078 /* DWSettingsMenuModel.m */; }; + C9D2C8222A320AA000D15901 /* DWUserProfileSendRequestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A80F3D824DC55CC003E3B1E /* DWUserProfileSendRequestCell.m */; }; + C9D2C8232A320AA000D15901 /* BaseNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478A2C6E28DC457000AD1420 /* BaseNavigationController.swift */; }; + C9D2C8242A320AA000D15901 /* UIViewController+Coinbase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CDEECB294A2BAD008AE06D /* UIViewController+Coinbase.swift */; }; + C9D2C8252A320AA000D15901 /* DWCheckExistenceUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD59D2451F39500688A8D /* DWCheckExistenceUsernameValidationRule.m */; }; + C9D2C8262A320AA000D15901 /* CSVBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13E0299E1F2F006903F1 /* CSVBuilder.swift */; }; + C9D2C8272A320AA000D15901 /* DWDPRegistrationStatusTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5E4546243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.m */; }; + C9D2C8282A320AA000D15901 /* CoinbaseUserAuthInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDF9D28C896BC000427E7 /* CoinbaseUserAuthInformation.swift */; }; + C9D2C8292A320AA000D15901 /* DWTransactionStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913EB523A7E145006A2A59 /* DWTransactionStub.m */; }; + C9D2C82A2A320AA000D15901 /* OnlineAccountEmailController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118C05B729928F7800717E65 /* OnlineAccountEmailController.swift */; }; + C9D2C82B2A320AA000D15901 /* DWBaseActionButtonViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFE82305264200C475EB /* DWBaseActionButtonViewController.m */; }; + C9D2C82C2A320AA000D15901 /* CoinbasePaymentMethodsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA228C896BC000427E7 /* CoinbasePaymentMethodsResponse.swift */; }; + C9D2C82D2A320AA000D15901 /* UIView+DWEmbedding.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8B23A3134E006A2A59 /* UIView+DWEmbedding.m */; }; + C9D2C82E2A320AA000D15901 /* DWLockActionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6300482328EA8900827825 /* DWLockActionButton.m */; }; + C9D2C82F2A320AA000D15901 /* PointOfUseDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BA028BFADB600490F5E /* PointOfUseDAO.swift */; }; + C9D2C8302A320AA000D15901 /* ProvideAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4759D51629300212002F20DC /* ProvideAmountViewController.swift */; }; + C9D2C8312A320AA000D15901 /* DWCheckbox.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE7622DB51B500C99324 /* DWCheckbox.m */; }; + C9D2C8322A320AA000D15901 /* DWRequestAmountViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8582319399100A96B62 /* DWRequestAmountViewController.m */; }; + C9D2C8332A320AA000D15901 /* DerivationPathKeysModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909615229F28E3700002D82 /* DerivationPathKeysModel.swift */; }; + C9D2C8342A320AA000D15901 /* DWSeedUIConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFF42305333000C475EB /* DWSeedUIConstants.m */; }; + C9D2C8352A320AA000D15901 /* DWWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69B22312E8A0001B8C90 /* DWWindow.m */; }; + C9D2C8362A320AA000D15901 /* BalanceNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13E2299E23B7006903F1 /* BalanceNotifier.swift */; }; + C9D2C8372A320AA000D15901 /* DWSeedWordModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE6322D9127600C99324 /* DWSeedWordModel.m */; }; + C9D2C8382A320AA000D15901 /* DWRootModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44313F22CF642C009BAF7F /* DWRootModel.m */; }; + C9D2C8392A320AA000D15901 /* BaseAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661AE28FDAA3300028A8D /* BaseAmountViewController.swift */; }; + C9D2C83A2A320AA000D15901 /* SendingToView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478C982B2942F03500FAA0F0 /* SendingToView.swift */; }; + C9D2C83B2A320AA000D15901 /* TxUserInfoDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709C317287EA23000B4BD48 /* TxUserInfoDAO.swift */; }; + C9D2C83C2A320AA000D15901 /* DWModalContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69E223155A0E001B8C90 /* DWModalContentView.m */; }; + C9D2C83D2A320AA000D15901 /* DWUpholdConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDFF2230FF2B00956D5F /* DWUpholdConstants.m */; }; + C9D2C83E2A320AA000D15901 /* DWMaxLengthUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD5972451DD0700688A8D /* DWMaxLengthUsernameValidationRule.m */; }; + C9D2C83F2A320AA000D15901 /* CoinbaseSwapeTradeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA128C896BC000427E7 /* CoinbaseSwapeTradeResponse.swift */; }; + C9D2C8402A320AA000D15901 /* DWQRScanView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC92C861FEB0AE8008CAEE0 /* DWQRScanView.m */; }; + C9D2C8412A320AA000D15901 /* ServiceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4774DCDE28F43AB4008CF87D /* ServiceItem.swift */; }; + C9D2C8422A320AA000D15901 /* ErrorPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EE171629560DC200BA1986 /* ErrorPresentable.swift */; }; + C9D2C8432A320AA000D15901 /* DWPinInputStepView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8E1231E507900A96B62 /* DWPinInputStepView.m */; }; + C9D2C8442A320AA000D15901 /* DWDashPaySetupFlowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC52AD5241BB5FC00D9A829 /* DWDashPaySetupFlowController.m */; }; + C9D2C8452A320AA000D15901 /* DWRecoverViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFEC2305318000C475EB /* DWRecoverViewController.m */; }; + C9D2C8462A320AA000D15901 /* DWDPAmountContactView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A919F9E24A65CE00018C9A3 /* DWDPAmountContactView.m */; }; + C9D2C8472A320AA000D15901 /* DWBaseTransactionListDataProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913EAD23A7AC86006A2A59 /* DWBaseTransactionListDataProvider.m */; }; + C9D2C8482A320AA000D15901 /* CoinbaseTransactionsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA828C896BC000427E7 /* CoinbaseTransactionsRequest.swift */; }; + C9D2C8492A320AA000D15901 /* DWAppRootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBAC22E1DA4000A50237 /* DWAppRootViewController.m */; }; + C9D2C84A2A320AA000D15901 /* DWUpholdConfirmViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFCB9C023BE766D00FF59A6 /* DWUpholdConfirmViewController.m */; }; + C9D2C84B2A320AA000D15901 /* UITableView+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F005FE297164600029EB10 /* UITableView+DashWallet.swift */; }; + C9D2C84C2A320AA000D15901 /* TransferAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661B128FDC72700028A8D /* TransferAmountViewController.swift */; }; + C9D2C84D2A320AA000D15901 /* ShortcutCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8D29D404850034FD57 /* ShortcutCell.swift */; }; + C9D2C84E2A320AA000D15901 /* DWDPGenericContactRequestItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34324822AE0001D74F9 /* DWDPGenericContactRequestItemView.m */; }; + C9D2C84F2A320AA000D15901 /* UIColor+DWStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44312022CCA2A0009BAF7F /* UIColor+DWStyle.m */; }; + C9D2C8502A320AA000D15901 /* DWEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = FBEF3AF021823CD800917AB6 /* DWEnvironment.m */; }; + C9D2C8512A320AA000D15901 /* AtmItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BC928C1305E00490F5E /* AtmItemCell.swift */; }; + C9D2C8522A320AA000D15901 /* CoinbaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFB628C896BC000427E7 /* CoinbaseService.swift */; }; + C9D2C8532A320AA000D15901 /* DWControllerCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E5522FEDF2900FF8653 /* DWControllerCollectionView.m */; }; + C9D2C8542A320AA000D15901 /* SFSafariViewController+DashWallet.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDF22230FF1A00956D5F /* SFSafariViewController+DashWallet.m */; }; + C9D2C8552A320AA000D15901 /* DWContactsDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E54C2487CE0100B52F14 /* DWContactsDataSourceObject.m */; }; + C9D2C8562A320AA000D15901 /* TransactionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474C7217298A422400475CA6 /* TransactionItemView.swift */; }; + C9D2C8572A320AA000D15901 /* UIView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474C7210298A1A9500475CA6 /* UIView+Reuse.swift */; }; + C9D2C8582A320AA000D15901 /* DWUpholdClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE022230FF2B00956D5F /* DWUpholdClient.m */; }; + C9D2C8592A320AA000D15901 /* CrowdNodePortalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1147687D294B789800FB1EEE /* CrowdNodePortalViewController.swift */; }; + C9D2C85A2A320AA000D15901 /* ConfirmOrderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F4B6C929484C9800AED4C9 /* ConfirmOrderModel.swift */; }; + C9D2C85B2A320AA000D15901 /* PointOfUseListEmptyResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472CEE002924AA6D00656B48 /* PointOfUseListEmptyResultsView.swift */; }; + C9D2C85C2A320AA000D15901 /* ShortcutsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8B29D3FEC10034FD57 /* ShortcutsModel.swift */; }; + C9D2C85D2A320AA000D15901 /* DWLockPinInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6300442328D07500827825 /* DWLockPinInputView.m */; }; + C9D2C85E2A320AA000D15901 /* UIView+DWHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69AB23125074001B8C90 /* UIView+DWHUD.m */; }; + C9D2C85F2A320AA000D15901 /* DWPayOptionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E6722FFE4CC00FF8653 /* DWPayOptionModel.m */; }; + C9D2C8602A320AA000D15901 /* DWDPRespondedIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36824826681001D74F9 /* DWDPRespondedIncomingRequestObject.m */; }; + C9D2C8612A320AA000D15901 /* CoinbaseTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA328C896BC000427E7 /* CoinbaseTokenResponse.swift */; }; + C9D2C8622A320AA000D15901 /* BackupInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB129DD5141001BC549 /* BackupInfoViewController.swift */; }; + C9D2C8632A320AA000D15901 /* DWPlanetarySystemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1AE79123F468CD00179A6E /* DWPlanetarySystemView.m */; }; + C9D2C8642A320AA000D15901 /* AppliedFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C6E6E4291A68B6003FEDF2 /* AppliedFiltersView.swift */; }; + C9D2C8652A320AA000D15901 /* DWCaptureSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BAD234770C900451078 /* DWCaptureSessionManager.m */; }; + C9D2C8662A320AA000D15901 /* DWDataMigrationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A11F59E2194BD6200E7B563 /* DWDataMigrationManager.m */; }; + C9D2C8672A320AA000D15901 /* DWRequestsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5692487E1DB00B52F14 /* DWRequestsModel.m */; }; + C9D2C8682A320AA000D15901 /* DWDPTextStatusCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF35824824FEB001D74F9 /* DWDPTextStatusCell.m */; }; + C9D2C8692A320AA000D15901 /* DWContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5592487D9AF00B52F14 /* DWContactsViewController.m */; }; + C9D2C86A2A320AA000D15901 /* OutlinedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EA863929952E9800ABD7B4 /* OutlinedTextField.swift */; }; + C9D2C86B2A320AA000D15901 /* AmountPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13E5299E3C81006903F1 /* AmountPreviewView.swift */; }; + C9D2C86C2A320AA000D15901 /* ModalNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4759D513292FEFFB002F20DC /* ModalNavigationController.swift */; }; + C9D2C86D2A320AA000D15901 /* DWDPSmallContactView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A919F9B24A4DCAD0018C9A3 /* DWDPSmallContactView.m */; }; + C9D2C86E2A320AA000D15901 /* TransferAmountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A50F3A2913BC0900C70123 /* TransferAmountModel.swift */; }; + C9D2C86F2A320AA000D15901 /* NavigationBarAppearanceCustomizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4730586F295AD62B004641DA /* NavigationBarAppearanceCustomizable.swift */; }; + C9D2C8702A320AA000D15901 /* AmountObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471DD1B5290A901200E030C8 /* AmountObject.swift */; }; + C9D2C8712A320AA000D15901 /* DerivationPathKeysViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909615029F158D700002D82 /* DerivationPathKeysViewController.swift */; }; + C9D2C8722A320AA000D15901 /* DSTransaction+DashWallet.m in Sources */ = {isa = PBXBuildFile; fileRef = 4709C31D2880247C00B4BD48 /* DSTransaction+DashWallet.m */; }; + C9D2C8732A320AA000D15901 /* DWHomeViewController+DWJailbreakCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74F0062305C60B00C475EB /* DWHomeViewController+DWJailbreakCheck.m */; }; + C9D2C8742A320AA000D15901 /* DWLocalCurrencyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7C1C234B771400451078 /* DWLocalCurrencyModel.m */; }; + C9D2C8752A320AA000D15901 /* DWMainMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BC2234797FC00451078 /* DWMainMenuModel.m */; }; + C9D2C8762A320AA000D15901 /* DWUpholdMainnetConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = FB8ACEB522E0502100EE5035 /* DWUpholdMainnetConstants.m */; }; + C9D2C8772A320AA000D15901 /* DWUserSearchResultViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FCB2449F08700B9F679 /* DWUserSearchResultViewController.m */; }; + C9D2C8782A320AA000D15901 /* DWDPGenericStatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34B24823315001D74F9 /* DWDPGenericStatusItemView.m */; }; + C9D2C8792A320AA000D15901 /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FA829DC09CF001BC549 /* Style.swift */; }; + C9D2C87A2A320AA000D15901 /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C917024029D462C6008C034D /* PayViewController.swift */; }; + C9D2C87B2A320AA000D15901 /* DatabaseConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 479E7923287C00EC00D0F7D7 /* DatabaseConnection.swift */; }; + C9D2C87C2A320AA000D15901 /* DWSwitcherFormCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE312230FF4600956D5F /* DWSwitcherFormCellModel.m */; }; + C9D2C87D2A320AA000D15901 /* DWIntrinsicTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFF723053ECE00C475EB /* DWIntrinsicTextView.m */; }; + C9D2C87E2A320AA000D15901 /* DWExploreTestnetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BE428C1305E00490F5E /* DWExploreTestnetViewController.m */; }; + C9D2C87F2A320AA000D15901 /* AddressUserInfoDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471A2607289ACDA00056B7B2 /* AddressUserInfoDAO.swift */; }; + C9D2C8802A320AA000D15901 /* DWDashPayContactsUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CC1249755BB00F4A689 /* DWDashPayContactsUpdater.m */; }; + C9D2C8812A320AA000D15901 /* DWModalTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69D223143568001B8C90 /* DWModalTransition.m */; }; + C9D2C8822A320AA000D15901 /* DWUpholdMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE6D2230FF4600956D5F /* DWUpholdMainModel.m */; }; + C9D2C8832A320AA000D15901 /* MerchantsDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD628C1305E00490F5E /* MerchantsDataProvider.swift */; }; + C9D2C8842A320AA000D15901 /* FullCrowdNodeSignUpTxSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D072905409300D406C1 /* FullCrowdNodeSignUpTxSet.swift */; }; + C9D2C8852A320AA000D15901 /* DWSeedPhraseModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431E822D738C0009BAF7F /* DWSeedPhraseModel.m */; }; + C9D2C8862A320AA000D15901 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478C983B2945801D00FAA0F0 /* Constants.swift */; }; + C9D2C8872A320AA000D15901 /* DWBaseFormCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE372230FF4600956D5F /* DWBaseFormCellModel.m */; }; + C9D2C8882A320AA000D15901 /* ExploreMapAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BDA28C1305E00490F5E /* ExploreMapAnnotationView.swift */; }; + C9D2C8892A320AA000D15901 /* DWHomeViewController+DWBackupReminder.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7DBF232669FF00BA8C6A /* DWHomeViewController+DWBackupReminder.m */; }; + C9D2C88A2A320AA000D15901 /* DWHomeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E533722F023AB00E5168A /* DWHomeModel.m */; }; + C9D2C88B2A320AA000D15901 /* DWUserProfileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D7524640A2B001D7C0D /* DWUserProfileModel.m */; }; + C9D2C88C2A320AA000D15901 /* DWHomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBB822E1FA1000A50237 /* DWHomeViewController.m */; }; + C9D2C88D2A320AA000D15901 /* IsAddressInUse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1186092029758B2F00279FCC /* IsAddressInUse.swift */; }; + C9D2C88E2A320AA000D15901 /* DWFetchedResultsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD26E246D46BF0001624F /* DWFetchedResultsDataSource.m */; }; + C9D2C88F2A320AA000D15901 /* DWSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E3F22FD75C000FF8653 /* DWSegmentedControl.m */; }; + C9D2C8902A320AA000D15901 /* MerchantItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BCC28C1305E00490F5E /* MerchantItemCell.swift */; }; + C9D2C8912A320AA000D15901 /* PaymentMethodCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478C98362943A60000FAA0F0 /* PaymentMethodCell.swift */; }; + C9D2C8922A320AA000D15901 /* DWContactsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5562487D8C000B52F14 /* DWContactsContentViewController.m */; }; + C9D2C8932A320AA000D15901 /* CBUserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EEE23A293F041E00049E0B /* CBUserManager.swift */; }; + C9D2C8942A320AA000D15901 /* DerivationPathKeysHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909615A29F6535300002D82 /* DerivationPathKeysHeaderView.swift */; }; + C9D2C8952A320AA000D15901 /* ConvertCryptoOrderPreviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CACF2970224D00F63AC4 /* ConvertCryptoOrderPreviewModel.swift */; }; + C9D2C8962A320AA000D15901 /* DWContactsSearchDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2CB24757A210001624F /* DWContactsSearchDataSourceObject.m */; }; + C9D2C8972A320AA000D15901 /* RatesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2E3A82972B15F0032A63B /* RatesProvider.swift */; }; + C9D2C8982A320AA000D15901 /* CBAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2A2E8293E612900938DB7 /* CBAuth.swift */; }; + C9D2C8992A320AA000D15901 /* DWButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A307CBE22E8A44200A18347 /* DWButton.m */; }; + C9D2C89A2A320AA000D15901 /* CoinbaseCreateAddressesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFAE28C896BC000427E7 /* CoinbaseCreateAddressesRequest.swift */; }; + C9D2C89B2A320AA000D15901 /* DWImportWalletInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A10EB342358996700C38B61 /* DWImportWalletInfoViewController.m */; }; + C9D2C89C2A320AA000D15901 /* TxListEmptyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452022A0CEB5800825057 /* TxListEmptyTableViewCell.swift */; }; + C9D2C89D2A320AA000D15901 /* SyncingAlertContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451ED2A0BF1F500825057 /* SyncingAlertContentView.swift */; }; + C9D2C89E2A320AA000D15901 /* AddressUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471A2604289ACD5C0056B7B2 /* AddressUserInfo.swift */; }; + C9D2C89F2A320AA000D15901 /* MerchantInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C0B28C53E4A00490F5E /* MerchantInfoViewController.swift */; }; + C9D2C8A02A320AA000D15901 /* SendCoinsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E47BAA28EB38510097CFA0 /* SendCoinsService.swift */; }; + C9D2C8A12A320AA000D15901 /* ShortcutAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8929D3FCCF0034FD57 /* ShortcutAction.swift */; }; + C9D2C8A22A320AA000D15901 /* DWModalChevronView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69DF231556D6001B8C90 /* DWModalChevronView.m */; }; + C9D2C8A32A320AA000D15901 /* DWBaseLegacyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A58815821A5906C00FD4D2C /* DWBaseLegacyViewController.m */; }; + C9D2C8A42A320AA000D15901 /* DWNoNotificationsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C3CA202F247E54C400158074 /* DWNoNotificationsCell.m */; }; + C9D2C8A52A320AA000D15901 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452002A0CE6C900825057 /* HomeView.swift */; }; + C9D2C8A62A320AA000D15901 /* CoinbaseRatesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2E3AB2972B1A60032A63B /* CoinbaseRatesProvider.swift */; }; + C9D2C8A72A320AA000D15901 /* TaxReportGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13DE299DF5C6006903F1 /* TaxReportGenerator.swift */; }; + C9D2C8A82A320AA000D15901 /* CrowdNodeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D112909513F00D406C1 /* CrowdNodeError.swift */; }; + C9D2C8A92A320AA000D15901 /* WithdrawalLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C4D296C52F800788E18 /* WithdrawalLimit.swift */; }; + C9D2C8AA2A320AA000D15901 /* DWPayModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E6422FFE43500FF8653 /* DWPayModel.m */; }; + C9D2C8AB2A320AA000D15901 /* DWSearchStateViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FC62449B66500B9F679 /* DWSearchStateViewController.m */; }; + C9D2C8AC2A320AA000D15901 /* DWDPTxListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3A724BE0C7D00DB32DE /* DWDPTxListCell.m */; }; + C9D2C8AD2A320AA000D15901 /* PointOfUseDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1728C63F9C00490F5E /* PointOfUseDetailsView.swift */; }; + C9D2C8AE2A320AA000D15901 /* DSChain+DashWallet.m in Sources */ = {isa = PBXBuildFile; fileRef = 47E4F7C6297596D8006BEA68 /* DSChain+DashWallet.m */; }; + C9D2C8AF2A320AA000D15901 /* FromLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193FF3A2961960B004EA8D7 /* FromLabel.swift */; }; + C9D2C8B02A320AA000D15901 /* DWPhraseRepairChildViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9172D225233F4F0024B4C5 /* DWPhraseRepairChildViewController.m */; }; + C9D2C8B12A320AA000D15901 /* SingleInputAddressSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B8449928F6D5480082770C /* SingleInputAddressSelector.swift */; }; + C9D2C8B22A320AA000D15901 /* CoinbaseAccountAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFAC28C896BC000427E7 /* CoinbaseAccountAddress.swift */; }; + C9D2C8B32A320AA000D15901 /* DWMainMenuTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BBC2347950700451078 /* DWMainMenuTableViewCell.m */; }; + C9D2C8B42A320AA000D15901 /* CrowdNodeTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1141E4C1291BB12200ACDA9E /* CrowdNodeTransferViewController.swift */; }; + C9D2C8B52A320AA000D15901 /* DWUserSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB2373724488DB80081B62C /* DWUserSearchViewController.m */; }; + C9D2C8B62A320AA000D15901 /* CBAuthInterop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478483E729629C0700E05A5A /* CBAuthInterop.swift */; }; + C9D2C8B72A320AA000D15901 /* UIFont+DWDPItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF38E2482BDF1001D74F9 /* UIFont+DWDPItem.m */; }; + C9D2C8B82A320AA000D15901 /* CoinbaseAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4789D26F29825F5400BAFEFA /* CoinbaseAmountViewController.swift */; }; + C9D2C8B92A320AA000D15901 /* DWUpholdAuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE482230FF4600956D5F /* DWUpholdAuthViewController.m */; }; + C9D2C8BA2A320AA000D15901 /* DWSegmentSliderFormCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F640E238D5C0900A9B505 /* DWSegmentSliderFormCellModel.m */; }; + C9D2C8BB2A320AA000D15901 /* DWSeedPhraseView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE5F22D8E9D900C99324 /* DWSeedPhraseView.m */; }; + C9D2C8BC2A320AA000D15901 /* DWDPAvatarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3CCEFB242BB1DD00300AF8 /* DWDPAvatarView.m */; }; + C9D2C8BD2A320AA000D15901 /* UIStackView+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B30D7B29100ABA0080C326 /* UIStackView+DashWallet.swift */; }; + C9D2C8BE2A320AA000D15901 /* DWUserProfileContactActionsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D7B24644E46001D7C0D /* DWUserProfileContactActionsCell.m */; }; + C9D2C8BF2A320AA000D15901 /* ExplorePointOfUse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8B9B28BFAD2800490F5E /* ExplorePointOfUse.swift */; }; + C9D2C8C02A320AA000D15901 /* DWUserProfileDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3B124BE3CDC00DB32DE /* DWUserProfileDataSourceObject.m */; }; + C9D2C8C12A320AA000D15901 /* DWDashPayModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EF0524193AEB002C32F3 /* DWDashPayModel.m */; }; + C9D2C8C22A320AA000D15901 /* TransactionObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 117ED4A028EC86E0006E3EE4 /* TransactionObserver.swift */; }; + C9D2C8C32A320AA000D15901 /* DWInfoTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431D322D52F67009BAF7F /* DWInfoTextCell.m */; }; + C9D2C8C42A320AA000D15901 /* ColorizedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EE17182959CDC200BA1986 /* ColorizedText.swift */; }; + C9D2C8C52A320AA000D15901 /* DWDPPendingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF37A2482756D001D74F9 /* DWDPPendingRequestObject.m */; }; + C9D2C8C62A320AA000D15901 /* DWModalUserProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A80F39424D86201003E3B1E /* DWModalUserProfileViewController.m */; }; + C9D2C8C72A320AA000D15901 /* PaymentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FA229DBC183001BC549 /* PaymentsViewController.swift */; }; + C9D2C8C82A320AA000D15901 /* DWDPContactsItemsFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3822482954C001D74F9 /* DWDPContactsItemsFactory.m */; }; + C9D2C8C92A320AA000D15901 /* ServiceDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4774DCDC28F43A68008CF87D /* ServiceDataSource.swift */; }; + C9D2C8CA2A320AA000D15901 /* ConverterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B792900196F0003E8AB /* ConverterView.swift */; }; + C9D2C8CB2A320AA000D15901 /* DWModalBaseAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69C923142E11001B8C90 /* DWModalBaseAnimation.m */; }; + C9D2C8CC2A320AA000D15901 /* SpendableTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB729DFC506001BC549 /* SpendableTransaction.swift */; }; + C9D2C8CD2A320AA000D15901 /* StakingInfoDialogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11ED906A29681773003784F9 /* StakingInfoDialogController.swift */; }; + C9D2C8CE2A320AA000D15901 /* DWCreateUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B64023CDB98A0016F221 /* DWCreateUsernameViewController.m */; }; + C9D2C8CF2A320AA000D15901 /* TwoFactorAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F36937D2919A5DB007F4E91 /* TwoFactorAuthViewController.swift */; }; + C9D2C8D02A320AA000D15901 /* Taxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471A2609289ACDF70056B7B2 /* Taxes.swift */; }; + C9D2C8D12A320AA000D15901 /* DWSeedWordModel+DWLayoutSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE9622DD0E8E00C99324 /* DWSeedWordModel+DWLayoutSupport.m */; }; + C9D2C8D22A320AA000D15901 /* CoinbaseAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EEE242293F436200049E0B /* CoinbaseAPIClient.swift */; }; + C9D2C8D32A320AA000D15901 /* SendAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B30D7F291123D30080C326 /* SendAmountViewController.swift */; }; + C9D2C8D42A320AA000D15901 /* DWAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8F420E21BEE95D00858B91 /* DWAboutViewController.m */; }; + C9D2C8D52A320AA000D15901 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF46A0296540EF0067B6EE /* AccountService.swift */; }; + C9D2C8D62A320AA000D15901 /* ListHandlerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C6E6E6291A90B2003FEDF2 /* ListHandlerView.swift */; }; + C9D2C8D72A320AA000D15901 /* DWSecurityMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BCC2347F01B00451078 /* DWSecurityMenuViewController.m */; }; + C9D2C8D82A320AA000D15901 /* CrowdNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BD738028E7356100A34022 /* CrowdNode.swift */; }; + C9D2C8D92A320AA000D15901 /* SQLite+ExloreDash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8B9E28BFAD8200490F5E /* SQLite+ExloreDash.swift */; }; + C9D2C8DA2A320AA000D15901 /* DWFilterHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E534A22F03A9E00E5168A /* DWFilterHeaderView.m */; }; + C9D2C8DB2A320AA000D15901 /* DWBaseContactsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A951CE323D1B92C00602824 /* DWBaseContactsModel.m */; }; + C9D2C8DC2A320AA000D15901 /* DWAdvancedSecurityModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F6414238FEEA900A9B505 /* DWAdvancedSecurityModel.m */; }; + C9D2C8DD2A320AA000D15901 /* AtmDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD528C1305E00490F5E /* AtmDataProvider.swift */; }; + C9D2C8DE2A320AA000D15901 /* GettingStartedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110C67942921147F006B580C /* GettingStartedViewController.swift */; }; + C9D2C8DF2A320AA000D15901 /* NetworkUnavailableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477A963D292CD27D0013605B /* NetworkUnavailableView.swift */; }; + C9D2C8E02A320AA000D15901 /* DWBorderedActionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BE62348E9AA00451078 /* DWBorderedActionButton.m */; }; + C9D2C8E12A320AA000D15901 /* DWLockScreenViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A63004D2328F37C00827825 /* DWLockScreenViewController.m */; }; + C9D2C8E22A320AA000D15901 /* UpholdTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4789D2302981069700BAFEFA /* UpholdTransferViewController.swift */; }; + C9D2C8E32A320AA000D15901 /* DWVerifySeedPhraseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE8322DC9B5D00C99324 /* DWVerifySeedPhraseViewController.m */; }; + C9D2C8E42A320AA000D15901 /* DerivationPathKeysCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909615429F297DD00002D82 /* DerivationPathKeysCell.swift */; }; + C9D2C8E52A320AA000D15901 /* Cells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474C721C298B65C100475CA6 /* Cells.swift */; }; + C9D2C8E62A320AA000D15901 /* DWContainerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E9723A400D3006A2A59 /* DWContainerViewController.m */; }; + C9D2C8E72A320AA000D15901 /* DashTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B30D79290D035B0080C326 /* DashTextAttachment.swift */; }; + C9D2C8E82A320AA000D15901 /* HomeBalanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F067F129E4576D0022D958 /* HomeBalanceView.swift */; }; + C9D2C8E92A320AA000D15901 /* PointOfUseListFiltersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B81290271170003E8AB /* PointOfUseListFiltersModel.swift */; }; + C9D2C8EA2A320AA000D15901 /* ConvertCryptoOrderPreviewViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CAD82970509600F63AC4 /* ConvertCryptoOrderPreviewViews.swift */; }; + C9D2C8EB2A320AA000D15901 /* DWAboutModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8F421F21BEFEEA00858B91 /* DWAboutModel.m */; }; + C9D2C8EC2A320AA000D15901 /* CNCreateAccountTxDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47083B3A298948B70010AF71 /* CNCreateAccountTxDetailsViewController.swift */; }; + C9D2C8ED2A320AA000D15901 /* UIColor+DWDashPay.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB7303D24D0BC0400DCB420 /* UIColor+DWDashPay.m */; }; + C9D2C8EE2A320AA000D15901 /* WelcomeToCrowdNodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1141E4C4291FDC7A00ACDA9E /* WelcomeToCrowdNodeViewController.swift */; }; + C9D2C8EF2A320AA000D15901 /* TerritoriesListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C6E6E229196D48003FEDF2 /* TerritoriesListCell.swift */; }; + C9D2C8F02A320AA000D15901 /* DWProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E531C22EA49FE00E5168A /* DWProgressView.m */; }; + C9D2C8F12A320AA000D15901 /* TxDetailCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A5146328491C60005A8E3E /* TxDetailCells.swift */; }; + C9D2C8F22A320AA000D15901 /* BaseViewController+NetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477F501629543834003C7508 /* BaseViewController+NetworkReachability.swift */; }; + C9D2C8F32A320AA000D15901 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 75D5F3CD191EC270004AB296 /* main.m */; }; + C9D2C8F42A320AA000D15901 /* DWQuickReceiveViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C24B323336FEA00000D43 /* DWQuickReceiveViewController.m */; }; + C9D2C8F62A320AA000D15901 /* libDashSync.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FC75A8E28F03E22000E4858 /* libDashSync.a */; }; + C9D2C8F72A320AA000D15901 /* CoreNFC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A5279BB23D994BC00F856D3 /* CoreNFC.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + C9D2C8F82A320AA000D15901 /* BackgroundTasks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB3E9F5F236125F600C09C5C /* BackgroundTasks.framework */; }; + C9D2C8F92A320AA000D15901 /* GameplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AD1CE9822DE63FA00C99324 /* GameplayKit.framework */; }; + C9D2C8FA2A320AA000D15901 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB4FA9C222505DD60060B017 /* AudioToolbox.framework */; }; + C9D2C8FB2A320AA000D15901 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AA08533237D6CF500797F95 /* CloudKit.framework */; }; + C9D2C8FC2A320AA000D15901 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB248B621F79BB7C00405AE0 /* SafariServices.framework */; }; + C9D2C8FD2A320AA000D15901 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB248B5C1F73803100405AE0 /* UserNotifications.framework */; }; + C9D2C8FE2A320AA000D15901 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBF3F4301E42B02800C7248E /* ImageIO.framework */; }; + C9D2C8FF2A320AA000D15901 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBF3F42E1E42B01E00C7248E /* CoreGraphics.framework */; }; + C9D2C9002A320AA000D15901 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBF3F42C1E42B00C00C7248E /* UIKit.framework */; }; + C9D2C9012A320AA000D15901 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2ABCA9172357A61B00092C09 /* Foundation.framework */; }; + C9D2C9022A320AA000D15901 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBF3F42A1E42AF8F00C7248E /* QuartzCore.framework */; }; + C9D2C9032A320AA000D15901 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 225383001C694D7400968BEE /* CoreLocation.framework */; }; + C9D2C9042A320AA000D15901 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 22D3613C1C56F2CD0057CF76 /* libsqlite3.tbd */; }; + C9D2C9052A320AA000D15901 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 222E7F571C46E9BE009AB45D /* SystemConfiguration.framework */; }; + C9D2C9062A320AA000D15901 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 222E7F551C46E9B8009AB45D /* Security.framework */; }; + C9D2C9072A320AA000D15901 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 222040C51C1A1940005CE1C3 /* WebKit.framework */; }; + C9D2C9082A320AA000D15901 /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 22B6A4471C0E963900673913 /* libbz2.tbd */; }; + C9D2C90B2A320AA000D15901 /* DWShortcutCollectionViewCell~ipad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A1AF6DC23C7681B00442AF5 /* DWShortcutCollectionViewCell~ipad.xib */; }; + C9D2C90C2A320AA000D15901 /* DWDPRegistrationErrorTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2AFF01DE243F74BE003718DC /* DWDPRegistrationErrorTableViewCell.xib */; }; + C9D2C90D2A320AA000D15901 /* ExploreDash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 47838B7E290160860003E8AB /* ExploreDash.storyboard */; }; + C9D2C90E2A320AA000D15901 /* TxDetailHeaderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 474C720C298A19D100475CA6 /* TxDetailHeaderCell.xib */; }; + C9D2C90F2A320AA000D15901 /* StartStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2AB231D32196E27300A6E7E6 /* StartStoryboard.storyboard */; }; + C9D2C9102A320AA000D15901 /* DWConfirmUsernameContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A60C9442444BF3900AF72CF /* DWConfirmUsernameContentView.xib */; }; + C9D2C9112A320AA000D15901 /* DWShortcutCollectionViewCell~iphone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A1AF6DD23C7681B00442AF5 /* DWShortcutCollectionViewCell~iphone.xib */; }; + C9D2C9122A320AA000D15901 /* DWInfoTextCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4431D422D52F67009BAF7F /* DWInfoTextCell.xib */; }; + C9D2C9132A320AA000D15901 /* BiometricAuth.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A44314822CF82EF009BAF7F /* BiometricAuth.storyboard */; }; + C9D2C9142A320AA000D15901 /* TxListEmptyTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E533F22F025FE00E5168A /* TxListEmptyTableViewCell.xib */; }; + C9D2C9152A320AA000D15901 /* Payments.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A8B9E3C22FD71E100FF8653 /* Payments.storyboard */; }; + C9D2C9162A320AA000D15901 /* DWFilterHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */; }; + C9D2C9172A320AA000D15901 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 757E09971ADB8EEB006FD352 /* Localizable.strings */; }; + C9D2C9182A320AA000D15901 /* LockScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A6300412328CCE900827825 /* LockScreen.storyboard */; }; + C9D2C9192A320AA000D15901 /* UpholdMainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A9FFE6E2230FF4600956D5F /* UpholdMainStoryboard.storyboard */; }; + C9D2C91A2A320AA000D15901 /* Migrations.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 4709C30E287E787700B4BD48 /* Migrations.bundle */; }; + C9D2C91B2A320AA000D15901 /* TxDetailTaxCategoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 474C720E298A1A3E00475CA6 /* TxDetailTaxCategoryCell.xib */; }; + C9D2C91C2A320AA000D15901 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 2ADC722723B5547000D9DD37 /* Localizable.stringsdict */; }; + C9D2C91D2A320AA000D15901 /* ShortcutsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A2CD71722F99CAE008C7BC9 /* ShortcutsView.xib */; }; + C9D2C91E2A320AA000D15901 /* HomeBalanceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F067F329E457790022D958 /* HomeBalanceView.xib */; }; + C9D2C91F2A320AA000D15901 /* DWDPRegistrationStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A5E4547243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.xib */; }; + C9D2C9202A320AA000D15901 /* TxDetailInfoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 474C7212298A1EFC00475CA6 /* TxDetailInfoCell.xib */; }; + C9D2C9212A320AA000D15901 /* UpholdOTPStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A9FFE552230FF4600956D5F /* UpholdOTPStoryboard.storyboard */; }; + C9D2C9222A320AA000D15901 /* AppAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A4430F122CBD57A009BAF7F /* AppAssets.xcassets */; }; + C9D2C9232A320AA000D15901 /* PayTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A8B9E6022FF712000FF8653 /* PayTableViewCell.xib */; }; + C9D2C9242A320AA000D15901 /* coinflip.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 75E83CF51B5F997A0038FB70 /* coinflip.aiff */; }; + C9D2C9252A320AA000D15901 /* SharedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2ADF83FE23633116008459A7 /* SharedAssets.xcassets */; }; + C9D2C9262A320AA000D15901 /* BackupInfo.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A4431C422D4CAFA009BAF7F /* BackupInfo.storyboard */; }; + C9D2C9272A320AA000D15901 /* SyncView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E531922EA382B00E5168A /* SyncView.xib */; }; + C9D2C9282A320AA000D15901 /* TxListTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E535322F1D0D900E5168A /* TxListTableViewCell.xib */; }; + C9D2C9292A320AA000D15901 /* BackupInfoItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F42FB529DD8702001BC549 /* BackupInfoItemView.xib */; }; + C9D2C92A2A320AA000D15901 /* explore.db in Resources */ = {isa = PBXBuildFile; fileRef = 47AE8BAF28BFF28400490F5E /* explore.db */; }; + C9D2C92B2A320AA000D15901 /* DWConfirmPaymentContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A0C69F323179C0F001B8C90 /* DWConfirmPaymentContentView.xib */; }; + C9D2C92C2A320AA000D15901 /* Coinbase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0F3693812919A70B007F4E91 /* Coinbase.storyboard */; }; + C9D2C92D2A320AA000D15901 /* DWTitleActionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C3DAD2C7247538AA0001624F /* DWTitleActionHeaderView.xib */; }; + C9D2C92E2A320AA000D15901 /* ImportWalletInfo.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A10EB3D2358BDA500C38B61 /* ImportWalletInfo.storyboard */; }; + C9D2C92F2A320AA000D15901 /* ReceiveContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F42FAC29DC115A001BC549 /* ReceiveContentView.xib */; }; + C9D2C9302A320AA000D15901 /* VerifySeedPhrase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2AD1CE8522DC9B7300C99324 /* VerifySeedPhrase.storyboard */; }; + C9D2C9312A320AA000D15901 /* DWSecurityStatusView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A7F3B20238C653000DEA3EF /* DWSecurityStatusView.xib */; }; + C9D2C9322A320AA000D15901 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2AB7C906234DB82700A56795 /* About.storyboard */; }; + C9D2C9332A320AA000D15901 /* SetPin.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A44312922CCC1D4009BAF7F /* SetPin.storyboard */; }; + C9D2C9342A320AA000D15901 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A913E7023A2667E006A2A59 /* Onboarding.storyboard */; }; + C9D2C9352A320AA000D15901 /* OperationStatus.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1193FF3D2962F1BE004EA8D7 /* OperationStatus.storyboard */; }; + C9D2C9362A320AA000D15901 /* UpholdLogoutTutorialStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A9FFE662230FF4600956D5F /* UpholdLogoutTutorialStoryboard.storyboard */; }; + C9D2C9372A320AA000D15901 /* UpholdAuthStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A9FFE492230FF4600956D5F /* UpholdAuthStoryboard.storyboard */; }; + C9D2C9382A320AA000D15901 /* CrowdNode.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1121A8B628E5DB6E00464C31 /* CrowdNode.storyboard */; }; + C9D2C9392A320AA000D15901 /* DWDPRegistrationDoneTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2AFF01E4243F8625003718DC /* DWDPRegistrationDoneTableViewCell.xib */; }; + C9D2C93A2A320AA000D15901 /* ResetWalletInfo.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A10EB402358D2A900C38B61 /* ResetWalletInfo.storyboard */; }; + C9D2C93B2A320AA000D15901 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB66977E212C0B940034BE4F /* LaunchScreen.storyboard */; }; + C9D2C93C2A320AA000D15901 /* Pay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A8B9E5B22FF6FE500FF8653 /* Pay.storyboard */; }; + C9D2C93D2A320AA000D15901 /* QuickReceive.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A8C24B5233370A600000D43 /* QuickReceive.storyboard */; }; + C9D2C93E2A320AA000D15901 /* Tx.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 47A514612848FEAD005A8E3E /* Tx.storyboard */; }; + C9D2C93F2A320AA000D15901 /* DashSyncCurrentCommit in Resources */ = {isa = PBXBuildFile; fileRef = 2A8F420821BED16300858B91 /* DashSyncCurrentCommit */; }; + C9D2C9402A320AA000D15901 /* DWMainMenuTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A7A7BBD2347950700451078 /* DWMainMenuTableViewCell.xib */; }; + C9D2C9412A320AA000D15901 /* VerifiedSuccessfully.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2AD1CE9E22DFBA6C00C99324 /* VerifiedSuccessfully.storyboard */; }; + C9D2C9422A320AA000D15901 /* Setup.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A4430EA22CBBA82009BAF7F /* Setup.storyboard */; }; + C9D2C9432A320AA000D15901 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C94F5E8729D3E7E30034FD57 /* GoogleService-Info.plist */; }; + C9D2C9442A320AA000D15901 /* uphold-logout.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 2A811558269CE09300215F81 /* uphold-logout.jpg */; }; + C9D2C9452A320AA000D15901 /* AmountPreviewView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2ACCD864231959CD00A96B62 /* AmountPreviewView.xib */; }; + C9D2C9462A320AA000D15901 /* TxDetailActionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 474C7214298A1FCF00475CA6 /* TxDetailActionCell.xib */; }; + C9D2C9472A320AA000D15901 /* CNCreateAccountCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47083B3729893F4B0010AF71 /* CNCreateAccountCell.xib */; }; + C9D2C95E2A386D7E00D15901 /* DWBasePressableControl.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C95D2A386D7E00D15901 /* DWBasePressableControl.m */; }; + C9D2C9622A386DA200D15901 /* DWDPWelcomeView.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C9602A386DA200D15901 /* DWDPWelcomeView.m */; }; + C9D2C9652A38733B00D15901 /* DPAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9D2C9642A38733B00D15901 /* DPAssets.xcassets */; }; C9F067F229E4576D0022D958 /* HomeBalanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F067F129E4576D0022D958 /* HomeBalanceView.swift */; }; C9F067F429E543630022D958 /* HomeBalanceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F067F329E457790022D958 /* HomeBalanceView.xib */; }; C9F42F9F29DA82E5001BC549 /* PayableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42F9E29DA82E5001BC549 /* PayableViewController.swift */; }; @@ -721,7 +1415,7 @@ 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 */; }; + DE3A167A235B79D705C0A962 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; FB248B5D1F73803100405AE0 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB248B5C1F73803100405AE0 /* UserNotifications.framework */; }; FB248B631F79BB7C00405AE0 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB248B621F79BB7C00405AE0 /* SafariServices.framework */; }; FB2E5537218BA161003A1B7C /* DWVersionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FB2E5536218BA161003A1B7C /* DWVersionManager.m */; }; @@ -897,6 +1591,8 @@ 11E47BAC28EB3A7D0097CFA0 /* CrowdNode+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CrowdNode+Constants.swift"; sourceTree = ""; }; 11EA863929952E9800ABD7B4 /* OutlinedTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutlinedTextField.swift; sourceTree = ""; }; 11ED906A29681773003784F9 /* StakingInfoDialogController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingInfoDialogController.swift; sourceTree = ""; }; + 17427514C25A58AB4AEDF999 /* libPods-dashwallet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-dashwallet.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1EBB53DD8E53B4B63E5A885E /* Pods-dashpay.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashpay.debug.xcconfig"; path = "Pods/Target Support Files/Pods-dashpay/Pods-dashpay.debug.xcconfig"; sourceTree = ""; }; 206554BC730E9F2BB594D044 /* Pods-TodayExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TodayExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TodayExtension/Pods-TodayExtension.debug.xcconfig"; sourceTree = ""; }; 2142023B66B3AB7DC260D9A9 /* Pods-TodayExtension.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TodayExtension.testflight.xcconfig"; path = "Pods/Target Support Files/Pods-TodayExtension/Pods-TodayExtension.testflight.xcconfig"; sourceTree = ""; }; 222040C51C1A1940005CE1C3 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; @@ -1623,6 +2319,7 @@ 2AFF01E4243F8625003718DC /* DWDPRegistrationDoneTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWDPRegistrationDoneTableViewCell.xib; sourceTree = ""; }; 3410FBA3C9ECBF291385B203 /* Pods-dashwallet no watch.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashwallet no watch.release.xcconfig"; path = "Pods/Target Support Files/Pods-dashwallet no watch/Pods-dashwallet no watch.release.xcconfig"; sourceTree = ""; }; 362213F26B43E7CB81B2FCE2 /* Pods-dashwallet.testnet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashwallet.testnet.xcconfig"; path = "Pods/Target Support Files/Pods-dashwallet/Pods-dashwallet.testnet.xcconfig"; sourceTree = ""; }; + 3BF640216ECD5FBFC17B85D3 /* Pods-dashpay.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashpay.testflight.xcconfig"; path = "Pods/Target Support Files/Pods-dashpay/Pods-dashpay.testflight.xcconfig"; sourceTree = ""; }; 470617D5299A671900DCC667 /* CrowdNodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdNodeCell.swift; sourceTree = ""; }; 47081196298CF20B003FCA3D /* Transaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transaction.swift; sourceTree = ""; }; 47081198298CF57D003FCA3D /* TransactionListDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TransactionListDataSource.swift; path = DashWallet/Sources/UI/Home/Models/TransactionListDataSource.swift; sourceTree = SOURCE_ROOT; }; @@ -1865,7 +2562,6 @@ 87872BF3D40B38CD0FC246AB /* Pods-DashWalletTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashWalletTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DashWalletTests/Pods-DashWalletTests.debug.xcconfig"; sourceTree = ""; }; 8A9877BEC5093CED81768D3D /* Pods-TodayExtension.testnet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TodayExtension.testnet.xcconfig"; path = "Pods/Target Support Files/Pods-TodayExtension/Pods-TodayExtension.testnet.xcconfig"; sourceTree = ""; }; 934DC791C539EABD2EE14E81 /* Pods-dashwallet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashwallet.release.xcconfig"; path = "Pods/Target Support Files/Pods-dashwallet/Pods-dashwallet.release.xcconfig"; sourceTree = ""; }; - 982607F21196681DAC51A074 /* libPods-dashwallet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-dashwallet.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A169BE797EC811F83373CCB9 /* Pods_dashwallet_no_watch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_dashwallet_no_watch.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A9529BDC33490B8F700E733D /* Pods-DashWalletScreenshotsUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashWalletScreenshotsUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-DashWalletScreenshotsUITests/Pods-DashWalletScreenshotsUITests.release.xcconfig"; sourceTree = ""; }; AA118F99071BC98E7056E3F4 /* Pods-dashwallet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashwallet.debug.xcconfig"; path = "Pods/Target Support Files/Pods-dashwallet/Pods-dashwallet.debug.xcconfig"; sourceTree = ""; }; @@ -1921,6 +2617,7 @@ C3DAD2D32476886D0001624F /* DWContactsSearchInfoHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsSearchInfoHeaderView.h; sourceTree = ""; }; C3DAD2D42476886D0001624F /* DWContactsSearchInfoHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWContactsSearchInfoHeaderView.m; sourceTree = ""; }; C47D5A9D319D41B450A9B96B /* libPods-WatchApp Extension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WatchApp Extension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + C4C041DE4CD73FF445090FA3 /* Pods-dashpay.testnet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashpay.testnet.xcconfig"; path = "Pods/Target Support Files/Pods-dashpay/Pods-dashpay.testnet.xcconfig"; sourceTree = ""; }; C909614C29EFF7D600002D82 /* WalletKeysOverviewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletKeysOverviewModel.swift; sourceTree = ""; }; C909615029F158D700002D82 /* DerivationPathKeysViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DerivationPathKeysViewController.swift; sourceTree = ""; }; C909615229F28E3700002D82 /* DerivationPathKeysModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DerivationPathKeysModel.swift; sourceTree = ""; }; @@ -1941,6 +2638,13 @@ C94F5E8F29D4060A0034FD57 /* ShortcutsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsView.swift; sourceTree = ""; }; C98AA93FF5283EC6405BCE4B /* Pods-WatchApp Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchApp Extension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WatchApp Extension/Pods-WatchApp Extension.debug.xcconfig"; sourceTree = ""; }; C9903A5329E6A5F600535A4E /* KeysOverviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeysOverviewViewController.swift; sourceTree = ""; }; + C9D2C9532A320AA000D15901 /* dashpay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = dashpay.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C9D2C9562A320F3700D15901 /* dashpay-info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "dashpay-info.plist"; sourceTree = ""; }; + C9D2C95C2A386D7E00D15901 /* DWBasePressableControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWBasePressableControl.h; sourceTree = ""; }; + C9D2C95D2A386D7E00D15901 /* DWBasePressableControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWBasePressableControl.m; sourceTree = ""; }; + C9D2C9602A386DA200D15901 /* DWDPWelcomeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPWelcomeView.m; sourceTree = ""; }; + C9D2C9612A386DA200D15901 /* DWDPWelcomeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPWelcomeView.h; sourceTree = ""; }; + C9D2C9642A38733B00D15901 /* DPAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DPAssets.xcassets; sourceTree = ""; }; C9F067F129E4576D0022D958 /* HomeBalanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeBalanceView.swift; sourceTree = ""; }; C9F067F329E457790022D958 /* HomeBalanceView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HomeBalanceView.xib; sourceTree = ""; }; C9F42F9E29DA82E5001BC549 /* PayableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayableViewController.swift; sourceTree = ""; }; @@ -1971,8 +2675,10 @@ 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 = ""; }; + CE89DF632BC53160BB8FBED1 /* libPods-dashpay.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-dashpay.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + EBB5D208A38257AF3DA73A99 /* Pods-dashpay.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashpay.release.xcconfig"; path = "Pods/Target Support Files/Pods-dashpay/Pods-dashpay.release.xcconfig"; sourceTree = ""; }; F12049B257DDE750DE332799 /* Pods-WatchApp.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchApp.testflight.xcconfig"; path = "Pods/Target Support Files/Pods-WatchApp/Pods-WatchApp.testflight.xcconfig"; sourceTree = ""; }; FB0ACB9E1F9AB76800F4AB52 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; FB0ACBA01F9ABB6200F4AB52 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; @@ -2049,7 +2755,8 @@ 222E7F561C46E9B8009AB45D /* Security.framework in Frameworks */, 222040C61C1A1940005CE1C3 /* WebKit.framework in Frameworks */, 22B6A4481C0E963900673913 /* libbz2.tbd in Frameworks */, - DE3A167A235B79D705C0A962 /* libPods-dashwallet.a in Frameworks */, + DE3A167A235B79D705C0A962 /* (null) in Frameworks */, + A90D08EA4AA9019A2D806A9C /* libPods-dashwallet.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2086,6 +2793,33 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C9D2C8F52A320AA000D15901 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C9D2C8F62A320AA000D15901 /* libDashSync.a in Frameworks */, + C9D2C8F72A320AA000D15901 /* CoreNFC.framework in Frameworks */, + C9D2C8F82A320AA000D15901 /* BackgroundTasks.framework in Frameworks */, + C9D2C8F92A320AA000D15901 /* GameplayKit.framework in Frameworks */, + C9D2C8FA2A320AA000D15901 /* AudioToolbox.framework in Frameworks */, + C9D2C8FB2A320AA000D15901 /* CloudKit.framework in Frameworks */, + C9D2C8FC2A320AA000D15901 /* SafariServices.framework in Frameworks */, + C9D2C8FD2A320AA000D15901 /* UserNotifications.framework in Frameworks */, + C9D2C8FE2A320AA000D15901 /* ImageIO.framework in Frameworks */, + C9D2C8FF2A320AA000D15901 /* CoreGraphics.framework in Frameworks */, + C9D2C9002A320AA000D15901 /* UIKit.framework in Frameworks */, + C9D2C9012A320AA000D15901 /* Foundation.framework in Frameworks */, + C9D2C9022A320AA000D15901 /* QuartzCore.framework in Frameworks */, + C9D2C9032A320AA000D15901 /* CoreLocation.framework in Frameworks */, + C9D2C9042A320AA000D15901 /* libsqlite3.tbd in Frameworks */, + C9D2C9052A320AA000D15901 /* SystemConfiguration.framework in Frameworks */, + C9D2C9062A320AA000D15901 /* Security.framework in Frameworks */, + C9D2C9072A320AA000D15901 /* WebKit.framework in Frameworks */, + C9D2C9082A320AA000D15901 /* libbz2.tbd in Frameworks */, + AEC28D84403A194A6C7D8C5A /* libPods-dashpay.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -5449,6 +6183,7 @@ 75D5F3B5191EC270004AB296 = { isa = PBXGroup; children = ( + C9D2C9552A320AC100D15901 /* DashPay */, 2ADF83FB23632D55008459A7 /* Shared */, 75D5F3C7191EC270004AB296 /* DashWallet */, BAE12BE81B2DEE7F00895CC5 /* TodayExtension */, @@ -5471,6 +6206,7 @@ BA913BDE1BD57E4D005A7C0E /* WatchApp.app */, BA913BEA1BD57E4D005A7C0E /* WatchApp Extension.appex */, 2A4663002279DC2F0027533B /* DashWalletScreenshotsUITests.xctest */, + C9D2C9532A320AA000D15901 /* dashpay.app */, ); name = Products; sourceTree = ""; @@ -5507,8 +6243,9 @@ 02771AC5DDCA0A1749C6A05B /* libPods-TodayExtension.a */, 07283055DE20FE578E399BE7 /* libPods-WatchApp.a */, C47D5A9D319D41B450A9B96B /* libPods-WatchApp Extension.a */, - 982607F21196681DAC51A074 /* libPods-dashwallet.a */, 0CDD4C961516ED20BC9F01FA /* libPods-DashWalletScreenshotsUITests.a */, + CE89DF632BC53160BB8FBED1 /* libPods-dashpay.a */, + 17427514C25A58AB4AEDF999 /* libPods-dashwallet.a */, ); name = Frameworks; sourceTree = ""; @@ -5657,6 +6394,67 @@ path = Model; sourceTree = ""; }; + C9D2C9552A320AC100D15901 /* DashPay */ = { + isa = PBXGroup; + children = ( + C9D2C9632A38732900D15901 /* Assets */, + C9D2C9582A386A5B00D15901 /* Presentation */, + C9D2C9562A320F3700D15901 /* dashpay-info.plist */, + ); + path = DashPay; + sourceTree = ""; + }; + C9D2C9582A386A5B00D15901 /* Presentation */ = { + isa = PBXGroup; + children = ( + C9D2C9592A386A6700D15901 /* Home */, + C9D2C95A2A386D5200D15901 /* Shared */, + ); + path = Presentation; + sourceTree = ""; + }; + C9D2C9592A386A6700D15901 /* Home */ = { + isa = PBXGroup; + children = ( + C9D2C95F2A386D9700D15901 /* Views */, + ); + path = Home; + sourceTree = ""; + }; + C9D2C95A2A386D5200D15901 /* Shared */ = { + isa = PBXGroup; + children = ( + C9D2C95B2A386D6C00D15901 /* Buttons */, + ); + path = Shared; + sourceTree = ""; + }; + C9D2C95B2A386D6C00D15901 /* Buttons */ = { + isa = PBXGroup; + children = ( + C9D2C95C2A386D7E00D15901 /* DWBasePressableControl.h */, + C9D2C95D2A386D7E00D15901 /* DWBasePressableControl.m */, + ); + path = Buttons; + sourceTree = ""; + }; + C9D2C95F2A386D9700D15901 /* Views */ = { + isa = PBXGroup; + children = ( + C9D2C9612A386DA200D15901 /* DWDPWelcomeView.h */, + C9D2C9602A386DA200D15901 /* DWDPWelcomeView.m */, + ); + path = Views; + sourceTree = ""; + }; + C9D2C9632A38732900D15901 /* Assets */ = { + isa = PBXGroup; + children = ( + C9D2C9642A38733B00D15901 /* DPAssets.xcassets */, + ); + path = Assets; + sourceTree = ""; + }; C9F42FA729DC09C6001BC549 /* Style */ = { isa = PBXGroup; children = ( @@ -5742,6 +6540,10 @@ 75EDC78DE1686E55AE12233C /* Pods-DashWalletScreenshotsUITests.testnet.xcconfig */, A9529BDC33490B8F700E733D /* Pods-DashWalletScreenshotsUITests.release.xcconfig */, EA95ACF6CA2A73810B9BB451 /* Pods-DashWalletScreenshotsUITests.testflight.xcconfig */, + 1EBB53DD8E53B4B63E5A885E /* Pods-dashpay.debug.xcconfig */, + C4C041DE4CD73FF445090FA3 /* Pods-dashpay.testnet.xcconfig */, + EBB5D208A38257AF3DA73A99 /* Pods-dashpay.release.xcconfig */, + 3BF640216ECD5FBFC17B85D3 /* Pods-dashpay.testflight.xcconfig */, ); name = Pods; sourceTree = ""; @@ -5881,12 +6683,36 @@ productReference = BAE12BE51B2DEE7F00895CC5 /* TodayExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + C9D2C68A2A320AA000D15901 /* dashpay */ = { + isa = PBXNativeTarget; + buildConfigurationList = C9D2C94E2A320AA000D15901 /* Build configuration list for PBXNativeTarget "dashpay" */; + buildPhases = ( + C9D2C68F2A320AA000D15901 /* [CP] Check Pods Manifest.lock */, + C9D2C6902A320AA000D15901 /* Run Script - Uphold Constants */, + C9D2C6912A320AA000D15901 /* Run Script - clang-format */, + C9D2C6922A320AA000D15901 /* Run Script - bartycrouch */, + C9D2C6932A320AA000D15901 /* Sources */, + C9D2C8F52A320AA000D15901 /* Frameworks */, + C9D2C90A2A320AA000D15901 /* Resources */, + C9D2C94C2A320AA000D15901 /* [CP] Copy Pods Resources */, + C9D2C94D2A320AA000D15901 /* Run Script - Cleanup Uphold Constants */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = dashpay; + productName = DashWallet; + productReference = C9D2C9532A320AA000D15901 /* dashpay.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 75D5F3B6191EC270004AB296 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; CLASSPREFIX = DW; LastSwiftUpdateCheck = 1020; LastUpgradeCheck = 1400; @@ -5976,6 +6802,7 @@ projectRoot = ""; targets = ( 75D5F3BD191EC270004AB296 /* dashwallet */, + C9D2C68A2A320AA000D15901 /* dashpay */, BAE12BE41B2DEE7F00895CC5 /* TodayExtension */, 75D5F3E4191EC270004AB296 /* DashWalletTests */, BA913BDD1BD57E4D005A7C0E /* WatchApp */, @@ -6097,6 +6924,75 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C9D2C90A2A320AA000D15901 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C9D2C90B2A320AA000D15901 /* DWShortcutCollectionViewCell~ipad.xib in Resources */, + C9D2C90C2A320AA000D15901 /* DWDPRegistrationErrorTableViewCell.xib in Resources */, + C9D2C90D2A320AA000D15901 /* ExploreDash.storyboard in Resources */, + C9D2C90E2A320AA000D15901 /* TxDetailHeaderCell.xib in Resources */, + C9D2C9652A38733B00D15901 /* DPAssets.xcassets in Resources */, + C9D2C90F2A320AA000D15901 /* StartStoryboard.storyboard in Resources */, + C9D2C9102A320AA000D15901 /* DWConfirmUsernameContentView.xib in Resources */, + C9D2C9112A320AA000D15901 /* DWShortcutCollectionViewCell~iphone.xib in Resources */, + C9D2C9122A320AA000D15901 /* DWInfoTextCell.xib in Resources */, + C9D2C9132A320AA000D15901 /* BiometricAuth.storyboard in Resources */, + C9D2C9142A320AA000D15901 /* TxListEmptyTableViewCell.xib in Resources */, + C9D2C9152A320AA000D15901 /* Payments.storyboard in Resources */, + C9D2C9162A320AA000D15901 /* DWFilterHeaderView.xib in Resources */, + C9D2C9172A320AA000D15901 /* Localizable.strings in Resources */, + C9D2C9182A320AA000D15901 /* LockScreen.storyboard in Resources */, + C9D2C9192A320AA000D15901 /* UpholdMainStoryboard.storyboard in Resources */, + C9D2C91A2A320AA000D15901 /* Migrations.bundle in Resources */, + C9D2C91B2A320AA000D15901 /* TxDetailTaxCategoryCell.xib in Resources */, + C9D2C91C2A320AA000D15901 /* Localizable.stringsdict in Resources */, + C9D2C91D2A320AA000D15901 /* ShortcutsView.xib in Resources */, + C9D2C91E2A320AA000D15901 /* HomeBalanceView.xib in Resources */, + C9D2C91F2A320AA000D15901 /* DWDPRegistrationStatusTableViewCell.xib in Resources */, + C9D2C9202A320AA000D15901 /* TxDetailInfoCell.xib in Resources */, + C9D2C9212A320AA000D15901 /* UpholdOTPStoryboard.storyboard in Resources */, + C9D2C9222A320AA000D15901 /* AppAssets.xcassets in Resources */, + C9D2C9232A320AA000D15901 /* PayTableViewCell.xib in Resources */, + C9D2C9242A320AA000D15901 /* coinflip.aiff in Resources */, + C9D2C9252A320AA000D15901 /* SharedAssets.xcassets in Resources */, + C9D2C9262A320AA000D15901 /* BackupInfo.storyboard in Resources */, + C9D2C9272A320AA000D15901 /* SyncView.xib in Resources */, + C9D2C9282A320AA000D15901 /* TxListTableViewCell.xib in Resources */, + C9D2C9292A320AA000D15901 /* BackupInfoItemView.xib in Resources */, + C9D2C92A2A320AA000D15901 /* explore.db in Resources */, + C9D2C92B2A320AA000D15901 /* DWConfirmPaymentContentView.xib in Resources */, + C9D2C92C2A320AA000D15901 /* Coinbase.storyboard in Resources */, + C9D2C92D2A320AA000D15901 /* DWTitleActionHeaderView.xib in Resources */, + C9D2C92E2A320AA000D15901 /* ImportWalletInfo.storyboard in Resources */, + C9D2C92F2A320AA000D15901 /* ReceiveContentView.xib in Resources */, + C9D2C9302A320AA000D15901 /* VerifySeedPhrase.storyboard in Resources */, + C9D2C9312A320AA000D15901 /* DWSecurityStatusView.xib in Resources */, + C9D2C9322A320AA000D15901 /* About.storyboard in Resources */, + C9D2C9332A320AA000D15901 /* SetPin.storyboard in Resources */, + C9D2C9342A320AA000D15901 /* Onboarding.storyboard in Resources */, + C9D2C9352A320AA000D15901 /* OperationStatus.storyboard in Resources */, + C9D2C9362A320AA000D15901 /* UpholdLogoutTutorialStoryboard.storyboard in Resources */, + C9D2C9372A320AA000D15901 /* UpholdAuthStoryboard.storyboard in Resources */, + C9D2C9382A320AA000D15901 /* CrowdNode.storyboard in Resources */, + C9D2C9392A320AA000D15901 /* DWDPRegistrationDoneTableViewCell.xib in Resources */, + C9D2C93A2A320AA000D15901 /* ResetWalletInfo.storyboard in Resources */, + C9D2C93B2A320AA000D15901 /* LaunchScreen.storyboard in Resources */, + C9D2C93C2A320AA000D15901 /* Pay.storyboard in Resources */, + C9D2C93D2A320AA000D15901 /* QuickReceive.storyboard in Resources */, + C9D2C93E2A320AA000D15901 /* Tx.storyboard in Resources */, + C9D2C93F2A320AA000D15901 /* DashSyncCurrentCommit in Resources */, + C9D2C9402A320AA000D15901 /* DWMainMenuTableViewCell.xib in Resources */, + C9D2C9412A320AA000D15901 /* VerifiedSuccessfully.storyboard in Resources */, + C9D2C9422A320AA000D15901 /* Setup.storyboard in Resources */, + C9D2C9432A320AA000D15901 /* GoogleService-Info.plist in Resources */, + C9D2C9442A320AA000D15901 /* uphold-logout.jpg in Resources */, + C9D2C9452A320AA000D15901 /* AmountPreviewView.xib in Resources */, + C9D2C9462A320AA000D15901 /* TxDetailActionCell.xib in Resources */, + C9D2C9472A320AA000D15901 /* CNCreateAccountCell.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -6302,6 +7198,112 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + C9D2C68F2A320AA000D15901 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-dashpay-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C9D2C6902A320AA000D15901 /* Run Script - Uphold Constants */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script - Uphold Constants"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ \"${CONFIGURATION}\" = \"Release\" ] ; then\n if [ ! -f \"DWUpholdMainnetConstants__Release.m\" ]; then\n echo \"error: DWUpholdMainnetConstants__Release.m file not found!\"\n exit 1\n fi\n\n cp \"DWUpholdMainnetConstants__Release.m\" \"DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m\"\nelse\n # check if original file exist (in case it was moved or deleted while refactoring)\n if [ ! -f \"DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m\" ]; then\n echo \"error: DWUpholdMainnetConstants.m file not found! Fix Uphold Constants build phase\"\n exit 1\n fi\nfi\n"; + }; + C9D2C6912A320AA000D15901 /* Run Script - clang-format */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script - clang-format"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "#!/bin/bash\n\necho \"Reformatting code with clang-format\"\n\nfunction formatObjCFiles() {\n find \"$1\" -name '*.h' -print0 | xargs -0 clang-format -i -style=file\n find \"$1\" -name '*.m' -print0 | xargs -0 clang-format -i -style=file\n find \"$1\" -name '*.mm' -print0 | xargs -0 clang-format -i -style=file\n}\n\nif which clang-format >/dev/null; then\n for SUBDIR in DashWallet/Sources\n do\n formatObjCFiles $SUBDIR\n done\n \n formatObjCFiles TodayExtension\n formatObjCFiles Shared/Sources\nelse\n echo \"warning: clang-format not installed, install it by running `brew install clang-format`\"\nfi\n"; + }; + C9D2C6922A320AA000D15901 /* Run Script - bartycrouch */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script - bartycrouch"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which bartycrouch > /dev/null; then\n BTCRCH=`which bartycrouch`\n env -i $BTCRCH update -x\n env -i $BTCRCH lint -x\nelse\n echo \"warning: BartyCrouch not installed, download it from https://github.com/Flinesoft/BartyCrouch\"\nfi\n"; + }; + C9D2C94C2A320AA000D15901 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-dashpay/Pods-dashpay-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/DashSync/DashSync.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DashSync.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-dashpay/Pods-dashpay-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + C9D2C94D2A320AA000D15901 /* Run Script - Cleanup Uphold Constants */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script - Cleanup Uphold Constants"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif [ \"${CONFIGURATION}\" = \"Release\" ] ; then\nif [ ! -f \"DWUpholdMainnetConstants__Empty.m\" ]; then\n echo \"error: DWUpholdMainnetConstants__Empty.m file not found!\"\n exit 1\nfi\n\ncp \"DWUpholdMainnetConstants__Empty.m\" \"DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m\"\nelse\n # check if original file exist (in case it was moved or deleted while refactoring)\n if [ ! -f \"DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m\" ]; then\n echo \"error: DWUpholdMainnetConstants.m file not found! Fix Uphold Constants build phase\"\n exit 1\n fi\nfi\n"; + }; DD2839496F426BDB0829397D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -7023,6 +8025,624 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C9D2C6932A320AA000D15901 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C9D2C6942A320AA000D15901 /* DWReceiveModelStub.m in Sources */, + C9D2C6952A320AA000D15901 /* DWBaseAdvancedSecurityModel.m in Sources */, + C9D2C6962A320AA000D15901 /* DWBaseViewController.m in Sources */, + C9D2C6972A320AA000D15901 /* DWBiometricAuthViewController.m in Sources */, + C9D2C6982A320AA000D15901 /* CrowdNodeWebService.swift in Sources */, + C9D2C6992A320AA000D15901 /* DWStartViewController.m in Sources */, + C9D2C69A2A320AA000D15901 /* CrowdNodeAPI.swift in Sources */, + C9D2C69B2A320AA000D15901 /* DWStartModel.m in Sources */, + C9D2C69C2A320AA000D15901 /* DWAdvancedSecurityModelStub.m in Sources */, + C9D2C69D2A320AA000D15901 /* Foundation+Bitcoin.swift in Sources */, + C9D2C69E2A320AA000D15901 /* AtmDetailsView.swift in Sources */, + C9D2C69F2A320AA000D15901 /* DWPlaceholderFormTableViewCell.m in Sources */, + C9D2C6A02A320AA000D15901 /* DWTitleDetailCellView.m in Sources */, + C9D2C6A12A320AA000D15901 /* AmountInputControl.swift in Sources */, + C9D2C6A22A320AA000D15901 /* DWSegmentSliderFormTableViewCell.m in Sources */, + C9D2C6A32A320AA000D15901 /* ApiCode.swift in Sources */, + C9D2C6A42A320AA000D15901 /* CoinbaseEntryPointModel.swift in Sources */, + C9D2C6A52A320AA000D15901 /* SyncingAlertViewController.swift in Sources */, + C9D2C6A62A320AA000D15901 /* DWDashPayConstants.m in Sources */, + C9D2C6A72A320AA000D15901 /* CrowdNodeResponse.swift in Sources */, + C9D2C6A82A320AA000D15901 /* FileManager+DashWallet.swift in Sources */, + C9D2C6A92A320AA000D15901 /* PointOfUseItemCell.swift in Sources */, + C9D2C6AA2A320AA000D15901 /* DSTransaction+DashWallet.swift in Sources */, + C9D2C6AB2A320AA000D15901 /* DWConfirmUsernameViewController.m in Sources */, + C9D2C6AC2A320AA000D15901 /* PayableViewController.swift in Sources */, + C9D2C6AD2A320AA000D15901 /* HairlineView.swift in Sources */, + C9D2C6AE2A320AA000D15901 /* DWInitialViewController.m in Sources */, + C9D2C6AF2A320AA000D15901 /* PortalViewController.swift in Sources */, + C9D2C6B02A320AA000D15901 /* DWSettingsMenuViewController.m in Sources */, + C9D2C6B12A320AA000D15901 /* AboutViewController.swift in Sources */, + C9D2C6B22A320AA000D15901 /* UISpringTimingParameters+DWInit.m in Sources */, + C9D2C6B32A320AA000D15901 /* DWDPNewIncomingRequestObject.m in Sources */, + C9D2C6B42A320AA000D15901 /* DWToolsMenuModel.m in Sources */, + C9D2C6B52A320AA000D15901 /* DWNotificationsViewController.m in Sources */, + C9D2C6B62A320AA000D15901 /* NumberKeyboardButton.swift in Sources */, + C9D2C6B72A320AA000D15901 /* IsDefaultEmail.swift in Sources */, + C9D2C6B82A320AA000D15901 /* DWHomeViewController+DWSecureWalletDelegateImpl.m in Sources */, + C9D2C6B92A320AA000D15901 /* DWNotificationsProvider.m in Sources */, + C9D2C6BA2A320AA000D15901 /* DWUpholdAccountObject.m in Sources */, + C9D2C6BB2A320AA000D15901 /* DWFormTableViewController.m in Sources */, + C9D2C6BC2A320AA000D15901 /* WalletKeysOverviewModel.swift in Sources */, + C9D2C6BD2A320AA000D15901 /* UIAssembly.swift in Sources */, + C9D2C6BE2A320AA000D15901 /* DWLocationManager.swift in Sources */, + C9D2C6BF2A320AA000D15901 /* FetchingNextPageCell.swift in Sources */, + C9D2C6C02A320AA000D15901 /* Transaction.swift in Sources */, + C9D2C6C12A320AA000D15901 /* AllMerchantLocationsViewController.swift in Sources */, + C9D2C6C22A320AA000D15901 /* DWPaymentOutput.m in Sources */, + C9D2C6C32A320AA000D15901 /* DWPayModelStub.m in Sources */, + C9D2C6C42A320AA000D15901 /* DWRecoverContentView.m in Sources */, + C9D2C6C52A320AA000D15901 /* DWSeedPhraseViewLayout.m in Sources */, + C9D2C6C62A320AA000D15901 /* Coinbase+Error.swift in Sources */, + C9D2C6C72A320AA000D15901 /* DWPaymentOutput+DWView.m in Sources */, + C9D2C6C82A320AA000D15901 /* UIView+DWRecursiveSubview.m in Sources */, + C9D2C6C92A320AA000D15901 /* AddressStatus.swift in Sources */, + C9D2C6CA2A320AA000D15901 /* ExtendedPublicKeysModel.swift in Sources */, + C9D2C6CB2A320AA000D15901 /* OrderPreviewViewController.swift in Sources */, + C9D2C6CC2A320AA000D15901 /* ActivePaymentMethodView.swift in Sources */, + C9D2C6CD2A320AA000D15901 /* BasicInfoController.swift in Sources */, + C9D2C6CE2A320AA000D15901 /* DWPreviewSeedPhraseModel.m in Sources */, + C9D2C6CF2A320AA000D15901 /* DWModalPresentationAnimation.m in Sources */, + C9D2C6D02A320AA000D15901 /* UINavigationController+CrowdNode.swift in Sources */, + C9D2C6D12A320AA000D15901 /* DWReceiveModel.m in Sources */, + C9D2C6D22A320AA000D15901 /* TransactionWrapper.swift in Sources */, + C9D2C6D32A320AA000D15901 /* Transactions.swift in Sources */, + C9D2C6D42A320AA000D15901 /* DWRecoverTextView.m in Sources */, + C9D2C6D52A320AA000D15901 /* TxUserInfo.swift in Sources */, + C9D2C6D62A320AA000D15901 /* ExtendedPublicKeysViewController.swift in Sources */, + C9D2C6D72A320AA000D15901 /* DWContactsFetchedDataSource.m in Sources */, + C9D2C6D82A320AA000D15901 /* AtmListViewController.swift in Sources */, + C9D2C6D92A320AA000D15901 /* TxDetailViewController.swift in Sources */, + C9D2C6DA2A320AA000D15901 /* NSAttributedString+Builder.swift in Sources */, + C9D2C6DB2A320AA000D15901 /* DWPreviewSeedPhraseContentView.m in Sources */, + C9D2C6DC2A320AA000D15901 /* DWPhoneWCSessionManager.m in Sources */, + C9D2C6DD2A320AA000D15901 /* NumberKeyboard.swift in Sources */, + C9D2C6DE2A320AA000D15901 /* DWSeedPhraseTitledView.m in Sources */, + C9D2C6DF2A320AA000D15901 /* UICollectionView+DWDPItemDequeue.m in Sources */, + C9D2C6E02A320AA000D15901 /* CrowdNodeTopUpTx.swift in Sources */, + C9D2C6E12A320AA000D15901 /* BackupInfoItemView.swift in Sources */, + C9D2C6E22A320AA000D15901 /* DWContactsSearchInfoHeaderView.m in Sources */, + C9D2C6E32A320AA000D15901 /* AccountCreatingController.swift in Sources */, + C9D2C6E42A320AA000D15901 /* UIView+DWReuseHelper.m in Sources */, + C9D2C6E52A320AA000D15901 /* DWExtendedContainerViewController.m in Sources */, + C9D2C6E62A320AA000D15901 /* CrowdNodeEndpoint.swift in Sources */, + C9D2C6E72A320AA000D15901 /* ServiceEntryPointModel.swift in Sources */, + C9D2C6E82A320AA000D15901 /* ExploreDash.swift in Sources */, + C9D2C6E92A320AA000D15901 /* DWToolsMenuViewController.m in Sources */, + C9D2C6EA2A320AA000D15901 /* DWUsernamePendingViewController.m in Sources */, + C9D2C6EB2A320AA000D15901 /* UpholdAmountModel.swift in Sources */, + C9D2C6EC2A320AA000D15901 /* DWModalInteractiveTransition.m in Sources */, + C9D2C6ED2A320AA000D15901 /* DWRequestsViewController.m in Sources */, + C9D2C6EE2A320AA000D15901 /* DWUpholdMainViewController.m in Sources */, + C9D2C6EF2A320AA000D15901 /* DWDPUserObject.m in Sources */, + C9D2C6F02A320AA000D15901 /* UIImage+Utils.m in Sources */, + C9D2C6F12A320AA000D15901 /* CNCreateAccountCell.swift in Sources */, + C9D2C6F22A320AA000D15901 /* DWSetupViewController.m in Sources */, + C9D2C6F32A320AA000D15901 /* ExploreDatabaseConnection.swift in Sources */, + C9D2C6F42A320AA000D15901 /* WithdrawalLimitsController.swift in Sources */, + C9D2C6F52A320AA000D15901 /* CoinbaseTransactionResponse.swift in Sources */, + C9D2C6F62A320AA000D15901 /* DWIncomingFetchedDataSource.m in Sources */, + C9D2C6F72A320AA000D15901 /* TxDetailModel.swift in Sources */, + C9D2C6F82A320AA000D15901 /* NewAccountViewController.swift in Sources */, + C9D2C6F92A320AA000D15901 /* PointOfUseListModel.swift in Sources */, + C9D2C6FA2A320AA000D15901 /* NSPredicate+DWFullTextSearch.m in Sources */, + C9D2C6FB2A320AA000D15901 /* TxReclassifyTransactionsWhereToChangeViewController.swift in Sources */, + C9D2C6FC2A320AA000D15901 /* DWLocalCurrencyViewController.m in Sources */, + C9D2C6FD2A320AA000D15901 /* BadgeView.swift in Sources */, + C9D2C6FE2A320AA000D15901 /* HomeHeaderView.swift in Sources */, + C9D2C6FF2A320AA000D15901 /* ProgressView.swift in Sources */, + C9D2C7002A320AA000D15901 /* DWAmountInputValidator.m in Sources */, + C9D2C7012A320AA000D15901 /* ExplorePointOfUseListViewController.swift in Sources */, + C9D2C7022A320AA000D15901 /* CrowdNodeWebViewController.swift in Sources */, + C9D2C7032A320AA000D15901 /* UIViewController+DWDisplayError.m in Sources */, + C9D2C7042A320AA000D15901 /* BRAppleWatchData.m in Sources */, + C9D2C7052A320AA000D15901 /* DWRequestAmountContentView.m in Sources */, + C9D2C7062A320AA000D15901 /* DWOnboardingModel.m in Sources */, + C9D2C7072A320AA000D15901 /* AmountInputTypeSwitcher.swift in Sources */, + C9D2C7082A320AA000D15901 /* DWPlaceholderFormCellModel.m in Sources */, + C9D2C7092A320AA000D15901 /* DWOverlapControl.m in Sources */, + C9D2C70A2A320AA000D15901 /* CoinsToAddressTxFilter.swift in Sources */, + C9D2C95E2A386D7E00D15901 /* DWBasePressableControl.m in Sources */, + C9D2C70B2A320AA000D15901 /* DWDemoAppRootViewController.m in Sources */, + C9D2C70C2A320AA000D15901 /* DWUpholdLogoutTutorialViewController.m in Sources */, + C9D2C70D2A320AA000D15901 /* CBAccount.swift in Sources */, + C9D2C70E2A320AA000D15901 /* Types.swift in Sources */, + C9D2C70F2A320AA000D15901 /* DWConfirmPaymentViewController.m in Sources */, + C9D2C7102A320AA000D15901 /* DWDPGenericItemView.m in Sources */, + C9D2C7112A320AA000D15901 /* Coinbase.swift in Sources */, + C9D2C7122A320AA000D15901 /* SyncModel.swift in Sources */, + C9D2C7132A320AA000D15901 /* ReceiveContentView.swift in Sources */, + C9D2C7142A320AA000D15901 /* AmountView.swift in Sources */, + C9D2C7152A320AA000D15901 /* DWBasePayViewController.m in Sources */, + C9D2C7162A320AA000D15901 /* CrowdNodeTransferModel.swift in Sources */, + C9D2C7172A320AA000D15901 /* DWSetPinViewController.m in Sources */, + C9D2C7182A320AA000D15901 /* ConfirmOrderController.swift in Sources */, + C9D2C7192A320AA000D15901 /* DWUsernameHeaderView.m in Sources */, + C9D2C71A2A320AA000D15901 /* DWDashPayContactsActions.m in Sources */, + C9D2C71B2A320AA000D15901 /* DWResetWalletInfoViewController.m in Sources */, + C9D2C71C2A320AA000D15901 /* DWLockScreenModel.m in Sources */, + C9D2C71D2A320AA000D15901 /* DWVerifySeedPhraseContentView.m in Sources */, + C9D2C71E2A320AA000D15901 /* Coinbase+Constants.swift in Sources */, + C9D2C71F2A320AA000D15901 /* PointOfUseListSearchCell.swift in Sources */, + C9D2C7202A320AA000D15901 /* ServiceDataProvider.swift in Sources */, + C9D2C7212A320AA000D15901 /* PaymentButton.swift in Sources */, + C9D2C7222A320AA000D15901 /* DWPreviewSeedPhraseViewController.m in Sources */, + C9D2C7232A320AA000D15901 /* DashPayProfileView.swift in Sources */, + C9D2C7242A320AA000D15901 /* WKWebView+CrowdNode.swift in Sources */, + C9D2C7252A320AA000D15901 /* SyncingActivityMonitor.swift in Sources */, + C9D2C7262A320AA000D15901 /* EmptyView.swift in Sources */, + C9D2C7272A320AA000D15901 /* DWConfirmSendPaymentViewController.m in Sources */, + C9D2C7282A320AA000D15901 /* DWQRScanModel.m in Sources */, + C9D2C7292A320AA000D15901 /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.m in Sources */, + C9D2C72A2A320AA000D15901 /* SyncView.swift in Sources */, + C9D2C72B2A320AA000D15901 /* SuccessfulOperationStatusViewController.swift in Sources */, + C9D2C72C2A320AA000D15901 /* CustodialSwapsModel.swift in Sources */, + C9D2C72D2A320AA000D15901 /* DWLocalCurrencyTableViewCell.m in Sources */, + C9D2C72E2A320AA000D15901 /* DWRecoverModel.m in Sources */, + C9D2C72F2A320AA000D15901 /* DWCenteredTableView.m in Sources */, + C9D2C7302A320AA000D15901 /* DWOnboardingViewController.m in Sources */, + C9D2C7312A320AA000D15901 /* DWUsernameValidationView.m in Sources */, + C9D2C7322A320AA000D15901 /* CrowdNode+UserDefaults.swift in Sources */, + C9D2C7332A320AA000D15901 /* DWDPAcceptedRequestNotificationObject.m in Sources */, + C9D2C7342A320AA000D15901 /* DWPinView.m in Sources */, + C9D2C7352A320AA000D15901 /* DWUsernameValidationRule.m in Sources */, + C9D2C7362A320AA000D15901 /* DWBaseReceiveModel.m in Sources */, + C9D2C7372A320AA000D15901 /* DWSegmentSlider.m in Sources */, + C9D2C7382A320AA000D15901 /* CoinbasePlaceBuyOrderRequest.swift in Sources */, + C9D2C7392A320AA000D15901 /* CoinbaseAmount.swift in Sources */, + C9D2C73A2A320AA000D15901 /* DWDPRegistrationErrorTableViewCell.m in Sources */, + C9D2C73B2A320AA000D15901 /* SendAmountModel.swift in Sources */, + C9D2C73C2A320AA000D15901 /* DWDecimalInputValidator.m in Sources */, + C9D2C73D2A320AA000D15901 /* PaymentMethods.swift in Sources */, + C9D2C73E2A320AA000D15901 /* DWTitleDetailCellModel.m in Sources */, + C9D2C73F2A320AA000D15901 /* BuyDashModel.swift in Sources */, + C9D2C7402A320AA000D15901 /* MerchantListViewController.swift in Sources */, + C9D2C7412A320AA000D15901 /* DWUpholdCardObject.m in Sources */, + C9D2C7422A320AA000D15901 /* DWIntrinsicCollectionView.m in Sources */, + C9D2C7432A320AA000D15901 /* DWRootModelStub.m in Sources */, + C9D2C7442A320AA000D15901 /* DWDPIncomingRequestObject.m in Sources */, + C9D2C7452A320AA000D15901 /* UIDevice+DashWallet.m in Sources */, + C9D2C7462A320AA000D15901 /* CBAccountManager.swift in Sources */, + C9D2C7472A320AA000D15901 /* FailedOperationStatusViewController.swift in Sources */, + C9D2C7482A320AA000D15901 /* UIPanGestureRecognizer+DWProjected.m in Sources */, + C9D2C7492A320AA000D15901 /* DWCurrencyObject.m in Sources */, + C9D2C74A2A320AA000D15901 /* ShortcutsView.swift in Sources */, + C9D2C74B2A320AA000D15901 /* DWFormSectionModel.m in Sources */, + C9D2C74C2A320AA000D15901 /* DWActionButton.m in Sources */, + C9D2C74D2A320AA000D15901 /* CoinbasePlaceBuyOrderResponse.swift in Sources */, + C9D2C74E2A320AA000D15901 /* ExploreMapView.swift in Sources */, + C9D2C74F2A320AA000D15901 /* DWSeedPhraseRow.m in Sources */, + C9D2C7502A320AA000D15901 /* ServiceOverviewViewController.swift in Sources */, + C9D2C7512A320AA000D15901 /* AccountListModel.swift in Sources */, + C9D2C7522A320AA000D15901 /* DWDPSearchItemsFactory.m in Sources */, + C9D2C7532A320AA000D15901 /* DWRecoverWalletCommand.m in Sources */, + C9D2C7542A320AA000D15901 /* DWSwitcherFormTableViewCell.m in Sources */, + C9D2C7552A320AA000D15901 /* DWPaymentInput.m in Sources */, + C9D2C7562A320AA000D15901 /* DWBaseContactsViewController.m in Sources */, + C9D2C7572A320AA000D15901 /* OnlineAccountDetailsController.swift in Sources */, + C9D2C7582A320AA000D15901 /* UIApplication+DashWallet.swift in Sources */, + C9D2C7592A320AA000D15901 /* OnlineAccountInfoController.swift in Sources */, + C9D2C75A2A320AA000D15901 /* PaymentController.swift in Sources */, + C9D2C75B2A320AA000D15901 /* CALayer+MBAnimationPersistence.m in Sources */, + C9D2C75C2A320AA000D15901 /* UIView+DWAnimations.m in Sources */, + C9D2C75D2A320AA000D15901 /* VerifiedSuccessfullyViewController.swift in Sources */, + C9D2C75E2A320AA000D15901 /* KeyboardHeader.swift in Sources */, + C9D2C75F2A320AA000D15901 /* DWUpholdOTPViewController.m in Sources */, + C9D2C7602A320AA000D15901 /* DWAppGroupOptions.m in Sources */, + C9D2C7612A320AA000D15901 /* DWTransactionListDataItemObject.m in Sources */, + C9D2C7622A320AA000D15901 /* DWUpholdAPIProvider.m in Sources */, + C9D2C7632A320AA000D15901 /* DWConfirmUsernameContentView.m in Sources */, + C9D2C7642A320AA000D15901 /* BasePageSheetViewController.swift in Sources */, + C9D2C7652A320AA000D15901 /* DWListCollectionLayout.m in Sources */, + C9D2C7662A320AA000D15901 /* CrowdNodeRequest.swift in Sources */, + C9D2C7672A320AA000D15901 /* CoinbaseExchangeRateResponse.swift in Sources */, + C9D2C7682A320AA000D15901 /* DWDPTxItemView.m in Sources */, + C9D2C7692A320AA000D15901 /* DWConfirmPaymentContentView.m in Sources */, + C9D2C76A2A320AA000D15901 /* CrowdNodePortalItem.swift in Sources */, + C9D2C76B2A320AA000D15901 /* BaseViewController.swift in Sources */, + C9D2C76C2A320AA000D15901 /* AccountListController.swift in Sources */, + C9D2C76D2A320AA000D15901 /* DWUpholdTransactionObject.m in Sources */, + C9D2C76E2A320AA000D15901 /* DWBaseSeedViewController.m in Sources */, + C9D2C76F2A320AA000D15901 /* DWProgressAnimator.mm in Sources */, + C9D2C7702A320AA000D15901 /* CrowdNodeModel.swift in Sources */, + C9D2C7712A320AA000D15901 /* UIDevice+Compatibility.swift in Sources */, + C9D2C7722A320AA000D15901 /* DWSeedWordView.m in Sources */, + C9D2C7732A320AA000D15901 /* DWSecurityStatusView.m in Sources */, + C9D2C7742A320AA000D15901 /* DWRequestsContentViewController.m in Sources */, + C9D2C7752A320AA000D15901 /* DWAnimatedShapeLayer.m in Sources */, + C9D2C7762A320AA000D15901 /* DWUserProfileNavigationTitleView.m in Sources */, + C9D2C7772A320AA000D15901 /* DWTextField.m in Sources */, + C9D2C7782A320AA000D15901 /* DWScreenshotWarningViewController.m in Sources */, + C9D2C7792A320AA000D15901 /* ConvertCryptoOrderPreviewController.swift in Sources */, + C9D2C77A2A320AA000D15901 /* TransactionDataItem.swift in Sources */, + C9D2C77B2A320AA000D15901 /* DWSetPinModel.m in Sources */, + C9D2C77C2A320AA000D15901 /* DWVerifySeedPhraseModel.m in Sources */, + C9D2C77D2A320AA000D15901 /* ReceiveViewController.swift in Sources */, + C9D2C77E2A320AA000D15901 /* ConfirmationTransactionQRController.swift in Sources */, + C9D2C77F2A320AA000D15901 /* DWDashPayAnimationView.m in Sources */, + C9D2C7802A320AA000D15901 /* DWDPOutgoingRequestNotificationObject.m in Sources */, + C9D2C7812A320AA000D15901 /* Numbers+Dash.swift in Sources */, + C9D2C7822A320AA000D15901 /* PaymentMethodsController.swift in Sources */, + C9D2C7832A320AA000D15901 /* PointOfUseDetailsViewController.swift in Sources */, + C9D2C7842A320AA000D15901 /* PointOfUseLocationServicePopup.swift in Sources */, + C9D2C7852A320AA000D15901 /* BalanceModel.swift in Sources */, + C9D2C7862A320AA000D15901 /* TxReclassifyTransactionsInfoViewController.swift in Sources */, + C9D2C7872A320AA000D15901 /* DWTransactionListDataProvider.m in Sources */, + C9D2C7882A320AA000D15901 /* DWModalPopupTransition.m in Sources */, + C9D2C7892A320AA000D15901 /* DWAdvancedSecurityViewController.m in Sources */, + C9D2C78A2A320AA000D15901 /* DWInputUsernameViewController.m in Sources */, + C9D2C78B2A320AA000D15901 /* OrderPreviewModel.swift in Sources */, + C9D2C78C2A320AA000D15901 /* DWDPGenericImageItemView.m in Sources */, + C9D2C78D2A320AA000D15901 /* UIView+DWFindConstraints.m in Sources */, + C9D2C78E2A320AA000D15901 /* DWModalPopupPresentationController.m in Sources */, + C9D2C78F2A320AA000D15901 /* CrowdNodeBalance.swift in Sources */, + C9D2C7902A320AA000D15901 /* String+DashWallet.swift in Sources */, + C9D2C7912A320AA000D15901 /* DWBaseContactsContentViewController.m in Sources */, + C9D2C7922A320AA000D15901 /* DWUserProfileViewController.m in Sources */, + C9D2C7932A320AA000D15901 /* DWBaseModalViewController.m in Sources */, + C9D2C7942A320AA000D15901 /* DWSecurityMenuModel.m in Sources */, + C9D2C7952A320AA000D15901 /* KeysOverviewCell.swift in Sources */, + C9D2C7962A320AA000D15901 /* UIViewController+DWShareReceiveInfo.m in Sources */, + C9D2C7972A320AA000D15901 /* OnlineAccountConfirmationController.swift in Sources */, + C9D2C7982A320AA000D15901 /* BaseResponse.swift in Sources */, + C9D2C7992A320AA000D15901 /* DWDemoAdvancedSecurityViewController.m in Sources */, + C9D2C79A2A320AA000D15901 /* TxListTableViewCell.swift in Sources */, + C9D2C79B2A320AA000D15901 /* DWStretchyHeaderListCollectionLayout.m in Sources */, + C9D2C79C2A320AA000D15901 /* TxWithinTimePeriod.swift in Sources */, + C9D2C79D2A320AA000D15901 /* CoinbaseSwapeTradeRequest.swift in Sources */, + C9D2C79E2A320AA000D15901 /* UIFont+DWFont.m in Sources */, + C9D2C79F2A320AA000D15901 /* CustodialSwapsViewController.swift in Sources */, + C9D2C7A02A320AA000D15901 /* NSAttributedString+DWHighlightText.m in Sources */, + C9D2C7A12A320AA000D15901 /* CoinbaseInfoViewController.swift in Sources */, + C9D2C7A22A320AA000D15901 /* DWSelectorFormCellModel.m in Sources */, + C9D2C7A32A320AA000D15901 /* DWVersionManager.m in Sources */, + C9D2C7A42A320AA000D15901 /* DWDPEstablishedContactNotificationObject.m in Sources */, + C9D2C7A52A320AA000D15901 /* DWModalPresentationController.m in Sources */, + C9D2C7A62A320AA000D15901 /* PointOfUseInfoViewController.swift in Sources */, + C9D2C7A72A320AA000D15901 /* ActionButtonViewController.swift in Sources */, + C9D2C7A82A320AA000D15901 /* PortalServiceItemCell.swift in Sources */, + C9D2C7A92A320AA000D15901 /* DWDPImageStatusCell.m in Sources */, + C9D2C7AA2A320AA000D15901 /* WithdrawalConfirmationController.swift in Sources */, + C9D2C7AB2A320AA000D15901 /* DWBaseFormTableViewCell.m in Sources */, + C9D2C7AC2A320AA000D15901 /* AccountCell.swift in Sources */, + C9D2C7AD2A320AA000D15901 /* DWTransactionListDataProviderStub.m in Sources */, + C9D2C7AE2A320AA000D15901 /* DWUserSearchModel.m in Sources */, + C9D2C7AF2A320AA000D15901 /* DWDPBasicCell.m in Sources */, + C9D2C7B02A320AA000D15901 /* DWExploreHeaderView.m in Sources */, + C9D2C7B12A320AA000D15901 /* CoinbaseBaseIDForCurrencyResponse.swift in Sources */, + C9D2C7B22A320AA000D15901 /* DWBaseActionButton.m in Sources */, + C9D2C7B32A320AA000D15901 /* SpecifyAmountViewController.swift in Sources */, + C9D2C7B42A320AA000D15901 /* DWUpholdTransactionObject+DWView.m in Sources */, + C9D2C7B52A320AA000D15901 /* DWBackupSeedPhraseViewController.m in Sources */, + C9D2C7B62A320AA000D15901 /* CrowdNodeDepositTx.swift in Sources */, + C9D2C7B72A320AA000D15901 /* DWUpholdViewController.m in Sources */, + C9D2C7B82A320AA000D15901 /* DWNotificationsModel.m in Sources */, + C9D2C7B92A320AA000D15901 /* DWIntrinsicTableView.m in Sources */, + C9D2C7BA2A320AA000D15901 /* DWURLActions.m in Sources */, + C9D2C7BB2A320AA000D15901 /* KeysOverviewViewController.swift in Sources */, + C9D2C7BC2A320AA000D15901 /* AccountRepository.swift in Sources */, + C9D2C7BD2A320AA000D15901 /* DWSharedUIConstants.m in Sources */, + C9D2C7BE2A320AA000D15901 /* BRAppleWatchTransactionData.m in Sources */, + C9D2C7BF2A320AA000D15901 /* TerritoriesListViewController.swift in Sources */, + C9D2C7C02A320AA000D15901 /* HomeHeaderModel.swift in Sources */, + C9D2C7C12A320AA000D15901 /* TerritoryListModel.swift in Sources */, + C9D2C7C22A320AA000D15901 /* DWUpholdConfirmTransferModel.m in Sources */, + C9D2C7C32A320AA000D15901 /* DWDPRegistrationStatus.m in Sources */, + C9D2C7C42A320AA000D15901 /* CurrencyExchanger_Objc.m in Sources */, + C9D2C7C52A320AA000D15901 /* GiftCardInfoViewController.swift in Sources */, + C9D2C7C62A320AA000D15901 /* Tools.swift in Sources */, + C9D2C7C72A320AA000D15901 /* PortalModel.swift in Sources */, + C9D2C7C82A320AA000D15901 /* DWDateFormatter.m in Sources */, + C9D2C7C92A320AA000D15901 /* BuyDashViewController.swift in Sources */, + C9D2C7CA2A320AA000D15901 /* DWModalDismissalAnimation.m in Sources */, + C9D2C7CB2A320AA000D15901 /* DWPressableButton.m in Sources */, + C9D2C7CC2A320AA000D15901 /* DWUpholdAuthURLNotification.m in Sources */, + C9D2C7CD2A320AA000D15901 /* UIViewController+DashWallet.swift in Sources */, + C9D2C7CE2A320AA000D15901 /* CrowdNodeCell.swift in Sources */, + C9D2C7CF2A320AA000D15901 /* MerchantListLocationOffCell.swift in Sources */, + C9D2C7D02A320AA000D15901 /* DWExploreTestnetContentsView.m in Sources */, + C9D2C7D12A320AA000D15901 /* DWMainMenuContentView.m in Sources */, + C9D2C7D22A320AA000D15901 /* DWDPIncomingRequestCell.m in Sources */, + C9D2C7D32A320AA000D15901 /* CrowdNodeAPIConfirmationTx.swift in Sources */, + C9D2C7D42A320AA000D15901 /* UIViewController+DWEmbedding.m in Sources */, + C9D2C7D52A320AA000D15901 /* DWShadowView.m in Sources */, + C9D2C7D62A320AA000D15901 /* CALayer+DWShadow.m in Sources */, + C9D2C7D72A320AA000D15901 /* MerchantDAO.swift in Sources */, + C9D2C7D82A320AA000D15901 /* ExploreDatabaseSyncManager.swift in Sources */, + C9D2C7D92A320AA000D15901 /* DWOnboardingCollectionViewCell.m in Sources */, + C9D2C7DA2A320AA000D15901 /* CrowdNodeErrorResponse.swift in Sources */, + C9D2C7DB2A320AA000D15901 /* DWHomeViewController+DWShortcuts.m in Sources */, + C9D2C7DC2A320AA000D15901 /* DWHomeModelStub.m in Sources */, + C9D2C7DD2A320AA000D15901 /* UISearchBar+DWAdditions.m in Sources */, + C9D2C7DE2A320AA000D15901 /* UIViewController+DWTxFilter.m in Sources */, + C9D2C7DF2A320AA000D15901 /* DWProfileTxsFetchedDataSource.m in Sources */, + C9D2C7E02A320AA000D15901 /* TransactionFilter.swift in Sources */, + C9D2C7E12A320AA000D15901 /* DWSearchViewController.m in Sources */, + C9D2C7E22A320AA000D15901 /* CrowdNodeWithdrawalReceivedTx.swift in Sources */, + C9D2C7E32A320AA000D15901 /* PointOfUseListSegmentedCell.swift in Sources */, + C9D2C7E42A320AA000D15901 /* DWURLRequestHandler.m in Sources */, + C9D2C7E52A320AA000D15901 /* CoinbaseAPIEndpoint.swift in Sources */, + C9D2C7E62A320AA000D15901 /* ConfirmOrderCells.swift in Sources */, + C9D2C7E72A320AA000D15901 /* CoinbaseUserAccountData.swift in Sources */, + C9D2C7E82A320AA000D15901 /* DWDPRegistrationDoneTableViewCell.m in Sources */, + C9D2C7E92A320AA000D15901 /* AtmDAO.swift in Sources */, + C9D2C7EA2A320AA000D15901 /* App.swift in Sources */, + C9D2C7EB2A320AA000D15901 /* DWPaymentProcessor.m in Sources */, + C9D2C7EC2A320AA000D15901 /* DWNotificationsData.m in Sources */, + C9D2C7ED2A320AA000D15901 /* SyncingHeaderView.swift in Sources */, + C9D2C7EE2A320AA000D15901 /* MinimumDepositBanner.swift in Sources */, + C9D2C7EF2A320AA000D15901 /* BalanceView.swift in Sources */, + C9D2C7F02A320AA000D15901 /* DWQRScanStatusView.m in Sources */, + C9D2C7F12A320AA000D15901 /* DWURLParser.m in Sources */, + C9D2C7F22A320AA000D15901 /* DWDPContactObject.m in Sources */, + C9D2C7F32A320AA000D15901 /* DWPaymentInputBuilder.m in Sources */, + C9D2C7F42A320AA000D15901 /* DWQRScanViewController.m in Sources */, + C9D2C7F52A320AA000D15901 /* DWSelectorFormTableViewCell.m in Sources */, + C9D2C7F62A320AA000D15901 /* DWRegistrationCompletedViewController.m in Sources */, + C9D2C7F72A320AA000D15901 /* DWUserProfileHeaderView.m in Sources */, + C9D2C7F82A320AA000D15901 /* UIViewController+AlertPresenting.swift in Sources */, + C9D2C7F92A320AA000D15901 /* DSWatchTransactionDataObject.m in Sources */, + C9D2C7FA2A320AA000D15901 /* HTTPClient.swift in Sources */, + C9D2C7FB2A320AA000D15901 /* DWPasteboardAddressExtractor.m in Sources */, + C9D2C7FC2A320AA000D15901 /* ViewModel+Coinbase.swift in Sources */, + C9D2C7FD2A320AA000D15901 /* CNCreateAccountTxDetailsModel.swift in Sources */, + C9D2C7FE2A320AA000D15901 /* CBUser.swift in Sources */, + C9D2C7FF2A320AA000D15901 /* CBSecureTokenService.swift in Sources */, + C9D2C8002A320AA000D15901 /* PayTableViewCell.swift in Sources */, + C9D2C8012A320AA000D15901 /* DWTitleActionHeaderView.m in Sources */, + C9D2C8022A320AA000D15901 /* DWDPTxObject.m in Sources */, + C9D2C8032A320AA000D15901 /* ServiceOverviewTableCell.swift in Sources */, + C9D2C8042A320AA000D15901 /* DWContactsModel.m in Sources */, + C9D2C8052A320AA000D15901 /* CoinbaseEntryPointViewController.swift in Sources */, + C9D2C8062A320AA000D15901 /* NSString+DWTextSize.m in Sources */, + C9D2C8072A320AA000D15901 /* PointOfUseListFiltersCell.swift in Sources */, + C9D2C8082A320AA000D15901 /* TransactionListDataSource.swift in Sources */, + C9D2C8092A320AA000D15901 /* DWAllowedCharactersUsernameValidationRule.m in Sources */, + C9D2C80A2A320AA000D15901 /* DWMainMenuViewController.m in Sources */, + C9D2C80B2A320AA000D15901 /* PointOfUseListFiltersViewController.swift in Sources */, + C9D2C80C2A320AA000D15901 /* AllMerchantLocationsDataProvider.swift in Sources */, + C9D2C80D2A320AA000D15901 /* MainTabbarController.swift in Sources */, + C9D2C80E2A320AA000D15901 /* NumberFormatter+DashWallet.swift in Sources */, + C9D2C80F2A320AA000D15901 /* DWNotificationsFetchedDataSource.m in Sources */, + C9D2C8102A320AA000D15901 /* BaseAmountModel.swift in Sources */, + C9D2C8112A320AA000D15901 /* DWDPNewIncomingRequestNotificationObject.m in Sources */, + C9D2C8122A320AA000D15901 /* SeedDB.swift in Sources */, + C9D2C8132A320AA000D15901 /* DWPinField.m in Sources */, + C9D2C8142A320AA000D15901 /* DWCenteredScrollView.m in Sources */, + C9D2C8152A320AA000D15901 /* DWDPEstablishedContactObject.m in Sources */, + C9D2C8162A320AA000D15901 /* DWPhraseRepairViewController.m in Sources */, + C9D2C8172A320AA000D15901 /* DWBiometricAuthModel.m in Sources */, + C9D2C8182A320AA000D15901 /* DWNumberKeyboardInputViewAudioFeedback.m in Sources */, + C9D2C8192A320AA000D15901 /* CrowdNode+Constants.swift in Sources */, + C9D2C81A2A320AA000D15901 /* CurrencyExchanger.swift in Sources */, + C9D2C9622A386DA200D15901 /* DWDPWelcomeView.m in Sources */, + C9D2C81B2A320AA000D15901 /* DWMinLengthUsernameValidationRule.m in Sources */, + C9D2C81C2A320AA000D15901 /* DemoMainTabbarViewController.swift in Sources */, + C9D2C81D2A320AA000D15901 /* MessageStatus.swift in Sources */, + C9D2C81E2A320AA000D15901 /* DWGlobalOptions.m in Sources */, + C9D2C81F2A320AA000D15901 /* AppDelegate.m in Sources */, + C9D2C8202A320AA000D15901 /* SendReceivePageController.swift in Sources */, + C9D2C8212A320AA000D15901 /* DWSettingsMenuModel.m in Sources */, + C9D2C8222A320AA000D15901 /* DWUserProfileSendRequestCell.m in Sources */, + C9D2C8232A320AA000D15901 /* BaseNavigationController.swift in Sources */, + C9D2C8242A320AA000D15901 /* UIViewController+Coinbase.swift in Sources */, + C9D2C8252A320AA000D15901 /* DWCheckExistenceUsernameValidationRule.m in Sources */, + C9D2C8262A320AA000D15901 /* CSVBuilder.swift in Sources */, + C9D2C8272A320AA000D15901 /* DWDPRegistrationStatusTableViewCell.m in Sources */, + C9D2C8282A320AA000D15901 /* CoinbaseUserAuthInformation.swift in Sources */, + C9D2C8292A320AA000D15901 /* DWTransactionStub.m in Sources */, + C9D2C82A2A320AA000D15901 /* OnlineAccountEmailController.swift in Sources */, + C9D2C82B2A320AA000D15901 /* DWBaseActionButtonViewController.m in Sources */, + C9D2C82C2A320AA000D15901 /* CoinbasePaymentMethodsResponse.swift in Sources */, + C9D2C82D2A320AA000D15901 /* UIView+DWEmbedding.m in Sources */, + C9D2C82E2A320AA000D15901 /* DWLockActionButton.m in Sources */, + C9D2C82F2A320AA000D15901 /* PointOfUseDAO.swift in Sources */, + C9D2C8302A320AA000D15901 /* ProvideAmountViewController.swift in Sources */, + C9D2C8312A320AA000D15901 /* DWCheckbox.m in Sources */, + C9D2C8322A320AA000D15901 /* DWRequestAmountViewController.m in Sources */, + C9D2C8332A320AA000D15901 /* DerivationPathKeysModel.swift in Sources */, + C9D2C8342A320AA000D15901 /* DWSeedUIConstants.m in Sources */, + C9D2C8352A320AA000D15901 /* DWWindow.m in Sources */, + C9D2C8362A320AA000D15901 /* BalanceNotifier.swift in Sources */, + C9D2C8372A320AA000D15901 /* DWSeedWordModel.m in Sources */, + C9D2C8382A320AA000D15901 /* DWRootModel.m in Sources */, + C9D2C8392A320AA000D15901 /* BaseAmountViewController.swift in Sources */, + C9D2C83A2A320AA000D15901 /* SendingToView.swift in Sources */, + C9D2C83B2A320AA000D15901 /* TxUserInfoDAO.swift in Sources */, + C9D2C83C2A320AA000D15901 /* DWModalContentView.m in Sources */, + C9D2C83D2A320AA000D15901 /* DWUpholdConstants.m in Sources */, + C9D2C83E2A320AA000D15901 /* DWMaxLengthUsernameValidationRule.m in Sources */, + C9D2C83F2A320AA000D15901 /* CoinbaseSwapeTradeResponse.swift in Sources */, + C9D2C8402A320AA000D15901 /* DWQRScanView.m in Sources */, + C9D2C8412A320AA000D15901 /* ServiceItem.swift in Sources */, + C9D2C8422A320AA000D15901 /* ErrorPresentable.swift in Sources */, + C9D2C8432A320AA000D15901 /* DWPinInputStepView.m in Sources */, + C9D2C8442A320AA000D15901 /* DWDashPaySetupFlowController.m in Sources */, + C9D2C8452A320AA000D15901 /* DWRecoverViewController.m in Sources */, + C9D2C8462A320AA000D15901 /* DWDPAmountContactView.m in Sources */, + C9D2C8472A320AA000D15901 /* DWBaseTransactionListDataProvider.m in Sources */, + C9D2C8482A320AA000D15901 /* CoinbaseTransactionsRequest.swift in Sources */, + C9D2C8492A320AA000D15901 /* DWAppRootViewController.m in Sources */, + C9D2C84A2A320AA000D15901 /* DWUpholdConfirmViewController.m in Sources */, + C9D2C84B2A320AA000D15901 /* UITableView+DashWallet.swift in Sources */, + C9D2C84C2A320AA000D15901 /* TransferAmountViewController.swift in Sources */, + C9D2C84D2A320AA000D15901 /* ShortcutCell.swift in Sources */, + C9D2C84E2A320AA000D15901 /* DWDPGenericContactRequestItemView.m in Sources */, + C9D2C84F2A320AA000D15901 /* UIColor+DWStyle.m in Sources */, + C9D2C8502A320AA000D15901 /* DWEnvironment.m in Sources */, + C9D2C8512A320AA000D15901 /* AtmItemCell.swift in Sources */, + C9D2C8522A320AA000D15901 /* CoinbaseService.swift in Sources */, + C9D2C8532A320AA000D15901 /* DWControllerCollectionView.m in Sources */, + C9D2C8542A320AA000D15901 /* SFSafariViewController+DashWallet.m in Sources */, + C9D2C8552A320AA000D15901 /* DWContactsDataSourceObject.m in Sources */, + C9D2C8562A320AA000D15901 /* TransactionItemView.swift in Sources */, + C9D2C8572A320AA000D15901 /* UIView+Reuse.swift in Sources */, + C9D2C8582A320AA000D15901 /* DWUpholdClient.m in Sources */, + C9D2C8592A320AA000D15901 /* CrowdNodePortalViewController.swift in Sources */, + C9D2C85A2A320AA000D15901 /* ConfirmOrderModel.swift in Sources */, + C9D2C85B2A320AA000D15901 /* PointOfUseListEmptyResultsView.swift in Sources */, + C9D2C85C2A320AA000D15901 /* ShortcutsModel.swift in Sources */, + C9D2C85D2A320AA000D15901 /* DWLockPinInputView.m in Sources */, + C9D2C85E2A320AA000D15901 /* UIView+DWHUD.m in Sources */, + C9D2C85F2A320AA000D15901 /* DWPayOptionModel.m in Sources */, + C9D2C8602A320AA000D15901 /* DWDPRespondedIncomingRequestObject.m in Sources */, + C9D2C8612A320AA000D15901 /* CoinbaseTokenResponse.swift in Sources */, + C9D2C8622A320AA000D15901 /* BackupInfoViewController.swift in Sources */, + C9D2C8632A320AA000D15901 /* DWPlanetarySystemView.m in Sources */, + C9D2C8642A320AA000D15901 /* AppliedFiltersView.swift in Sources */, + C9D2C8652A320AA000D15901 /* DWCaptureSessionManager.m in Sources */, + C9D2C8662A320AA000D15901 /* DWDataMigrationManager.m in Sources */, + C9D2C8672A320AA000D15901 /* DWRequestsModel.m in Sources */, + C9D2C8682A320AA000D15901 /* DWDPTextStatusCell.m in Sources */, + C9D2C8692A320AA000D15901 /* DWContactsViewController.m in Sources */, + C9D2C86A2A320AA000D15901 /* OutlinedTextField.swift in Sources */, + C9D2C86B2A320AA000D15901 /* AmountPreviewView.swift in Sources */, + C9D2C86C2A320AA000D15901 /* ModalNavigationController.swift in Sources */, + C9D2C86D2A320AA000D15901 /* DWDPSmallContactView.m in Sources */, + C9D2C86E2A320AA000D15901 /* TransferAmountModel.swift in Sources */, + C9D2C86F2A320AA000D15901 /* NavigationBarAppearanceCustomizable.swift in Sources */, + C9D2C8702A320AA000D15901 /* AmountObject.swift in Sources */, + C9D2C8712A320AA000D15901 /* DerivationPathKeysViewController.swift in Sources */, + C9D2C8722A320AA000D15901 /* DSTransaction+DashWallet.m in Sources */, + C9D2C8732A320AA000D15901 /* DWHomeViewController+DWJailbreakCheck.m in Sources */, + C9D2C8742A320AA000D15901 /* DWLocalCurrencyModel.m in Sources */, + C9D2C8752A320AA000D15901 /* DWMainMenuModel.m in Sources */, + C9D2C8762A320AA000D15901 /* DWUpholdMainnetConstants.m in Sources */, + C9D2C8772A320AA000D15901 /* DWUserSearchResultViewController.m in Sources */, + C9D2C8782A320AA000D15901 /* DWDPGenericStatusItemView.m in Sources */, + C9D2C8792A320AA000D15901 /* Style.swift in Sources */, + C9D2C87A2A320AA000D15901 /* PayViewController.swift in Sources */, + C9D2C87B2A320AA000D15901 /* DatabaseConnection.swift in Sources */, + C9D2C87C2A320AA000D15901 /* DWSwitcherFormCellModel.m in Sources */, + C9D2C87D2A320AA000D15901 /* DWIntrinsicTextView.m in Sources */, + C9D2C87E2A320AA000D15901 /* DWExploreTestnetViewController.m in Sources */, + C9D2C87F2A320AA000D15901 /* AddressUserInfoDAO.swift in Sources */, + C9D2C8802A320AA000D15901 /* DWDashPayContactsUpdater.m in Sources */, + C9D2C8812A320AA000D15901 /* DWModalTransition.m in Sources */, + C9D2C8822A320AA000D15901 /* DWUpholdMainModel.m in Sources */, + C9D2C8832A320AA000D15901 /* MerchantsDataProvider.swift in Sources */, + C9D2C8842A320AA000D15901 /* FullCrowdNodeSignUpTxSet.swift in Sources */, + C9D2C8852A320AA000D15901 /* DWSeedPhraseModel.m in Sources */, + C9D2C8862A320AA000D15901 /* Constants.swift in Sources */, + C9D2C8872A320AA000D15901 /* DWBaseFormCellModel.m in Sources */, + C9D2C8882A320AA000D15901 /* ExploreMapAnnotationView.swift in Sources */, + C9D2C8892A320AA000D15901 /* DWHomeViewController+DWBackupReminder.m in Sources */, + C9D2C88A2A320AA000D15901 /* DWHomeModel.m in Sources */, + C9D2C88B2A320AA000D15901 /* DWUserProfileModel.m in Sources */, + C9D2C88C2A320AA000D15901 /* DWHomeViewController.m in Sources */, + C9D2C88D2A320AA000D15901 /* IsAddressInUse.swift in Sources */, + C9D2C88E2A320AA000D15901 /* DWFetchedResultsDataSource.m in Sources */, + C9D2C88F2A320AA000D15901 /* DWSegmentedControl.m in Sources */, + C9D2C8902A320AA000D15901 /* MerchantItemCell.swift in Sources */, + C9D2C8912A320AA000D15901 /* PaymentMethodCell.swift in Sources */, + C9D2C8922A320AA000D15901 /* DWContactsContentViewController.m in Sources */, + C9D2C8932A320AA000D15901 /* CBUserManager.swift in Sources */, + C9D2C8942A320AA000D15901 /* DerivationPathKeysHeaderView.swift in Sources */, + C9D2C8952A320AA000D15901 /* ConvertCryptoOrderPreviewModel.swift in Sources */, + C9D2C8962A320AA000D15901 /* DWContactsSearchDataSourceObject.m in Sources */, + C9D2C8972A320AA000D15901 /* RatesProvider.swift in Sources */, + C9D2C8982A320AA000D15901 /* CBAuth.swift in Sources */, + C9D2C8992A320AA000D15901 /* DWButton.m in Sources */, + C9D2C89A2A320AA000D15901 /* CoinbaseCreateAddressesRequest.swift in Sources */, + C9D2C89B2A320AA000D15901 /* DWImportWalletInfoViewController.m in Sources */, + C9D2C89C2A320AA000D15901 /* TxListEmptyTableViewCell.swift in Sources */, + C9D2C89D2A320AA000D15901 /* SyncingAlertContentView.swift in Sources */, + C9D2C89E2A320AA000D15901 /* AddressUserInfo.swift in Sources */, + C9D2C89F2A320AA000D15901 /* MerchantInfoViewController.swift in Sources */, + C9D2C8A02A320AA000D15901 /* SendCoinsService.swift in Sources */, + C9D2C8A12A320AA000D15901 /* ShortcutAction.swift in Sources */, + C9D2C8A22A320AA000D15901 /* DWModalChevronView.m in Sources */, + C9D2C8A32A320AA000D15901 /* DWBaseLegacyViewController.m in Sources */, + C9D2C8A42A320AA000D15901 /* DWNoNotificationsCell.m in Sources */, + C9D2C8A52A320AA000D15901 /* HomeView.swift in Sources */, + C9D2C8A62A320AA000D15901 /* CoinbaseRatesProvider.swift in Sources */, + C9D2C8A72A320AA000D15901 /* TaxReportGenerator.swift in Sources */, + C9D2C8A82A320AA000D15901 /* CrowdNodeError.swift in Sources */, + C9D2C8A92A320AA000D15901 /* WithdrawalLimit.swift in Sources */, + C9D2C8AA2A320AA000D15901 /* DWPayModel.m in Sources */, + C9D2C8AB2A320AA000D15901 /* DWSearchStateViewController.m in Sources */, + C9D2C8AC2A320AA000D15901 /* DWDPTxListCell.m in Sources */, + C9D2C8AD2A320AA000D15901 /* PointOfUseDetailsView.swift in Sources */, + C9D2C8AE2A320AA000D15901 /* DSChain+DashWallet.m in Sources */, + C9D2C8AF2A320AA000D15901 /* FromLabel.swift in Sources */, + C9D2C8B02A320AA000D15901 /* DWPhraseRepairChildViewController.m in Sources */, + C9D2C8B12A320AA000D15901 /* SingleInputAddressSelector.swift in Sources */, + C9D2C8B22A320AA000D15901 /* CoinbaseAccountAddress.swift in Sources */, + C9D2C8B32A320AA000D15901 /* DWMainMenuTableViewCell.m in Sources */, + C9D2C8B42A320AA000D15901 /* CrowdNodeTransferViewController.swift in Sources */, + C9D2C8B52A320AA000D15901 /* DWUserSearchViewController.m in Sources */, + C9D2C8B62A320AA000D15901 /* CBAuthInterop.swift in Sources */, + C9D2C8B72A320AA000D15901 /* UIFont+DWDPItem.m in Sources */, + C9D2C8B82A320AA000D15901 /* CoinbaseAmountViewController.swift in Sources */, + C9D2C8B92A320AA000D15901 /* DWUpholdAuthViewController.m in Sources */, + C9D2C8BA2A320AA000D15901 /* DWSegmentSliderFormCellModel.m in Sources */, + C9D2C8BB2A320AA000D15901 /* DWSeedPhraseView.m in Sources */, + C9D2C8BC2A320AA000D15901 /* DWDPAvatarView.m in Sources */, + C9D2C8BD2A320AA000D15901 /* UIStackView+DashWallet.swift in Sources */, + C9D2C8BE2A320AA000D15901 /* DWUserProfileContactActionsCell.m in Sources */, + C9D2C8BF2A320AA000D15901 /* ExplorePointOfUse.swift in Sources */, + C9D2C8C02A320AA000D15901 /* DWUserProfileDataSourceObject.m in Sources */, + C9D2C8C12A320AA000D15901 /* DWDashPayModel.m in Sources */, + C9D2C8C22A320AA000D15901 /* TransactionObserver.swift in Sources */, + C9D2C8C32A320AA000D15901 /* DWInfoTextCell.m in Sources */, + C9D2C8C42A320AA000D15901 /* ColorizedText.swift in Sources */, + C9D2C8C52A320AA000D15901 /* DWDPPendingRequestObject.m in Sources */, + C9D2C8C62A320AA000D15901 /* DWModalUserProfileViewController.m in Sources */, + C9D2C8C72A320AA000D15901 /* PaymentsViewController.swift in Sources */, + C9D2C8C82A320AA000D15901 /* DWDPContactsItemsFactory.m in Sources */, + C9D2C8C92A320AA000D15901 /* ServiceDataSource.swift in Sources */, + C9D2C8CA2A320AA000D15901 /* ConverterView.swift in Sources */, + C9D2C8CB2A320AA000D15901 /* DWModalBaseAnimation.m in Sources */, + C9D2C8CC2A320AA000D15901 /* SpendableTransaction.swift in Sources */, + C9D2C8CD2A320AA000D15901 /* StakingInfoDialogController.swift in Sources */, + C9D2C8CE2A320AA000D15901 /* DWCreateUsernameViewController.m in Sources */, + C9D2C8CF2A320AA000D15901 /* TwoFactorAuthViewController.swift in Sources */, + C9D2C8D02A320AA000D15901 /* Taxes.swift in Sources */, + C9D2C8D12A320AA000D15901 /* DWSeedWordModel+DWLayoutSupport.m in Sources */, + C9D2C8D22A320AA000D15901 /* CoinbaseAPIClient.swift in Sources */, + C9D2C8D32A320AA000D15901 /* SendAmountViewController.swift in Sources */, + C9D2C8D42A320AA000D15901 /* DWAboutViewController.m in Sources */, + C9D2C8D52A320AA000D15901 /* AccountService.swift in Sources */, + C9D2C8D62A320AA000D15901 /* ListHandlerView.swift in Sources */, + C9D2C8D72A320AA000D15901 /* DWSecurityMenuViewController.m in Sources */, + C9D2C8D82A320AA000D15901 /* CrowdNode.swift in Sources */, + C9D2C8D92A320AA000D15901 /* SQLite+ExloreDash.swift in Sources */, + C9D2C8DA2A320AA000D15901 /* DWFilterHeaderView.m in Sources */, + C9D2C8DB2A320AA000D15901 /* DWBaseContactsModel.m in Sources */, + C9D2C8DC2A320AA000D15901 /* DWAdvancedSecurityModel.m in Sources */, + C9D2C8DD2A320AA000D15901 /* AtmDataProvider.swift in Sources */, + C9D2C8DE2A320AA000D15901 /* GettingStartedViewController.swift in Sources */, + C9D2C8DF2A320AA000D15901 /* NetworkUnavailableView.swift in Sources */, + C9D2C8E02A320AA000D15901 /* DWBorderedActionButton.m in Sources */, + C9D2C8E12A320AA000D15901 /* DWLockScreenViewController.m in Sources */, + C9D2C8E22A320AA000D15901 /* UpholdTransferViewController.swift in Sources */, + C9D2C8E32A320AA000D15901 /* DWVerifySeedPhraseViewController.m in Sources */, + C9D2C8E42A320AA000D15901 /* DerivationPathKeysCell.swift in Sources */, + C9D2C8E52A320AA000D15901 /* Cells.swift in Sources */, + C9D2C8E62A320AA000D15901 /* DWContainerViewController.m in Sources */, + C9D2C8E72A320AA000D15901 /* DashTextAttachment.swift in Sources */, + C9D2C8E82A320AA000D15901 /* HomeBalanceView.swift in Sources */, + C9D2C8E92A320AA000D15901 /* PointOfUseListFiltersModel.swift in Sources */, + C9D2C8EA2A320AA000D15901 /* ConvertCryptoOrderPreviewViews.swift in Sources */, + C9D2C8EB2A320AA000D15901 /* DWAboutModel.m in Sources */, + C9D2C8EC2A320AA000D15901 /* CNCreateAccountTxDetailsViewController.swift in Sources */, + C9D2C8ED2A320AA000D15901 /* UIColor+DWDashPay.m in Sources */, + C9D2C8EE2A320AA000D15901 /* WelcomeToCrowdNodeViewController.swift in Sources */, + C9D2C8EF2A320AA000D15901 /* TerritoriesListCell.swift in Sources */, + C9D2C8F02A320AA000D15901 /* DWProgressView.m in Sources */, + C9D2C8F12A320AA000D15901 /* TxDetailCells.swift in Sources */, + C9D2C8F22A320AA000D15901 /* BaseViewController+NetworkReachability.swift in Sources */, + C9D2C8F32A320AA000D15901 /* main.m in Sources */, + C9D2C8F42A320AA000D15901 /* DWQuickReceiveViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -7155,7 +8775,11 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = DashWalletScreenshotsUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.DashWalletScreenshotsUITests; @@ -7188,7 +8812,11 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = DashWalletScreenshotsUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.DashWalletScreenshotsUITests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7220,7 +8848,11 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = DashWalletScreenshotsUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.DashWalletScreenshotsUITests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7252,7 +8884,11 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = DashWalletScreenshotsUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.DashWalletScreenshotsUITests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7326,7 +8962,7 @@ "OTHER_SWIFT_FLAGS[arch=*]" = "-DDebug"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; ZERO_AR_DATE = 1; }; @@ -7384,8 +9020,9 @@ "-Wno-gnu", ); SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; WATCHOS_DEPLOYMENT_TARGET = 2.0; ZERO_AR_DATE = 1; @@ -7427,7 +9064,10 @@ INFOPLIST_FILE = DashWallet/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Dash; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7485,7 +9125,10 @@ INFOPLIST_FILE = DashWallet/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Dash; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7521,7 +9164,11 @@ ); INFOPLIST_FILE = DashWalletTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "DashWalletTests/DashWalletTests-Bridging-Header.h"; @@ -7552,7 +9199,11 @@ ); INFOPLIST_FILE = DashWalletTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "DashWalletTests/DashWalletTests-Bridging-Header.h"; @@ -7613,7 +9264,11 @@ ENABLE_BITCODE = NO; EXCLUDED_ARCHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; @@ -7636,7 +9291,11 @@ ENABLE_BITCODE = NO; EXCLUDED_ARCHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; @@ -7664,7 +9323,11 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7689,7 +9352,11 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7700,6 +9367,237 @@ }; name = Release; }; + C9D2C94F2A320AA000D15901 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1EBB53DD8E53B4B63E5A885E /* Pods-dashpay.debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLIENT_ID = 0c38beb67db0c68191326be347d7ec0abd7d77adb02a79db1abeba343f16a0f7; + CLIENT_SECRET = cc980185754f905e24250f877792817c03540b3d0e0959721df291c816797e59; + CODE_SIGN_ENTITLEMENTS = dashwallet/dashwallet.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 44RJ69WHFF; + EXCLUDED_ARCHS = ""; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "DashWallet/DashWallet-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "$(inherited)", + "PB_FIELD_32BIT=1", + "PB_NO_PACKED_STRUCTS=1", + "PB_ENABLE_MALLOC=1", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/secp256k1", + "$(SRCROOT)", + ); + INFOPLIST_FILE = "DashPay/dashpay-info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = DashPay; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 7.0.1; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLiteMigrationManager.swift/SQLiteMigrationManager.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/FBLPromises/PromisesObjC.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/SSZipArchive/SSZipArchive.modulemap\" -DDASHPAY"; + PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dashpaytnt; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + SWIFT_OBJC_BRIDGING_HEADER = "DashWallet/dashwallet-Bridging-Header.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "dashwallet-Swift.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = ""; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + C9D2C9502A320AA000D15901 /* Testnet */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C4C041DE4CD73FF445090FA3 /* Pods-dashpay.testnet.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLIENT_ID = 0c38beb67db0c68191326be347d7ec0abd7d77adb02a79db1abeba343f16a0f7; + CLIENT_SECRET = cc980185754f905e24250f877792817c03540b3d0e0959721df291c816797e59; + CODE_SIGN_ENTITLEMENTS = dashwallet/dashwallet.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 44RJ69WHFF; + EXCLUDED_ARCHS = ""; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "DashWallet/DashWallet-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "$(inherited)", + "PB_FIELD_32BIT=1", + "PB_NO_PACKED_STRUCTS=1", + "PB_ENABLE_MALLOC=1", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/secp256k1", + "$(SRCROOT)", + ); + INFOPLIST_FILE = "DashPay/dashpay-info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = DashPay; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 7.0.1; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLiteMigrationManager.swift/SQLiteMigrationManager.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/FBLPromises/PromisesObjC.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/SSZipArchive/SSZipArchive.modulemap\""; + "OTHER_SWIFT_FLAGS[arch=*]" = "-DDebug -DDASH_TESTNET -DDASHPAY"; + PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dashpaytnt; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "DashWallet/dashwallet-Bridging-Header.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "dashwallet-Swift.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = ""; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Testnet; + }; + C9D2C9512A320AA000D15901 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EBB5D208A38257AF3DA73A99 /* Pods-dashpay.release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLIENT_ID = 0c38beb67db0c68191326be347d7ec0abd7d77adb02a79db1abeba343f16a0f7; + CLIENT_SECRET = cc980185754f905e24250f877792817c03540b3d0e0959721df291c816797e59; + CODE_SIGN_ENTITLEMENTS = dashwallet/dashwallet.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 44RJ69WHFF; + EXCLUDED_ARCHS = ""; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "DashWallet/DashWallet-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "$(inherited)", + "PB_FIELD_32BIT=1", + "PB_NO_PACKED_STRUCTS=1", + "PB_ENABLE_MALLOC=1", + ); + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "$(inherited)", + "PB_FIELD_32BIT=1", + "PB_NO_PACKED_STRUCTS=1", + "PB_ENABLE_MALLOC=1", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/secp256k1", + "$(SRCROOT)", + ); + INFOPLIST_FILE = "DashPay/dashpay-info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = DashPay; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 7.0.1; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLiteMigrationManager.swift/SQLiteMigrationManager.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/FBLPromises/PromisesObjC.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/SSZipArchive/SSZipArchive.modulemap\" -DDASHPAY"; + PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dashpaytnt; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "DashWallet/dashwallet-Bridging-Header.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "dashwallet-Swift.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = ""; + VALID_ARCHS = "arm64 arm64e"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + C9D2C9522A320AA000D15901 /* Testflight */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3BF640216ECD5FBFC17B85D3 /* Pods-dashpay.testflight.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLIENT_ID = 0c38beb67db0c68191326be347d7ec0abd7d77adb02a79db1abeba343f16a0f7; + CLIENT_SECRET = cc980185754f905e24250f877792817c03540b3d0e0959721df291c816797e59; + CODE_SIGN_ENTITLEMENTS = dashwallet/dashwallet.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 44RJ69WHFF; + EXCLUDED_ARCHS = ""; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "DashWallet/DashWallet-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "$(inherited)", + "PB_FIELD_32BIT=1", + "PB_NO_PACKED_STRUCTS=1", + "PB_ENABLE_MALLOC=1", + ); + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "$(inherited)", + "PB_FIELD_32BIT=1", + "PB_NO_PACKED_STRUCTS=1", + "PB_ENABLE_MALLOC=1", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/secp256k1", + "$(SRCROOT)", + ); + INFOPLIST_FILE = "DashPay/dashpay-info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = DashPay; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 7.0.1; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLiteMigrationManager.swift/SQLiteMigrationManager.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/FBLPromises/PromisesObjC.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/SSZipArchive/SSZipArchive.modulemap\""; + PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dashpaytnt; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "DashWallet/dashwallet-Bridging-Header.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "dashwallet-Swift.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = ""; + VALID_ARCHS = "arm64 arm64e"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Testflight; + }; CEE253D21E26A9BC00A25B15 /* Testflight */ = { isa = XCBuildConfiguration; buildSettings = { @@ -7754,8 +9652,9 @@ ); OTHER_SWIFT_FLAGS = "-DTestflight"; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; WATCHOS_DEPLOYMENT_TARGET = 2.0; ZERO_AR_DATE = 1; @@ -7806,7 +9705,10 @@ INFOPLIST_FILE = DashWallet/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Dash; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7834,7 +9736,11 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7868,7 +9774,11 @@ ); INFOPLIST_FILE = DashWalletTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "DashWalletTests/DashWalletTests-Bridging-Header.h"; @@ -7907,7 +9817,11 @@ ENABLE_BITCODE = NO; EXCLUDED_ARCHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; @@ -7983,7 +9897,7 @@ "OTHER_SWIFT_FLAGS[arch=*]" = "-DDebug -DDASH_TESTNET"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; ZERO_AR_DATE = 1; }; @@ -8023,7 +9937,10 @@ INFOPLIST_FILE = DashWallet/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Dash; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -8050,7 +9967,11 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -8083,7 +10004,11 @@ ); INFOPLIST_FILE = DashWalletTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.TodayExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "DashWalletTests/DashWalletTests-Bridging-Header.h"; @@ -8122,7 +10047,11 @@ ENABLE_BITCODE = NO; EXCLUDED_ARCHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 7.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dash.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; @@ -8216,6 +10145,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C9D2C94E2A320AA000D15901 /* Build configuration list for PBXNativeTarget "dashpay" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C9D2C94F2A320AA000D15901 /* Debug */, + C9D2C9502A320AA000D15901 /* Testnet */, + C9D2C9512A320AA000D15901 /* Release */, + C9D2C9522A320AA000D15901 /* Testflight */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 75D5F3B6191EC270004AB296 /* Project object */; diff --git a/DashWallet.xcodeproj/xcshareddata/xcschemes/dashpay.xcscheme b/DashWallet.xcodeproj/xcshareddata/xcschemes/dashpay.xcscheme new file mode 100644 index 000000000..e1c9872fb --- /dev/null +++ b/DashWallet.xcodeproj/xcshareddata/xcschemes/dashpay.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Podfile b/Podfile index 861dc9a19..5c3aa501e 100644 --- a/Podfile +++ b/Podfile @@ -20,6 +20,28 @@ target 'dashwallet' do # Debugging purposes # pod 'Reveal-SDK', :configurations => ['Debug'] +end + +target 'dashpay' do + platform :ios, '14.0' + + pod 'DashSync', :path => '../DashSync/' + pod 'SQLite.swift', '~> 0.13.3' + pod 'SQLiteMigrationManager.swift' + pod 'CloudInAppMessaging', '0.1.0' + pod 'FirebaseStorage', '8.15.0' + pod 'Firebase/DynamicLinks' + pod 'SSZipArchive' + pod 'KVO-MVVM', '0.5.6' + pod 'UIViewController-KeyboardAdditions', '1.2.1' + pod 'MBProgressHUD', '1.1.0' + pod 'MMSegmentSlider', :git => 'https://github.com/podkovyrin/MMSegmentSlider', :commit => '2d91366' + pod 'CocoaImageHashing', :git => 'https://github.com/ameingast/cocoaimagehashing.git', :commit => 'ad01eee' + pod 'SDWebImage', '5.13.2' + pod 'Moya', '~> 15.0' + # Debugging purposes + # pod 'Reveal-SDK', :configurations => ['Debug'] + target 'DashWalletTests' do inherit! :search_paths end @@ -30,6 +52,7 @@ target 'dashwallet' do end + target 'TodayExtension' do platform :ios, '13.0' diff --git a/Podfile.lock b/Podfile.lock index 5dd312a11..34a53fe9c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -831,6 +831,6 @@ SPEC CHECKSUMS: TinyCborObjc: 5204540fb90ff0c40fb22d408fa51bab79d78a80 UIViewController-KeyboardAdditions: a691dc7e63a49854d341455a778ee8497dfc4662 -PODFILE CHECKSUM: 070344e2828a74f647aaa0ce756a1fba3489659b +PODFILE CHECKSUM: 923bcf58ceb233fa904192267a41641c010ae7f9 COCOAPODS: 1.12.1 From fd73c309befc2c51844a35149049bdf11be47e83 Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 13 Jun 2023 13:56:32 +0400 Subject: [PATCH 002/123] feat(ui): Show 'Join DashPay' button --- .../Assets/DPAssets.xcassets/Contents.json | 6 + .../pay_user_accessory.imageset/Contents.json | 23 +++ .../pay_user_accessory.png | Bin 0 -> 925 bytes .../pay_user_accessory@2x.png | Bin 0 -> 1663 bytes .../pay_user_accessory@3x.png | Bin 0 -> 2507 bytes .../Presentation/Home/Views/DWDPWelcomeView.h | 26 ++++ .../Presentation/Home/Views/DWDPWelcomeView.m | 133 ++++++++++++++++++ .../Shared/Buttons/DWBasePressableControl.h | 26 ++++ .../Shared/Buttons/DWBasePressableControl.m | 63 +++++++++ .../Home Balance View/HomeBalanceView.xib | 2 +- .../Home Header View/HomeHeaderView.swift | 21 ++- .../Sources/UI/Home/Views/HomeView.swift | 4 + .../UI/Views/BalanceView/BalanceView.swift | 3 + DashWallet/dashwallet-Bridging-Header.h | 1 + 14 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 DashPay/Assets/DPAssets.xcassets/Contents.json create mode 100644 DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/Contents.json create mode 100644 DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/pay_user_accessory.png create mode 100644 DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/pay_user_accessory@2x.png create mode 100644 DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/pay_user_accessory@3x.png create mode 100644 DashPay/Presentation/Home/Views/DWDPWelcomeView.h create mode 100644 DashPay/Presentation/Home/Views/DWDPWelcomeView.m create mode 100644 DashPay/Presentation/Shared/Buttons/DWBasePressableControl.h create mode 100644 DashPay/Presentation/Shared/Buttons/DWBasePressableControl.m diff --git a/DashPay/Assets/DPAssets.xcassets/Contents.json b/DashPay/Assets/DPAssets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/DashPay/Assets/DPAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/Contents.json b/DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/Contents.json new file mode 100644 index 000000000..d42e8bea6 --- /dev/null +++ b/DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "pay_user_accessory.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "pay_user_accessory@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "pay_user_accessory@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/pay_user_accessory.png b/DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/pay_user_accessory.png new file mode 100644 index 0000000000000000000000000000000000000000..fcf4edee93ed213c3678c5c880368cc154f0be2a GIT binary patch literal 925 zcmV;O17iG%P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$?2T4RhR9FecS3PeNK@ferJ_sQR zL0+}^L<#&svKSUHYn_=Mr*EX_za2iecnFD}yt3i>=| zi?2HM?M&eA$I}QK&pHk0@0qG`Y08)@Ew_)~PGDi}BG-1Qz2iXs>);HoAAFwnwUvN` zyX2sXC2|J8crN6xfXg z%{m`?6EGZf<>@!;Pa+}aL830$0mDDZ>@TonS_i&A2?@n;c#()Uvtkf!tG#Fc;==w+ zZbBe5#+`)hq#8y4)X${h@iGp0V1-1;G*o71*6>r0KX-jC$@uWxI8IItxhJC*4S@*SM+!po(Lhex=NI&V+ZXHf3-Efp$>am81M2u0gnaO@4 zhf`Kn7Pf&LPCJoFj^|d$#K&_vn#-ov$MO6R>~|o4L^hD=00000NkvXXu0mjf7Q~R* literal 0 HcmV?d00001 diff --git a/DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/pay_user_accessory@2x.png b/DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/pay_user_accessory@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..781f3d0e8781d27bd0bea47d748ef1dbf8923793 GIT binary patch literal 1663 zcmV-_27vjAP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuB=}AOERCod9TTO@@MHGHDy_=0c z>qZw$%&rGrH908;A*_PnAt(j{Mo7E}-oywp5HZ`}$SYtLj%oV=g9l{O|uh zL;qZToJ{y2g|v(7ckJA)}(^9`NfAf)l;jetWK!$&z`JQgM=?i-Cj&*q^r@wp% zc<0IjZQOXC0X|vlmCjE`6a^7=H*jbvK_>f-NzMQF?4w>8$aFRi;Tt3VSzs$fN zjp{TeAtuLxWK*oMVgY{X1qS&#CojmY7R4JgWIRunxy(eo zLGwF57?g;?2&nwBm#WqG_>DiD5!Qg&%y$$7FHw2NH(3kHMu5Ea)i=3qmNT1oR*jqU z!7?bU(EQwMS*)Rd0^a^(8`b_j$(8P7GtAz;n}KA#j&v}eQ|abI^y=+@_ES`U1XNac zQI$?H_y>jpydsTBxJUsVpz_=Wz18hl>h?f%zjEnGs)pRw7(66v&mI-%@FdcOb%T!3 zwMXgA0#INrJj0-~jpscog+XeD5U zCpLxo_Ru&l;rWVSOO~G`01Vk#UGk~Tas_NnR*)nB^Ld@pZWho<91F5$(U6uTKo&C^ z?&f@Yd~ura^D0dk<557Cu{M76HOT?+V~e*5_7h8g(eWiL3+603X_}n8PnXwX3y?KD zR=`8gA298YY~K>01rc5>L9r=T)*vPT3z(jy%1amrPp#3JpRPqUdLuzV6B&ho z2S#88W)@2TLeUV7<#9S(2zVg2)8TU(erE~54TPe35_q^6>iqQfX`o`PF#(9fgLr5L zH+V_x+bh>2&73&iMlp@h^ny?p>_B><^McYMvGK}DQ~ zQ6>Spi*v_V+Qbv5695trdrKUC>d`wywN6EJXAFWRz2x=;z+4b}OQ!Ita=Plhq_Xb7 zkYf6pyFCGT0)fYfy(P+Z+lngV_{f2htb3Q>>COZ|BLp4?rNc6Va;#x)#bLGwoqGtH z?oNP^z%jRoy&VlpSnoAX7|@b)0i<*i#0}TN)4coZeiZ~kau&imLr4Hd^){s&c8da7D(kk|kK002ov JPDHLkV1lv$=ZOFS literal 0 HcmV?d00001 diff --git a/DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/pay_user_accessory@3x.png b/DashPay/Assets/DPAssets.xcassets/pay_user_accessory.imageset/pay_user_accessory@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f7396494c2a10364d35ff1c49432bb50131542d8 GIT binary patch literal 2507 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 VM5uwY^}PT9002ovPDHLkV1iINiAn$f literal 0 HcmV?d00001 diff --git a/DashPay/Presentation/Home/Views/DWDPWelcomeView.h b/DashPay/Presentation/Home/Views/DWDPWelcomeView.h new file mode 100644 index 000000000..9b57a3681 --- /dev/null +++ b/DashPay/Presentation/Home/Views/DWDPWelcomeView.h @@ -0,0 +1,26 @@ +// +// 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 "DWBasePressableControl.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPWelcomeView : DWBasePressableControl + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Home/Views/DWDPWelcomeView.m b/DashPay/Presentation/Home/Views/DWDPWelcomeView.m new file mode 100644 index 000000000..9278cf59f --- /dev/null +++ b/DashPay/Presentation/Home/Views/DWDPWelcomeView.m @@ -0,0 +1,133 @@ +// +// 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 "DWDPWelcomeView.h" + +#import "DWShadowView.h" +#import "DWUIKit.h" +#import "UIView+DWAnimations.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPWelcomeView () + +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) UILabel *subtitleLabel; +@property (readonly, nonatomic, strong) UIImageView *arrowImageView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPWelcomeView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + + self.layoutMargins = UIEdgeInsetsMake(0, 16, 0, 16); + + DWShadowView *shadowView = [[DWShadowView alloc] initWithFrame:CGRectZero]; + shadowView.translatesAutoresizingMaskIntoConstraints = NO; + shadowView.insetsLayoutMarginsFromSafeArea = YES; + shadowView.userInteractionEnabled = NO; + [self addSubview:shadowView]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = [UIColor dw_backgroundColor]; + contentView.layer.cornerRadius = 8.0; + contentView.layer.masksToBounds = YES; + contentView.userInteractionEnabled = NO; + [shadowView addSubview:contentView]; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.numberOfLines = 0; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + titleLabel.text = NSLocalizedString(@"Join DashPay", nil); + titleLabel.userInteractionEnabled = NO; + [contentView addSubview:titleLabel]; + _titleLabel = titleLabel; + + UILabel *subtitleLabel = [[UILabel alloc] init]; + subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; + subtitleLabel.textColor = [UIColor dw_tertiaryTextColor]; + subtitleLabel.numberOfLines = 0; + subtitleLabel.adjustsFontForContentSizeCategory = YES; + subtitleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; + subtitleLabel.text = NSLocalizedString(@"Create a username, add your friends.", nil); + subtitleLabel.userInteractionEnabled = NO; + [contentView addSubview:subtitleLabel]; + _subtitleLabel = subtitleLabel; + + UIImageView *arrowImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pay_user_accessory"]]; + arrowImageView.translatesAutoresizingMaskIntoConstraints = NO; + arrowImageView.userInteractionEnabled = NO; + [contentView addSubview:arrowImageView]; + _arrowImageView = arrowImageView; + + [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 forAxis:UILayoutConstraintAxisHorizontal]; + [subtitleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 forAxis:UILayoutConstraintAxisHorizontal]; + [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 forAxis:UILayoutConstraintAxisVertical]; + [subtitleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 forAxis:UILayoutConstraintAxisVertical]; + + const CGFloat horizontalPadding = 12; + const CGFloat verticalPadding = 16; + UILayoutGuide *guide = self.layoutMarginsGuide; + [NSLayoutConstraint activateConstraints:@[ + [shadowView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [shadowView.topAnchor constraintEqualToAnchor:self.topAnchor], + [guide.trailingAnchor constraintEqualToAnchor:shadowView.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:shadowView.bottomAnchor], + + [contentView.leadingAnchor constraintEqualToAnchor:shadowView.leadingAnchor], + [contentView.topAnchor constraintEqualToAnchor:shadowView.topAnchor], + [shadowView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [shadowView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:verticalPadding], + [titleLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:horizontalPadding], + + [subtitleLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:2.0], + [subtitleLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:horizontalPadding], + [contentView.bottomAnchor constraintEqualToAnchor:subtitleLabel.bottomAnchor + constant:verticalPadding], + + [arrowImageView.leadingAnchor constraintEqualToAnchor:subtitleLabel.trailingAnchor + constant:horizontalPadding], + [arrowImageView.leadingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor + constant:horizontalPadding], + [arrowImageView.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:arrowImageView.trailingAnchor + constant:horizontalPadding], + + [arrowImageView.widthAnchor constraintEqualToConstant:32.0], + [arrowImageView.heightAnchor constraintEqualToConstant:32.0], + ]]; + } + return self; +} + +@end diff --git a/DashPay/Presentation/Shared/Buttons/DWBasePressableControl.h b/DashPay/Presentation/Shared/Buttons/DWBasePressableControl.h new file mode 100644 index 000000000..911fd8ed4 --- /dev/null +++ b/DashPay/Presentation/Shared/Buttons/DWBasePressableControl.h @@ -0,0 +1,26 @@ +// +// 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 DWBasePressableControl : UIControl + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Shared/Buttons/DWBasePressableControl.m b/DashPay/Presentation/Shared/Buttons/DWBasePressableControl.m new file mode 100644 index 000000000..222e61029 --- /dev/null +++ b/DashPay/Presentation/Shared/Buttons/DWBasePressableControl.m @@ -0,0 +1,63 @@ +// +// 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 "DWBasePressableControl.h" + +#import "UISpringTimingParameters+DWInit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWBasePressableControl () + +@property (nullable, strong, nonatomic) UIViewPropertyAnimator *animator; + +@end + +NS_ASSUME_NONNULL_END + + +@implementation DWBasePressableControl + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self addTarget:self + action:@selector(tochDown) + forControlEvents:UIControlEventTouchDown | UIControlEventTouchDragEnter]; + [self addTarget:self + action:@selector(touchUp) + forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchDragExit | UIControlEventTouchCancel]; + } + return self; +} + +- (void)tochDown { + [self.animator stopAnimation:YES]; + self.transform = CGAffineTransformMakeScale(0.95, 0.95); +} + +- (void)touchUp { + UISpringTimingParameters *params = [[UISpringTimingParameters alloc] initWithDamping:0.6 response:0.3]; + self.animator = [[UIViewPropertyAnimator alloc] initWithDuration:0.25 timingParameters:params]; + __weak typeof(self) weakSelf = self; + [self.animator addAnimations:^{ + weakSelf.transform = CGAffineTransformIdentity; + }]; + [self.animator startAnimation]; +} + +@end diff --git a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.xib b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.xib index 211d095c0..59b107cb4 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.xib +++ b/DashWallet/Sources/UI/Home/Views/Home Balance View/HomeBalanceView.xib @@ -57,7 +57,7 @@ - + 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..d7868395f 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift @@ -25,6 +25,8 @@ protocol HomeHeaderViewDelegate: AnyObject { func homeHeaderView(_ headerView: HomeHeaderView, profileButtonAction sender: UIControl) func homeHeaderView(_ headerView: HomeHeaderView, retrySyncButtonAction sender: UIView) func homeHeaderViewDidUpdateContents(_ headerView: HomeHeaderView) + + func homeHeaderViewJoinDashPayAction(_ headerView: HomeHeaderView) } // MARK: - HomeHeaderView @@ -39,7 +41,9 @@ final class HomeHeaderView: UIView { private(set) var syncView: SyncView! private(set) var shortcutsView: ShortcutsView! private(set) var stackView: UIStackView! - + + private(set) var welcomeView: DWDPWelcomeView? // Available only in DashPay + weak var shortcutsDelegate: ShortcutsActionDelegate? { get { shortcutsView.actionDelegate @@ -70,7 +74,16 @@ final class HomeHeaderView: UIView { shortcutsView = ShortcutsView(frame: .zero) shortcutsView.translatesAutoresizingMaskIntoConstraints = false + #if DASHPAY + welcomeView = DWDPWelcomeView(frame: .zero) + welcomeView!.translatesAutoresizingMaskIntoConstraints = false + welcomeView!.addTarget(self, action: #selector(joinDashPayAction), for: .touchUpInside) + + let views: [UIView] = [profileView, balanceView, shortcutsView, syncView, welcomeView!] + #else let views: [UIView] = [profileView, balanceView, shortcutsView, syncView] + #endif + let stackView = UIStackView(arrangedSubviews: views) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .vertical @@ -86,7 +99,6 @@ final class HomeHeaderView: UIView { if model.state == .syncFailed || model.state == .noConnection { showSyncView() - } else { hideSyncView() } @@ -133,6 +145,11 @@ final class HomeHeaderView: UIView { delegate?.homeHeaderView(self, profileButtonAction: sender) } + @objc + func joinDashPayAction() { + delegate?.homeHeaderViewJoinDashPayAction(self) + } + func parentScrollViewDidScroll(_ scrollView: UIScrollView) { } func reloadBalance() { diff --git a/DashWallet/Sources/UI/Home/Views/HomeView.swift b/DashWallet/Sources/UI/Home/Views/HomeView.swift index 8bbe43fe1..6a5afaa95 100644 --- a/DashWallet/Sources/UI/Home/Views/HomeView.swift +++ b/DashWallet/Sources/UI/Home/Views/HomeView.swift @@ -197,6 +197,10 @@ extension HomeView: HomeHeaderViewDelegate { func homeHeaderView(_ view: HomeHeaderView, profileButtonAction sender: UIControl) { delegate?.homeView(self, profileButtonAction: sender) } + + func homeHeaderViewJoinDashPayAction(_ headerView: HomeHeaderView) { + + } } // MARK: SyncingHeaderViewDelegate diff --git a/DashWallet/Sources/UI/Views/BalanceView/BalanceView.swift b/DashWallet/Sources/UI/Views/BalanceView/BalanceView.swift index 7adf500db..06eeda97c 100644 --- a/DashWallet/Sources/UI/Views/BalanceView/BalanceView.swift +++ b/DashWallet/Sources/UI/Views/BalanceView/BalanceView.swift @@ -73,6 +73,9 @@ extension BalanceView { } private func reloadView() { + guard let dashBalanceLabel, + let fiatBalanceLabel else { return } + let mainAmountString = dataSource?.mainAmountString ?? NumberFormatter.dashFormatter.string(from: 0)! let supplementaryAmountString = dataSource?.supplementaryAmountString ?? NumberFormatter.fiatFormatter.string(from: 0)! diff --git a/DashWallet/dashwallet-Bridging-Header.h b/DashWallet/dashwallet-Bridging-Header.h index a8d538531..9ad0bdb5c 100644 --- a/DashWallet/dashwallet-Bridging-Header.h +++ b/DashWallet/dashwallet-Bridging-Header.h @@ -102,6 +102,7 @@ static const bool _SNAPSHOT = 0; #import "DWDPRegistrationErrorRetryDelegate.h" #import "DWDPUserObject.h" #import "DWModalUserProfileViewController.h" +#import "DWDPWelcomeView.h" //MARK: CrowdNode #import "DWCheckbox.h" From aa9baf55c72ba026962e3da246625ed4c408fc5a Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 13 Jun 2023 14:10:55 +0400 Subject: [PATCH 003/123] feat: Update DashPay models and setup flow --- .../Home/Model/DWCurrentUserProfileModel.h | 44 ++++ .../Home/Model/DWCurrentUserProfileModel.m | 65 ++++++ .../Home/Model/DWDPUpdateProfileModel.h | 41 ++++ .../Home/Model/DWDPUpdateProfileModel.m | 80 +++++++ .../Setup/Model/DWDashPaySetupModel.h | 26 +++ .../Setup/Model/DWDashPaySetupModel.m | 71 ++++++ DashWallet.xcodeproj/project.pbxproj | 42 ++++ .../Setup/DWDashPaySetupFlowController.h | 15 +- .../Setup/DWDashPaySetupFlowController.m | 45 +++- .../Home/DWHomeViewController+DWShortcuts.m | 4 +- .../Sources/UI/Home/Models/DWDashPayModel.m | 215 +++++++++++++++--- .../UI/Home/Protocols/DWDashPayProtocol.h | 15 +- 12 files changed, 628 insertions(+), 35 deletions(-) create mode 100644 DashPay/Presentation/Home/Model/DWCurrentUserProfileModel.h create mode 100644 DashPay/Presentation/Home/Model/DWCurrentUserProfileModel.m create mode 100644 DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.h create mode 100644 DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.m create mode 100644 DashPay/Presentation/Setup/Model/DWDashPaySetupModel.h create mode 100644 DashPay/Presentation/Setup/Model/DWDashPaySetupModel.m diff --git a/DashPay/Presentation/Home/Model/DWCurrentUserProfileModel.h b/DashPay/Presentation/Home/Model/DWCurrentUserProfileModel.h new file mode 100644 index 000000000..d9bc75111 --- /dev/null +++ b/DashPay/Presentation/Home/Model/DWCurrentUserProfileModel.h @@ -0,0 +1,44 @@ +// +// 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 + +#import "DWDPUpdateProfileModel.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSBlockchainIdentity; + +typedef NS_ENUM(NSUInteger, DWCurrentUserProfileModelState) { + DWCurrentUserProfileModel_None, + DWCurrentUserProfileModel_Loading, + DWCurrentUserProfileModel_Done, + DWCurrentUserProfileModel_Error, +}; + +@interface DWCurrentUserProfileModel : NSObject + +@property (readonly, nonatomic, strong) DWDPUpdateProfileModel *updateModel; + +@property (readonly, nonatomic, assign) DWCurrentUserProfileModelState state; +@property (readonly, nullable, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +- (void)update; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Home/Model/DWCurrentUserProfileModel.m b/DashPay/Presentation/Home/Model/DWCurrentUserProfileModel.m new file mode 100644 index 000000000..f460eb66e --- /dev/null +++ b/DashPay/Presentation/Home/Model/DWCurrentUserProfileModel.m @@ -0,0 +1,65 @@ +// +// 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 "DWCurrentUserProfileModel.h" + +#import "DWEnvironment.h" + +@interface DWCurrentUserProfileModel () + +@property (nonatomic, assign) DWCurrentUserProfileModelState state; + +@end + +@implementation DWCurrentUserProfileModel + +- (instancetype)init { + self = [super init]; + if (self) { + _updateModel = [[DWDPUpdateProfileModel alloc] init]; + } + return self; +} + +- (DSBlockchainIdentity *)blockchainIdentity { + return [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; +} + +- (void)update { + if (self.blockchainIdentity == nil) { + self.state = DWCurrentUserProfileModel_None; + return; + } + + if (self.state == DWCurrentUserProfileModel_Loading) { + return; + } + + self.state = DWCurrentUserProfileModel_Loading; + + __weak typeof(self) weakSelf = self; + [self.blockchainIdentity fetchProfileWithCompletion:^(BOOL success, NSError *_Nonnull error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + strongSelf.state = success ? DWCurrentUserProfileModel_Done : DWCurrentUserProfileModel_Error; + }]; +} + +@end diff --git a/DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.h b/DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.h new file mode 100644 index 000000000..581662f3e --- /dev/null +++ b/DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.h @@ -0,0 +1,41 @@ +// +// 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 + +typedef NS_ENUM(NSUInteger, DWDPUpdateProfileModelState) { + DWDPUpdateProfileModelState_Ready, + DWDPUpdateProfileModelState_Loading, + DWDPUpdateProfileModelState_Error, +}; + +@interface DWDPUpdateProfileModel : NSObject + +@property (readonly, nonatomic, assign) DWDPUpdateProfileModelState state; + +- (void)updateWithDisplayName:(NSString *)rawDisplayName + aboutMe:(NSString *)rawAboutMe + avatarURLString:(nullable NSString *)avatarURLString; + +- (void)retry; +- (void)reset; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.m b/DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.m new file mode 100644 index 000000000..59c44f98e --- /dev/null +++ b/DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.m @@ -0,0 +1,80 @@ +// +// 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 "DWDPUpdateProfileModel.h" + +#import "DWEnvironment.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPUpdateProfileModel () + +@property (nonatomic, assign) DWDPUpdateProfileModelState state; +@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPUpdateProfileModel + +- (DSBlockchainIdentity *)blockchainIdentity { + return [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; +} + +- (void)updateWithDisplayName:(NSString *)rawDisplayName + aboutMe:(NSString *)rawAboutMe + avatarURLString:(nullable NSString *)avatarURLString { + NSString *displayName = rawDisplayName; + if ([rawDisplayName isEqualToString:self.blockchainIdentity.currentDashpayUsername]) { + displayName = @""; + } + displayName = [displayName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + NSString *aboutMe = [rawAboutMe stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + NSString *avatar = avatarURLString; + if (avatar.length == 0) { + avatar = nil; + } + + [self.blockchainIdentity updateDashpayProfileWithDisplayName:displayName + publicMessage:aboutMe + avatarURLString:avatar]; + + [self retry]; +} + +- (void)retry { + self.state = DWDPUpdateProfileModelState_Loading; + + __weak typeof(self) weakSelf = self; + [self.blockchainIdentity signAndPublishProfileWithCompletion:^(BOOL success, BOOL cancelled, NSError *_Nonnull error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + strongSelf.state = success ? DWDPUpdateProfileModelState_Ready : DWDPUpdateProfileModelState_Error; + }]; +} + +- (void)reset { + self.state = DWDPUpdateProfileModelState_Ready; +} + +@end diff --git a/DashPay/Presentation/Setup/Model/DWDashPaySetupModel.h b/DashPay/Presentation/Setup/Model/DWDashPaySetupModel.h new file mode 100644 index 000000000..ac876f39d --- /dev/null +++ b/DashPay/Presentation/Setup/Model/DWDashPaySetupModel.h @@ -0,0 +1,26 @@ +// +// 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 "DWDashPayProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDashPaySetupModel : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/Model/DWDashPaySetupModel.m b/DashPay/Presentation/Setup/Model/DWDashPaySetupModel.m new file mode 100644 index 000000000..1b9a6c8dd --- /dev/null +++ b/DashPay/Presentation/Setup/Model/DWDashPaySetupModel.m @@ -0,0 +1,71 @@ +// +// 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 "DWDashPaySetupModel.h" + +@implementation DWDashPaySetupModel + +@synthesize blockchainIdentity; + +@synthesize lastRegistrationError; + +@synthesize registrationCompleted; + +@synthesize registrationStatus; + +@synthesize unreadNotificationsCount; + +@synthesize username; + +@synthesize userProfile; + +- (BOOL)canRetry { + return NO; +} + +- (void)completeRegistration { + // nop +} + +- (void)createUsername:(nonnull NSString *)username invitation:(nonnull NSURL *)invitation { + NSAssert(NO, @"Should not be called"); + // nop +} + +- (void)retry { + NSAssert(NO, @"Should not be called"); + // nop +} + +- (void)setHasEnoughBalanceForInvitationNotification:(BOOL)value { + // nop +} + +- (BOOL)shouldPresentRegistrationPaymentConfirmation { + return YES; +} + +- (void)updateUsernameStatus { + // nop +} + +- (void)verifyDeeplink:(nonnull NSURL *)url completion:(nonnull void (^)(BOOL, NSString *_Nullable, NSString *_Nullable))completion { + NSAssert(NO, @"Should not be called"); + // nop +} + +@end diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 6b8aab8d9..5ac77c266 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -1385,6 +1385,9 @@ C9D2C95E2A386D7E00D15901 /* DWBasePressableControl.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C95D2A386D7E00D15901 /* DWBasePressableControl.m */; }; C9D2C9622A386DA200D15901 /* DWDPWelcomeView.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C9602A386DA200D15901 /* DWDPWelcomeView.m */; }; C9D2C9652A38733B00D15901 /* DPAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9D2C9642A38733B00D15901 /* DPAssets.xcassets */; }; + C9D2C9692A3875BA00D15901 /* DWCurrentUserProfileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C9672A3875BA00D15901 /* DWCurrentUserProfileModel.m */; }; + C9D2C96C2A38762800D15901 /* DWDPUpdateProfileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C96B2A38762800D15901 /* DWDPUpdateProfileModel.m */; }; + C9D2C9712A38778E00D15901 /* DWDashPaySetupModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C9702A38778E00D15901 /* DWDashPaySetupModel.m */; }; C9F067F229E4576D0022D958 /* HomeBalanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F067F129E4576D0022D958 /* HomeBalanceView.swift */; }; C9F067F429E543630022D958 /* HomeBalanceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F067F329E457790022D958 /* HomeBalanceView.xib */; }; C9F42F9F29DA82E5001BC549 /* PayableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42F9E29DA82E5001BC549 /* PayableViewController.swift */; }; @@ -2645,6 +2648,12 @@ C9D2C9602A386DA200D15901 /* DWDPWelcomeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPWelcomeView.m; sourceTree = ""; }; C9D2C9612A386DA200D15901 /* DWDPWelcomeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPWelcomeView.h; sourceTree = ""; }; C9D2C9642A38733B00D15901 /* DPAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DPAssets.xcassets; sourceTree = ""; }; + C9D2C9672A3875BA00D15901 /* DWCurrentUserProfileModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWCurrentUserProfileModel.m; sourceTree = ""; }; + C9D2C9682A3875BA00D15901 /* DWCurrentUserProfileModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWCurrentUserProfileModel.h; sourceTree = ""; }; + C9D2C96A2A38762700D15901 /* DWDPUpdateProfileModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPUpdateProfileModel.h; sourceTree = ""; }; + C9D2C96B2A38762800D15901 /* DWDPUpdateProfileModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPUpdateProfileModel.m; sourceTree = ""; }; + C9D2C96F2A38778E00D15901 /* DWDashPaySetupModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDashPaySetupModel.h; sourceTree = ""; }; + C9D2C9702A38778E00D15901 /* DWDashPaySetupModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPaySetupModel.m; sourceTree = ""; }; C9F067F129E4576D0022D958 /* HomeBalanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeBalanceView.swift; sourceTree = ""; }; C9F067F329E457790022D958 /* HomeBalanceView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HomeBalanceView.xib; sourceTree = ""; }; C9F42F9E29DA82E5001BC549 /* PayableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayableViewController.swift; sourceTree = ""; }; @@ -6407,6 +6416,7 @@ C9D2C9582A386A5B00D15901 /* Presentation */ = { isa = PBXGroup; children = ( + C9D2C96D2A38777A00D15901 /* Setup */, C9D2C9592A386A6700D15901 /* Home */, C9D2C95A2A386D5200D15901 /* Shared */, ); @@ -6416,6 +6426,7 @@ C9D2C9592A386A6700D15901 /* Home */ = { isa = PBXGroup; children = ( + C9D2C9662A3875AB00D15901 /* Model */, C9D2C95F2A386D9700D15901 /* Views */, ); path = Home; @@ -6455,6 +6466,34 @@ path = Assets; sourceTree = ""; }; + C9D2C9662A3875AB00D15901 /* Model */ = { + isa = PBXGroup; + children = ( + C9D2C96A2A38762700D15901 /* DWDPUpdateProfileModel.h */, + C9D2C96B2A38762800D15901 /* DWDPUpdateProfileModel.m */, + C9D2C9682A3875BA00D15901 /* DWCurrentUserProfileModel.h */, + C9D2C9672A3875BA00D15901 /* DWCurrentUserProfileModel.m */, + ); + path = Model; + sourceTree = ""; + }; + C9D2C96D2A38777A00D15901 /* Setup */ = { + isa = PBXGroup; + children = ( + C9D2C96E2A38778400D15901 /* Model */, + ); + path = Setup; + sourceTree = ""; + }; + C9D2C96E2A38778400D15901 /* Model */ = { + isa = PBXGroup; + children = ( + C9D2C96F2A38778E00D15901 /* DWDashPaySetupModel.h */, + C9D2C9702A38778E00D15901 /* DWDashPaySetupModel.m */, + ); + path = Model; + sourceTree = ""; + }; C9F42FA729DC09C6001BC549 /* Style */ = { isa = PBXGroup; children = ( @@ -8274,6 +8313,7 @@ C9D2C7852A320AA000D15901 /* BalanceModel.swift in Sources */, C9D2C7862A320AA000D15901 /* TxReclassifyTransactionsInfoViewController.swift in Sources */, C9D2C7872A320AA000D15901 /* DWTransactionListDataProvider.m in Sources */, + C9D2C9692A3875BA00D15901 /* DWCurrentUserProfileModel.m in Sources */, C9D2C7882A320AA000D15901 /* DWModalPopupTransition.m in Sources */, C9D2C7892A320AA000D15901 /* DWAdvancedSecurityViewController.m in Sources */, C9D2C78A2A320AA000D15901 /* DWInputUsernameViewController.m in Sources */, @@ -8522,8 +8562,10 @@ C9D2C87C2A320AA000D15901 /* DWSwitcherFormCellModel.m in Sources */, C9D2C87D2A320AA000D15901 /* DWIntrinsicTextView.m in Sources */, C9D2C87E2A320AA000D15901 /* DWExploreTestnetViewController.m in Sources */, + C9D2C96C2A38762800D15901 /* DWDPUpdateProfileModel.m in Sources */, C9D2C87F2A320AA000D15901 /* AddressUserInfoDAO.swift in Sources */, C9D2C8802A320AA000D15901 /* DWDashPayContactsUpdater.m in Sources */, + C9D2C9712A38778E00D15901 /* DWDashPaySetupModel.m in Sources */, C9D2C8812A320AA000D15901 /* DWModalTransition.m in Sources */, C9D2C8822A320AA000D15901 /* DWUpholdMainModel.m in Sources */, C9D2C8832A320AA000D15901 /* MerchantsDataProvider.swift in Sources */, diff --git a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.h b/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.h index 14149d9b9..f532d2205 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.h +++ b/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.h @@ -21,9 +21,22 @@ NS_ASSUME_NONNULL_BEGIN +@class DWDashPaySetupFlowController; + +@protocol DWDashPaySetupFlowControllerDelegate + +- (void)dashPaySetupFlowController:(DWDashPaySetupFlowController *)controller + didConfirmUsername:(NSString *)username; + +@end + @interface DWDashPaySetupFlowController : UIViewController -- (instancetype)initWithDashPayModel:(id)dashPayModel; +- (instancetype)initWithDashPayModel:(id)dashPayModel + invitation:(nullable NSURL *)invitationURL + definedUsername:(nullable NSString *)definedUsername; + +- (instancetype)initWithConfirmationDelegate:(id)delegate; - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; diff --git a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.m b/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.m index 0f5dd09b0..847eb7125 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.m +++ b/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.m @@ -21,6 +21,7 @@ #import "DWContainerViewController.h" #import "DWCreateUsernameViewController.h" #import "DWDPRegistrationStatus.h" +#import "DWDashPaySetupModel.h" #import "DWRegistrationCompletedViewController.h" #import "DWUIKit.h" #import "DWUsernameHeaderView.h" @@ -49,6 +50,9 @@ @interface DWDashPaySetupFlowController () @property (readonly, nonatomic, strong) id dashPayModel; +@property (nullable, nonatomic, readonly, strong) NSURL *invitationURL; +@property (nullable, nonatomic, readonly, copy) NSString *definedUsername; +@property (nullable, nonatomic, weak) id confirmationDelegate; @property (null_resettable, nonatomic, strong) DWUsernameHeaderView *headerView; @property (null_resettable, nonatomic, strong) UIView *contentView; @@ -63,10 +67,24 @@ @interface DWDashPaySetupFlowController () )dashPayModel { +- (instancetype)initWithDashPayModel:(id)dashPayModel + invitation:(NSURL *)invitationURL + definedUsername:(NSString *)definedUsername { self = [super initWithNibName:nil bundle:nil]; if (self) { _dashPayModel = dashPayModel; + _invitationURL = invitationURL; + _definedUsername = definedUsername; + } + return self; +} + +- (instancetype)initWithConfirmationDelegate:(id)delegate { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _dashPayModel = [[DWDashPaySetupModel alloc] init]; + _invitationURL = nil; + _confirmationDelegate = delegate; } return self; } @@ -137,6 +155,12 @@ - (void)viewDidAppear:(BOOL)animated { [self.headerView showInitialAnimation]; } +#pragma mark - DWNavigationFullscreenable + +- (BOOL)requiresNoNavigationBar { + return YES; +} + #pragma mark - Private - (void)registrationStatusUpdatedNotification { @@ -148,6 +172,11 @@ - (void)registrationStatusUpdatedNotification { } - (void)setCurrentStateController { + if (self.definedUsername != nil) { + [self createUsername:self.definedUsername]; + return; + } + if (self.dashPayModel.registrationStatus == nil || self.dashPayModel.registrationStatus.failed) { [self showCreateUsernameController]; @@ -164,7 +193,7 @@ - (void)setCurrentStateController { - (void)createUsername:(NSString *)username { __weak typeof(self) weakSelf = self; - [self.dashPayModel createUsername:username]; + [self.dashPayModel createUsername:username invitation:self.invitationURL]; [self showPendingController:username]; } @@ -181,6 +210,7 @@ - (DWUsernameHeaderView *)headerView { _headerView = [[DWUsernameHeaderView alloc] initWithFrame:CGRectZero]; _headerView.translatesAutoresizingMaskIntoConstraints = NO; _headerView.preservesSuperviewLayoutMargins = YES; + _headerView.cancelButton.hidden = self.confirmationDelegate != nil; [_headerView.cancelButton addTarget:self action:@selector(cancelButtonAction) forControlEvents:UIControlEventTouchUpInside]; @@ -259,9 +289,14 @@ - (void)confirmUsernameViewControllerDidConfirm:(DWConfirmUsernameViewController NSString *username = controller.username; [controller dismissViewControllerAnimated:YES completion:^{ - // initiate creation process once confirmation is dismissed because - // DashSync will be showing pin request modally - [self createUsername:username]; + if (self.confirmationDelegate) { + [self.confirmationDelegate dashPaySetupFlowController:self didConfirmUsername:username]; + } + else { + // initiate creation process once confirmation is dismissed because + // DashSync will be showing pin request modally + [self createUsername:username]; + } }]; } diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController+DWShortcuts.m b/DashWallet/Sources/UI/Home/DWHomeViewController+DWShortcuts.m index 47d0341bc..8d91c7f1c 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController+DWShortcuts.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController+DWShortcuts.m @@ -173,7 +173,9 @@ - (void)payToAddressAction:(UIView *)sender { - (void)showCreateUsername { DWDashPaySetupFlowController *controller = [[DWDashPaySetupFlowController alloc] - initWithDashPayModel:self.model.dashPayModel]; + initWithDashPayModel:self.model.dashPayModel + invitation:nil + definedUsername:nil]; controller.modalPresentationStyle = UIModalPresentationFullScreen; [self presentViewController:controller animated:YES completion:nil]; } diff --git a/DashWallet/Sources/UI/Home/Models/DWDashPayModel.m b/DashWallet/Sources/UI/Home/Models/DWDashPayModel.m index 7e836961c..c5ad4d7ec 100644 --- a/DashWallet/Sources/UI/Home/Models/DWDashPayModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWDashPayModel.m @@ -17,6 +17,7 @@ #import "DWDashPayModel.h" +#import "DWCurrentUserProfileModel.h" #import "DWDPRegistrationStatus.h" #import "DWDashPayConstants.h" #import "DWEnvironment.h" @@ -28,11 +29,14 @@ NS_ASSUME_NONNULL_BEGIN NSNotificationName const DWDashPayRegistrationStatusUpdatedNotification = @"DWDashPayRegistrationStatusUpdatedNotification"; +NSNotificationName const DWDashPaySentContactRequestToInviter = @"kDWDashPaySentContactRequestToInviter"; @interface DWDashPayModel () @property (nullable, nonatomic, strong) DWDPRegistrationStatus *registrationStatus; @property (nullable, nonatomic, strong) NSError *lastRegistrationError; +@property (nonatomic, assign) BOOL isInvitationNotificationAllowed; +@property (nullable, nonatomic, strong) NSURL *invitation; @end @@ -40,25 +44,30 @@ @interface DWDashPayModel () @implementation DWDashPayModel +@synthesize userProfile = _userProfile; + - (instancetype)init { self = [super init]; if (self) { DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; DSBlockchainIdentity *blockchainIdentity = wallet.defaultBlockchainIdentity; - NSString *username = [DWGlobalOptions sharedInstance].dashpayUsername; + //NSString *username = [DWGlobalOptions sharedInstance].persistedDashPayUsername; //TODO: DashPay - if (blockchainIdentity) { - if (username == nil) { - [DWGlobalOptions sharedInstance].dashpayUsername = blockchainIdentity.currentDashpayUsername; - username = blockchainIdentity.currentDashpayUsername; - } + _userProfile = [[DWCurrentUserProfileModel alloc] init]; - // username can be nil at this point - [self updateRegistrationStatusForBlockchainIdentity:blockchainIdentity - username:username]; - } + //TODO: DashPay +// if (blockchainIdentity) { +// if (username == nil) { +// [DWGlobalOptions sharedInstance].persistedDashPayUsername = blockchainIdentity.currentDashpayUsername; +// username = blockchainIdentity.currentDashpayUsername; +// } +// +// // username can be nil at this point +// [self updateRegistrationStatusForBlockchainIdentity:blockchainIdentity username:username]; +// } - DSLogPrivate(@"DWDP: Current username: %@", [DWGlobalOptions sharedInstance].dashpayUsername); + //TODO: DashPay +// DSLogPrivate(@"DWDP: Current username: %@", [DWGlobalOptions sharedInstance].persistedDashPayUsername); NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self @@ -74,7 +83,12 @@ - (instancetype)init { } - (NSString *)username { - return [DWGlobalOptions sharedInstance].dashpayUsername; + DSBlockchainIdentity *blockchainIdentity = [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; + return blockchainIdentity.currentDashpayUsername;// ?: [DWGlobalOptions sharedInstance].persistedDashPayUsername; //TODO: DashPay +} + +- (DSBlockchainIdentity *)blockchainIdentity { + return [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; } - (BOOL)registrationCompleted { @@ -82,6 +96,10 @@ - (BOOL)registrationCompleted { } - (NSUInteger)unreadNotificationsCount { + //TODO: DashPay + if (self.isInvitationNotificationAllowed) // && [DWGlobalOptions sharedInstance].shouldShowInvitationsBadge) { + return 1; + return [DWNotificationsProvider sharedInstance].data.unreadItems.count; } @@ -91,11 +109,50 @@ - (BOOL)shouldPresentRegistrationPaymentConfirmation { return blockchainIdentity == nil; } -- (void)createUsername:(NSString *)username { +- (void)createUsername:(NSString *)username invitation:(NSURL *)invitationURL { + self.invitation = invitationURL; self.lastRegistrationError = nil; - [DWGlobalOptions sharedInstance].dashpayUsername = username; + //[DWGlobalOptions sharedInstance].persistedDashPayUsername = username; //TODO: DashPay DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + + if (invitationURL != nil) { + DSBlockchainInvitation *invitation = [[DSBlockchainInvitation alloc] initWithInvitationLink:invitationURL.absoluteString inWallet:wallet]; + + __weak typeof(self) weakSelf = self; + [invitation + acceptInvitationUsingWalletIndex:0 + setDashpayUsername:username + authenticationPrompt:NSLocalizedString(@"Would you like to accept the invitation?", nil) + identityRegistrationSteps:[self invitationSteps] + stepCompletion:^(DSBlockchainIdentityRegistrationStep stepCompleted) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf handleSteps:stepCompleted error:nil]; + } + completion:^(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *_Nonnull error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + NSLog(@">>> completed invitation %@ - %@", @(stepsCompleted), error); + + [strongSelf handleSteps:stepsCompleted error:error]; + + if(!error) + { + [strongSelf sendContactRequestToInviterUsingInvitationURL:invitationURL]; + } + } + completionQueue:dispatch_get_main_queue()]; + + return; + } + DSBlockchainIdentity *blockchainIdentity = wallet.defaultBlockchainIdentity; if (blockchainIdentity) { @@ -119,16 +176,62 @@ - (void)createUsername:(NSString *)username { } } +- (void)sendContactRequestToInviterUsingInvitationURL:(NSURL *)invitationURL +{ + NSURLComponents *components = [NSURLComponents componentsWithURL:invitationURL resolvingAgainstBaseURL:NO]; + NSString *username; + + for (NSURLQueryItem *item in components.queryItems) { + if ([item.name isEqualToString:@"du"]) { + username = item.value; + break; + } + } + + if (!username) { + return; + } + + DSIdentitiesManager *manager = [DWEnvironment sharedInstance].currentChainManager.identitiesManager; + __weak typeof(self) weakSelf = self; + [manager searchIdentityByDashpayUsername:username withCompletion:^(BOOL success, DSBlockchainIdentity * _Nullable blockchainIdentity, NSError * _Nullable error) { + if (success) { + + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + + [myBlockchainIdentity sendNewFriendRequestToBlockchainIdentity:blockchainIdentity + completion:^(BOOL success, NSArray *_Nullable errors) { + DSLog(@"Friend request sent %i", success); + }]; + } + }]; +} + +- (void)sendContactRequestToBlockchainIdentity:(DSBlockchainIdentity *) blockchainIdentity { + + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + [myBlockchainIdentity sendNewFriendRequestToBlockchainIdentity:blockchainIdentity + completion:^(BOOL success, NSArray *_Nullable errors) { + + }]; +} + - (BOOL)canRetry { return self.username != nil; } - (void)retry { - [self createUsername:self.username]; + [self createUsername:self.username invitation:self.invitation]; } - (void)completeRegistration { + //TODO: DashPay + //[DWGlobalOptions sharedInstance].shouldShowInvitationsBadge = YES; [DWGlobalOptions sharedInstance].dashpayRegistrationCompleted = YES; + //[DWGlobalOptions sharedInstance].persistedDashPayUsername = nil; + NSAssert(self.username != nil, @"Default DSBlockchainIdentity has an empty username"); self.registrationStatus = nil; [[NSNotificationCenter defaultCenter] postNotificationName:DWDashPayRegistrationStatusUpdatedNotification object:nil]; } @@ -142,15 +245,59 @@ - (void)updateUsernameStatus { if (blockchainIdentity) { NSString *username = blockchainIdentity.currentDashpayUsername; DWGlobalOptions *options = [DWGlobalOptions sharedInstance]; - if (options.dashpayUsername == nil && username != nil) { - options.dashpayUsername = username; - [self updateRegistrationStatusForBlockchainIdentity:blockchainIdentity - username:username]; - } + //TODO: DashPay +// if (options.persistedDashPayUsername == nil && username != nil) { +// options.persistedDashPayUsername = username; +// [self updateRegistrationStatusForBlockchainIdentity:blockchainIdentity +// username:username]; +// } } [self didChangeValueForKey:key]; } +- (void)setHasEnoughBalanceForInvitationNotification:(BOOL)value { + //TODO: DashPay + //self.isInvitationNotificationAllowed = ([DWGlobalOptions sharedInstance].dpInvitationFlowEnabled && value); +} + +- (void)verifyDeeplink:(NSURL *)url + completion:(void (^)(BOOL success, + NSString *_Nullable errorTitle, + NSString *_Nullable errorMessage))completion { + DSChain *chain = [DWEnvironment sharedInstance].currentChain; + [DSBlockchainInvitation + verifyInvitationLink:url.absoluteString + onChain:chain + completion:^(DSTransaction *_Nonnull transaction, bool spent, NSError *_Nonnull error) { + NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + NSString *username = @""; + for (NSURLQueryItem *item in components.queryItems) { + if ([item.name isEqualToString:@"du"]) { + username = item.value; + break; + } + } + if (transaction != nil) { + completion(YES, nil, nil); + } + else { + if (spent) { + completion( + NO, + NSLocalizedString(@"Invitation already claimed", nil), + [NSString stringWithFormat:NSLocalizedString(@"Your invitation from %@ has been already claimed", nil), username]); + } + else { + completion( + NO, + NSLocalizedString(@"Invalid Inviation", nil), + [NSString stringWithFormat:NSLocalizedString(@"Your invitation from %@ is not valid", nil), username]); + } + } + } + completionQueue:dispatch_get_main_queue()]; +} + #pragma mark - Notifications - (void)notificationsWillUpdate { @@ -195,8 +342,7 @@ - (void)registerIdentity:(DSBlockchainIdentity *)blockchainIdentity { return; } - [strongSelf handleSteps:stepCompleted - error:nil]; + [strongSelf handleSteps:stepCompleted error:nil]; } completion:^(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *_Nonnull error) { __strong typeof(weakSelf) strongSelf = weakSelf; @@ -222,8 +368,7 @@ - (void)continueRegistering:(DSBlockchainIdentity *)blockchainIdentity { return; } - [strongSelf handleSteps:stepCompleted - error:nil]; + [strongSelf handleSteps:stepCompleted error:nil]; } completion:^(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *_Nonnull error) { __strong typeof(weakSelf) strongSelf = weakSelf; @@ -240,6 +385,12 @@ - (DSBlockchainIdentityRegistrationStep)steps { return DSBlockchainIdentityRegistrationStep_RegistrationStepsWithUsername; } +- (DSBlockchainIdentityRegistrationStep)invitationSteps { + return (DSBlockchainIdentityRegistrationStep_LocalInWalletPersistence | + DSBlockchainIdentityRegistrationStep_Identity | + DSBlockchainIdentityRegistrationStep_Username); +} + - (void)handleSteps:(DSBlockchainIdentityRegistrationStep)stepsCompleted error:(nullable NSError *)error { NSAssert([NSThread isMainThread], @"Main thread is assumed here"); @@ -254,9 +405,15 @@ - (void)handleSteps:(DSBlockchainIdentityRegistrationStep)stepsCompleted error:( self.lastRegistrationError = error; } - DWDPRegistrationState state = [self stateForCompletedSteps:stepsCompleted]; - const BOOL failed = error != nil; + + if(failed && self.blockchainIdentity.isFromIncomingInvitation) { + [self cancel]; + [self.blockchainIdentity unregisterLocally]; + return; + } + + DWDPRegistrationState state = [self stateForCompletedSteps:stepsCompleted]; self.registrationStatus = [[DWDPRegistrationStatus alloc] initWithState:state failed:failed username:self.username]; [[NSNotificationCenter defaultCenter] postNotificationName:DWDashPayRegistrationStatusUpdatedNotification object:nil]; @@ -265,7 +422,7 @@ - (void)handleSteps:(DSBlockchainIdentityRegistrationStep)stepsCompleted error:( - (void)cancel { NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - [DWGlobalOptions sharedInstance].dashpayUsername = nil; + // [DWGlobalOptions sharedInstance].persistedDashPayUsername = nil; //TODO: DashPay self.lastRegistrationError = nil; self.registrationStatus = nil; @@ -281,6 +438,10 @@ - (void)updateRegistrationStatusForBlockchainIdentity:(DSBlockchainIdentity *)bl if (isDone) { [DWGlobalOptions sharedInstance].dashpayRegistrationCompleted = YES; + //[DWGlobalOptions sharedInstance].persistedDashPayUsername = nil; //TODO: DashPay + NSAssert(self.username != nil, @"Default DSBlockchainIdentity has an empty username"); + + [self.userProfile update]; //TODO: DashPay } } } diff --git a/DashWallet/Sources/UI/Home/Protocols/DWDashPayProtocol.h b/DashWallet/Sources/UI/Home/Protocols/DWDashPayProtocol.h index 3fe934ad3..40556481f 100644 --- a/DashWallet/Sources/UI/Home/Protocols/DWDashPayProtocol.h +++ b/DashWallet/Sources/UI/Home/Protocols/DWDashPayProtocol.h @@ -17,26 +17,39 @@ #import +#import "DWCurrentUserProfileModel.h" + NS_ASSUME_NONNULL_BEGIN extern NSNotificationName const DWDashPayRegistrationStatusUpdatedNotification; +extern NSNotificationName const DWDashPaySentContactRequestToInviter; @class DWDPRegistrationStatus; +@class DSBlockchainIdentity; +@class DWCurrentUserProfileModel; @protocol DWDashPayProtocol @property (nullable, readonly, nonatomic, copy) NSString *username; +@property (nullable, readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; @property (nullable, readonly, nonatomic, strong) DWDPRegistrationStatus *registrationStatus; +@property (readonly, nonatomic, strong) DWCurrentUserProfileModel *userProfile; @property (nullable, readonly, nonatomic, strong) NSError *lastRegistrationError; @property (readonly, nonatomic, assign) BOOL registrationCompleted; @property (readonly, nonatomic, assign) NSUInteger unreadNotificationsCount; - (BOOL)shouldPresentRegistrationPaymentConfirmation; -- (void)createUsername:(NSString *)username; +- (void)createUsername:(NSString *)username invitation:(NSURL *)invitation; - (BOOL)canRetry; - (void)retry; - (void)completeRegistration; - (void)updateUsernameStatus; +- (void)setHasEnoughBalanceForInvitationNotification:(BOOL)value; + +- (void)verifyDeeplink:(NSURL *)url + completion:(void (^)(BOOL success, + NSString *_Nullable errorTitle, + NSString *_Nullable errorMessage))completion; @end From 2a282c0abf74770d274faa70ecfdee4e32860780 Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 13 Jun 2023 15:00:52 +0400 Subject: [PATCH 004/123] feat: Show profile view on home screen --- .../Home/Views}/DashPayProfileView.swift | 2 +- DashWallet.xcodeproj/project.pbxproj | 4 +- .../Home Header View/HomeHeaderView.swift | 41 +++++++++++-------- 3 files changed, 27 insertions(+), 20 deletions(-) rename {DashWallet/Sources/UI/Home/Views/Home Header View => DashPay/Presentation/Home/Views}/DashPayProfileView.swift (97%) diff --git a/DashWallet/Sources/UI/Home/Views/Home Header View/DashPayProfileView.swift b/DashPay/Presentation/Home/Views/DashPayProfileView.swift similarity index 97% rename from DashWallet/Sources/UI/Home/Views/Home Header View/DashPayProfileView.swift rename to DashPay/Presentation/Home/Views/DashPayProfileView.swift index e6adcf621..12ede0c0a 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Header View/DashPayProfileView.swift +++ b/DashPay/Presentation/Home/Views/DashPayProfileView.swift @@ -79,7 +79,7 @@ class DashPayProfileView: UIControl { contentView.addSubview(badgeView) NSLayoutConstraint.activate([ - contentView.topAnchor.constraint(equalTo: topAnchor), + contentView.topAnchor.constraint(equalTo: topAnchor, constant: 10), contentView.leadingAnchor.constraint(equalTo: leadingAnchor), contentView.trailingAnchor.constraint(equalTo: trailingAnchor), contentView.bottomAnchor.constraint(equalTo: bottomAnchor), diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 5ac77c266..4fe1069c6 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -1411,7 +1411,6 @@ 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 */; }; @@ -6452,6 +6451,7 @@ C9D2C95F2A386D9700D15901 /* Views */ = { isa = PBXGroup; children = ( + C9F451FA2A0CC2A800825057 /* DashPayProfileView.swift */, C9D2C9612A386DA200D15901 /* DWDPWelcomeView.h */, C9D2C9602A386DA200D15901 /* DWDPWelcomeView.m */, ); @@ -6514,7 +6514,6 @@ C9F451FE2A0CE60400825057 /* Home Header View */ = { isa = PBXGroup; children = ( - C9F451FA2A0CC2A800825057 /* DashPayProfileView.swift */, C9F451F42A0CAC9400825057 /* HomeHeaderView.swift */, C9F4520A2A1209D100825057 /* HomeHeaderModel.swift */, ); @@ -7554,7 +7553,6 @@ 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 */, 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 d7868395f..9fb2bf43b 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,7 @@ protocol HomeHeaderViewDelegate: AnyObject { func homeHeaderView(_ headerView: HomeHeaderView, profileButtonAction sender: UIControl) func homeHeaderView(_ headerView: HomeHeaderView, retrySyncButtonAction sender: UIView) func homeHeaderViewDidUpdateContents(_ headerView: HomeHeaderView) - + func homeHeaderViewJoinDashPayAction(_ headerView: HomeHeaderView) } @@ -36,14 +36,17 @@ 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! - - private(set) var welcomeView: DWDPWelcomeView? // Available only in DashPay - + + // Available only in DashPay + private(set) var profileView: DashPayProfileView? + private(set) var welcomeView: DWDPWelcomeView? + private var isProfileReady = false + weak var shortcutsDelegate: ShortcutsActionDelegate? { get { shortcutsView.actionDelegate @@ -60,11 +63,6 @@ final class HomeHeaderView: UIView { 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 @@ -75,15 +73,20 @@ final class HomeHeaderView: UIView { shortcutsView.translatesAutoresizingMaskIntoConstraints = false #if DASHPAY + profileView = DashPayProfileView(frame: .zero) + profileView!.translatesAutoresizingMaskIntoConstraints = false + profileView!.addTarget(self, action: #selector(profileViewAction(_:)), for: .touchUpInside) + profileView!.isHidden = true + welcomeView = DWDPWelcomeView(frame: .zero) welcomeView!.translatesAutoresizingMaskIntoConstraints = false welcomeView!.addTarget(self, action: #selector(joinDashPayAction), for: .touchUpInside) - - let views: [UIView] = [profileView, balanceView, shortcutsView, syncView, welcomeView!] + + let views: [UIView] = [profileView!, balanceView, shortcutsView, syncView, welcomeView!] #else - let views: [UIView] = [profileView, balanceView, shortcutsView, syncView] + let views: [UIView] = [balanceView, shortcutsView, syncView] #endif - + let stackView = UIStackView(arrangedSubviews: views) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .vertical @@ -148,8 +151,11 @@ final class HomeHeaderView: UIView { @objc func joinDashPayAction() { delegate?.homeHeaderViewJoinDashPayAction(self) + + isProfileReady.toggle() + updateProfileView() } - + func parentScrollViewDidScroll(_ scrollView: UIScrollView) { } func reloadBalance() { @@ -165,7 +171,10 @@ final class HomeHeaderView: UIView { } private func updateProfileView() { - profileView.isHidden = true + profileView!.username = "madmax" + + profileView!.isHidden = !isProfileReady + welcomeView!.isHidden = isProfileReady // TODO: Platform // let status = model?.dashPayModel.registrationStatus From 79a749af6b46b4dd50d09ec555e77132c4c1c4d5 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 19 Jun 2023 18:50:37 +0400 Subject: [PATCH 005/123] feat: Moving DashPay features into wallet --- .../icon_info.imageset/Contents.json | 23 + .../icon_info.imageset/icon_info.png | Bin 0 -> 497 bytes .../icon_info.imageset/icon_info@2x.png | Bin 0 -> 941 bytes .../icon_info.imageset/icon_info@3x.png | Bin 0 -> 1353 bytes ...WFullScreenModalControllerViewController.h | 44 + ...WFullScreenModalControllerViewController.m | 104 + .../Filter View/DWFilterHeaderView.xib | 34 +- .../Home/DWDashPayReadyProtocol.h | 7 +- .../Home/Views/DashPayProfileView.swift | 4 +- .../InfoPopup/DWInfoPopupContentView.h | 7 +- .../InfoPopup/DWInfoPopupContentView.m | 170 + .../InfoPopup/DWInfoPopupViewController.h | 12 +- .../InfoPopup/DWInfoPopupViewController.m | 73 + .../Menu/DWMainMenuViewController+DashPay.h | 26 + .../Menu/DWMainMenuViewController+DashPay.m | 40 + .../DWAvatarEditSelectorViewController.h | 39 + .../DWAvatarEditSelectorViewController.m | 103 + .../Views/DWAvatarEditSelectorContentView.h | 18 +- .../Views/DWAvatarEditSelectorContentView.m | 128 + .../EditProfile/DWCropAvatarViewController.h | 25 +- .../EditProfile/DWCropAvatarViewController.m | 292 ++ .../EditProfile/DWEditProfileViewController.h | 47 + .../EditProfile/DWEditProfileViewController.m | 369 ++ .../DWRootEditProfileViewController.h | 25 +- .../DWRootEditProfileViewController.m | 117 + .../DWAvatarGravatarViewController.h | 4 +- .../DWAvatarGravatarViewController.m | 120 + .../DWAvatarPublicURLViewController.h | 26 + .../DWAvatarPublicURLViewController.m | 128 + .../Skeleton/DWExternalSourceViewController.h | 53 + .../Skeleton/DWExternalSourceViewController.m | 185 + .../Views/DWAvatarExternalLoadingView.h | 15 +- .../Views/DWAvatarExternalLoadingView.m | 124 + .../Views/DWAvatarExternalSourceConfig.h | 14 +- .../Views/DWAvatarExternalSourceConfig.m | 6 +- .../Views/DWAvatarExternalSourceView.h | 23 +- .../Views/DWAvatarExternalSourceView.m | 236 ++ .../EditProfile/Imgur/DWImgurInfoChildView.h | 37 + .../EditProfile/Imgur/DWImgurInfoChildView.m | 159 + .../Imgur/DWImgurInfoViewController.h | 38 + .../Imgur/DWImgurInfoViewController.m | 72 + .../EditProfile/Imgur/DWImgurItemView.h | 28 + .../EditProfile/Imgur/DWImgurItemView.m | 55 +- .../SaveAlert/DWSaveAlertChildView.h | 37 + .../SaveAlert/DWSaveAlertChildView.m | 146 + .../SaveAlert/DWSaveAlertViewController.h | 37 + .../SaveAlert/DWSaveAlertViewController.m | 89 + .../Upload/DWHourGlassAnimationView.h | 2 +- .../Upload/DWHourGlassAnimationView.m | 103 + .../Upload/DWUploadAvatarChildView.h | 39 + .../Upload/DWUploadAvatarChildView.m | 239 ++ .../EditProfile/Upload/DWUploadAvatarModel.h | 23 +- .../EditProfile/Upload/DWUploadAvatarModel.m | 169 + .../Upload/DWUploadAvatarViewController.h | 19 +- .../Upload/DWUploadAvatarViewController.m | 92 + .../EditProfile/Utils/DWFaceDetector.h | 6 +- .../EditProfile/Utils/DWFaceDetector.m | 95 + .../Base/DWTextFieldFormCellModel.h | 62 + .../Base/DWTextFieldFormCellModel.m | 68 + .../Base/DWTextInputFormTableViewCell.h | 28 + .../CellModels/Base/DWTextViewFormCellModel.h | 26 + .../CellModels/Base/DWTextViewFormCellModel.m | 6 +- .../CellModels/DWProfileAboutCellModel.h | 4 +- .../CellModels/DWProfileAboutCellModel.m | 30 +- .../DWProfileDisplayNameCellModel.h | 4 +- .../DWProfileDisplayNameCellModel.m | 20 +- .../Views/DWEditProfileAvatarView.h | 16 +- .../Views/DWEditProfileAvatarView.m | 105 + .../EditProfile/Views/DWEditProfileBaseCell.h | 18 +- .../EditProfile/Views/DWEditProfileBaseCell.m | 114 + .../Views/DWEditProfileTextFieldCell.h | 18 +- .../Views/DWEditProfileTextFieldCell.m | 159 + .../Views/DWEditProfileTextViewCell.h | 13 +- .../Views/DWEditProfileTextViewCell.m | 132 + .../UserProfile/DWCurrentUserProfileView.h | 19 +- .../UserProfile/DWCurrentUserProfileView.m | 158 + .../Profile/UserProfile/DWDPWelcomeMenuView.h | 4 +- .../Profile/UserProfile/DWDPWelcomeMenuView.m | 129 + .../DWErrorUpdatingUserProfileView.h | 14 +- .../DWErrorUpdatingUserProfileView.m | 129 + .../UserProfile/DWUpdatingUserProfileView.h | 2 +- .../UserProfile/DWUpdatingUserProfileView.m | 103 + .../UserProfile/DWUserProfileContainerView.h | 14 +- .../UserProfile/DWUserProfileContainerView.m | 130 + ...lockchainIdentity+DWDisplayTitleSubtitle.h | 6 +- ...lockchainIdentity+DWDisplayTitleSubtitle.m | 59 + .../Models}/DWDPUpdateProfileModel.h | 0 .../Models}/DWDPUpdateProfileModel.m | 0 .../DWDWUserProfileModalQRContentView.h | 46 + .../DWUserProfileModalQRContentView.h | 46 + .../DWUserProfileModalQRContentView.m | 179 + .../DWUserProfileModalQRViewController.h | 13 +- .../DWUserProfileModalQRViewController.m | 102 + .../Shared/Autolayout/NSArray+DWFlatten.h | 28 + .../Shared/Autolayout/NSArray+DWFlatten.m | 47 + .../NSLayoutConstraint+DWAutolayout.h | 28 + .../NSLayoutConstraint+DWAutolayout.m | 17 +- .../Shared/Autolayout/UIView+DWAutolayout.h | 82 + .../Shared/Autolayout/UIView+DWAutolayout.m | 119 + .../Shared/Buttons/DWColoredButton.h | 15 +- .../Shared/Buttons/DWColoredButton.m | 45 +- .../Shared/DPAlert/DPAlertChildContentsView.h | 30 + .../Shared/DPAlert/DPAlertChildContentsView.m | 113 + .../Shared/DPAlert/DPAlertViewController.h | 19 +- .../Shared/DPAlert/DPAlertViewController.m | 104 + .../Presentation/Shared/DWDPAvatarView.h | 19 +- DashPay/Presentation/Shared/DWDPAvatarView.m | 184 + .../Shared/DWScrollingViewController.h | 16 +- .../Shared/DWScrollingViewController.m | 120 + .../Shared/UIImageView+DWDPAvatar.h | 4 +- .../Shared/UIImageView+DWDPAvatar.m | 136 + .../DWTxDetailPopupViewController.h | 46 + .../DWTxDetailPopupViewController.m | 119 + DashWallet.xcodeproj/project.pbxproj | 3278 ++++++++++------- DashWallet/AppDelegate.m | 3 + DashWallet/Sources/Models/DWGlobalOptions.h | 16 +- DashWallet/Sources/Models/DWGlobalOptions.m | 19 +- .../Contacts/Base/DWSearchViewController.m | 186 - ...ontactsContentViewController+DWProtected.h | 43 - .../DWBaseContactsContentViewController.h | 46 - .../DWBaseContactsContentViewController.m | 358 -- ...DWBaseContactsViewController+DWProtected.h | 40 - .../Contacts/DWBaseContactsViewController.m | 141 - .../DWContactsContentViewController.m | 42 - ... => DWContactsPlaceholderViewController.h} | 11 +- .../DWContactsPlaceholderViewController.m | 123 + .../Contacts/DWContactsViewController.m | 130 - ...oller.h => DWRootContactsViewController.h} | 10 +- .../Contacts/DWRootContactsViewController.m | 122 + .../Children/DWSearchStateViewController.h | 43 - .../Children/DWSearchStateViewController.m | 388 -- .../DWUserSearchResultViewController.h | 50 - .../DWUserSearchResultViewController.m | 118 - .../GlobalSearch/DWUserSearchViewController.m | 187 - .../GlobalSearch/Model/DWUserSearchModel.h | 52 - .../GlobalSearch/Model/DWUserSearchModel.m | 202 - .../Models/DWBaseContactsModel+DWProtected.h | 45 - .../Contacts/Models/DWBaseContactsModel.h | 52 - .../Contacts/Models/DWBaseContactsModel.m | 147 - .../DashPay/Contacts/Models/DWContactsModel.m | 61 - .../Models/DWContactsSortModeProtocol.h | 32 - .../Models/DataSource/DWContactsDataSource.h | 41 - .../DataSource/DWContactsDataSourceObject.h | 37 - .../DataSource/DWContactsDataSourceObject.m | 91 - .../DWContactsSearchDataSourceObject.h | 39 - .../DWContactsSearchDataSourceObject.m | 105 - .../DWContactsFetchedDataSource.m | 49 - .../DWFetchedResultsDataSource.h | 55 - .../DWFetchedResultsDataSource.m | 194 - .../DWIncomingFetchedDataSource.m | 58 - .../Placeholders/DWInvitationSuggestionView.h | 28 + .../Placeholders/DWInvitationSuggestionView.m | 70 + .../Placeholders/DWNoContactsViewController.h | 29 + .../Placeholders/DWNoContactsViewController.m | 100 + .../Requests/DWRequestsViewController.m | 70 - .../Requests/Models/DWRequestsModel.h | 33 - .../Views/BaseCollectionReusableView.h} | 7 +- .../Views/BaseCollectionReusableView.m} | 8 +- .../Views/DWContactsSearchPlaceholderView.h} | 17 +- .../Views/DWContactsSearchPlaceholderView.m | 127 + .../Views/DWGlobalMatchFailedHeaderView.h} | 4 +- .../Views/DWGlobalMatchFailedHeaderView.m | 48 + .../DWGlobalMatchHeaderView.h} | 8 +- .../Contacts/Views/DWGlobalMatchHeaderView.m | 110 + .../Contacts/Views/DWTitleActionHeaderView.m | 72 - .../Sources/UI/DashPay/DWDashPayConstants.m | 25 - .../DWNetworkErrorViewController.h} | 18 +- .../Error/DWNetworkErrorViewController.m | 107 + .../DSBlockchainIdentity+DWDisplayName.h | 28 + .../DSBlockchainIdentity+DWDisplayName.m} | 18 +- .../DashPay/Global/DWDashPayContactsActions.h | 34 - .../DashPay/Global/DWDashPayContactsActions.m | 96 - .../DashPay/Global/DWDashPayContactsUpdater.h | 43 - .../DashPay/Global/DWDashPayContactsUpdater.m | 138 - .../Notifications/DWNotificationsData.h | 37 - .../Notifications/DWNotificationsData.m | 45 - .../DWNotificationsFetchedDataSource.h | 35 - .../DWNotificationsFetchedDataSource.m | 48 - .../Notifications/DWNotificationsProvider.m | 209 -- .../UIImageView+DWDPAvatar.h} | 4 +- .../DashPay/Global/UIImageView+DWDPAvatar.m | 136 + .../DPAlertViewController+DWInvite.h} | 8 +- .../DPAlertViewController+DWInvite.m | 42 + .../DWConfirmInvitationContentView.h} | 4 +- .../DWConfirmInvitationContentView.m} | 41 +- .../DWConfirmInvitationContentView.xib} | 14 +- .../DWConfirmInvitationViewController.h} | 17 +- .../DWConfirmInvitationViewController.m | 126 + .../DWSendInviteFirstStepViewController.h | 36 + .../DWSendInviteFirstStepViewController.m | 100 + .../Invites/DWSendInviteFlowController.h | 36 + .../Invites/DWSendInviteFlowController.m | 110 + .../DWInvitationHistoryViewController.h | 26 + .../DWInvitationHistoryViewController.m | 193 + .../Filter/DWHistoryFilterContentView.h} | 15 +- .../Filter/DWHistoryFilterContentView.m | 94 + .../Filter/DWHistoryFilterViewController.h | 39 + .../Filter/DWHistoryFilterViewController.m | 91 + .../Models/DWInvitationHistoryFilter.h | 27 + .../History/Models/DWInvitationHistoryModel.h | 56 + .../History/Models/DWInvitationHistoryModel.m | 157 + .../History/Models/DWInvitationItem.h} | 12 +- .../History/Views/DWCreateInvitationButton.h | 26 + .../History/Views/DWCreateInvitationButton.m | 98 + .../History/Views/DWHistoryHeaderView.h | 29 + .../History/Views/DWHistoryHeaderView.m | 84 + .../History/Views/DWInvitationTableViewCell.h | 30 + .../History/Views/DWInvitationTableViewCell.m | 126 + .../BaseInvitationViewController.swift | 256 ++ .../Invitation/DWInvitationLinkBuilder.h} | 11 +- .../Invitation/DWInvitationLinkBuilder.m | 82 + .../SuccessInvitationViewController.swift | 78 + .../Views/DWInvitationActionsView.h} | 18 +- .../Views/DWInvitationActionsView.m | 117 + .../Views/DWInvitationMessageView.h} | 4 +- .../Views/DWInvitationMessageView.m | 98 + .../Views/DWSuccessInvitationView.h | 33 + .../Views/DWSuccessInvitationView.m | 131 + .../Views/InvitationBottomView.swift | 96 + .../Invitation/Views/InvitationTopView.swift | 141 + .../Views/SuccessInvitationTopView.swift | 69 + .../DWInvitationPreviewViewController.h | 26 + .../DWInvitationPreviewViewController.m | 163 + .../Items/Factory/DWDPContactsItemsFactory.m | 52 - .../Items/Factory/DWDPSearchItemsFactory.m | 47 - .../DashPay/Items/Objects/DWDPContactObject.m | 71 - .../Objects/DWDPEstablishedContactObject.h | 32 - .../Items/Objects/DWDPIncomingRequestObject.h | 38 - .../Items/Objects/DWDPIncomingRequestObject.m | 74 - .../Objects/DWDPNewIncomingRequestObject.h | 28 - .../Items/Objects/DWDPPendingRequestObject.h | 27 - .../DWDPRespondedIncomingRequestObject.h | 27 - .../DWDPRespondedIncomingRequestObject.m | 24 - .../UI/DashPay/Items/Objects/DWDPTxObject.h | 36 - .../UI/DashPay/Items/Objects/DWDPTxObject.m | 88 - .../UI/DashPay/Items/Objects/DWDPUserObject.h | 39 - .../UI/DashPay/Items/Objects/DWDPUserObject.m | 81 - .../DWDPAcceptedRequestNotificationObject.h | 35 - .../DWDPAcceptedRequestNotificationObject.m | 78 - ...DWDPEstablishedContactNotificationObject.h | 36 - ...DWDPEstablishedContactNotificationObject.m | 80 - ...DWDPNewIncomingRequestNotificationObject.h | 29 - ...DWDPNewIncomingRequestNotificationObject.m | 45 - .../DWDPOutgoingRequestNotificationObject.h | 35 - .../DWDPOutgoingRequestNotificationObject.m | 83 - .../Protocols/DWDPNewIncomingRequestItem.h | 43 - .../UI/DashPay/Items/Protocols/DWDPTxItem.h | 32 - .../DWDPFriendRequestBackedItem.h | 30 - .../DWDPGenericContactRequestItemView.h | 33 - .../DWDPGenericContactRequestItemView.m | 182 - .../ContentViews/DWDPGenericImageItemView.h | 28 - .../ContentViews/DWDPGenericImageItemView.m | 58 - .../Views/ContentViews/DWDPGenericItemView.m | 131 - .../ContentViews/DWDPGenericStatusItemView.h | 28 - .../ContentViews/DWDPGenericStatusItemView.m | 68 - .../Items/Views/ContentViews/DWDPTxItemView.m | 67 - .../UI/DashPay/Items/Views/DWDPBasicCell.h | 45 - .../UI/DashPay/Items/Views/DWDPBasicCell.m | 200 - .../DashPay/Items/Views/DWDPImageStatusCell.m | 49 - .../Items/Views/DWDPIncomingRequestCell.h | 31 - .../Items/Views/DWDPIncomingRequestCell.m | 75 - .../UI/DashPay/Items/Views/DWDPTxListCell.m | 49 - .../Views/UICollectionView+DWDPItemDequeue.m | 69 - .../UI/DashPay/Items/Views/UIFont+DWDPItem.m | 32 - .../Cells/DWNoNotificationsCell.m | 94 - .../Cells/DWNotificationsInvitationCell.h | 37 + .../Cells/DWNotificationsInvitationCell.m | 124 + .../Notifications/DWListCollectionLayout.h | 29 - .../Notifications/DWListCollectionLayout.m | 39 - .../DWNotificationsViewController.m | 252 -- .../Models/DWNotificationsModel.h | 46 - .../Models/DWNotificationsModel.m | 92 - .../DWModalUserProfileViewController.m | 66 - .../Profile/DWUserProfileViewController.h | 42 - .../Profile/DWUserProfileViewController.m | 392 -- .../Profile/Model/DWUserProfileModel.h | 71 - .../Profile/Model/DWUserProfileModel.m | 237 -- .../DWProfileTxsFetchedDataSource.h | 34 - .../DWProfileTxsFetchedDataSource.m | 85 - .../DataSource/DWUserProfileDataSource.h | 32 - .../DWUserProfileDataSourceObject.h | 41 - .../DWUserProfileDataSourceObject.m | 216 -- .../Profile/Views/DWPendingContactInfoView.h | 29 + .../Profile/Views/DWPendingContactInfoView.m | 111 + .../DWStretchyHeaderListCollectionLayout.h | 26 - .../DWStretchyHeaderListCollectionLayout.m | 68 - .../Views/DWUserProfileContactActionsCell.m | 219 -- .../Profile/Views/DWUserProfileHeaderView.h | 41 - .../Profile/Views/DWUserProfileHeaderView.m | 314 -- .../Views/DWUserProfileNavigationTitleView.m | 104 - .../Views/DWUserProfileSendRequestCell.m | 336 -- .../DWConfirmUsernameViewController.m | 75 - .../DWCreateUsernameViewController.m | 139 - .../DWInputUsernameViewController.m | 277 -- ...WAllowedCharactersUsernameValidationRule.h | 26 - ...WAllowedCharactersUsernameValidationRule.m | 57 - .../DWCheckExistenceUsernameValidationRule.m | 121 - ... => DWFirstUsernameSymbolValidationRule.h} | 4 +- .../DWFirstUsernameSymbolValidationRule.m | 44 + ...ule.h => DWLengthUsernameValidationRule.h} | 2 +- ...ule.m => DWLengthUsernameValidationRule.m} | 12 +- .../DWMaxLengthUsernameValidationRule.m | 33 - .../DWUsernameValidationRule+Protected.h | 28 - .../Models/DWUsernameValidationRule.h | 48 - .../Views/DWPlanetarySystemView.h | 62 - .../Views/DWPlanetarySystemView.m | 241 -- .../Setup/CreateUsername/Views/DWTextField.m | 37 - .../Views/DWUsernameHeaderView.m | 338 -- .../Views/DWUsernameValidationView.m | 139 - .../Setup/DWDashPaySetupFlowController.m | 316 -- .../UI/DashPay/Setup/DWDashPaySetupModel.h | 26 + .../UI/DashPay/Setup/DWDashPaySetupModel.m | 71 + .../DWRegistrationCompletedViewController.m | 156 - .../DWUsernamePendingViewController.m | 205 -- .../Sources/UI/DashPay/Views/DWDPAvatarView.h | 9 +- .../Sources/UI/DashPay/Views/DWDPAvatarView.m | 65 + .../UI/DashPay/Views/DWDPSmallContactView.m | 96 - .../UI/DashPay/Views/DWDashPayAnimationView.m | 368 -- .../DWNetworkUnavailableView.h} | 5 +- .../DashPay/Views/DWNetworkUnavailableView.m | 89 + .../UI/DashPay/Views/UIColor+DWDashPay.m | 40 - .../DWDPWelcomeCollectionViewController.h | 29 + .../DWDPWelcomeCollectionViewController.m | 228 ++ .../Welcome/DWDPWelcomePageViewController.h | 28 + .../Welcome/DWDPWelcomePageViewController.m | 134 + .../Welcome/DWDPWelcomeViewController.h | 38 + .../Welcome/DWDPWelcomeViewController.m | 73 + .../Welcome/DWInvitationFlowViewController.h | 38 + .../Welcome/DWInvitationFlowViewController.m | 82 + .../GetStarted/DWGetStarted.h} | 16 +- .../DWGetStartedContentViewController.h | 32 + .../DWGetStartedContentViewController.m | 171 + .../GetStarted/DWGetStartedViewController.h | 42 + .../GetStarted/DWGetStartedViewController.m | 68 + .../GetStarted/Views/DWGetStartedItemView.h | 36 + .../GetStarted/Views/DWGetStartedItemView.m | 173 + .../Views/DWPassthroughStackView.h} | 6 +- .../Welcome/Views/DWPassthroughStackView.m | 27 + .../DashPay/Welcome/Views/DWPassthroughView.h | 26 + .../Views/DWPassthroughView.m} | 13 +- .../Sources/UI/Home/DWHomeViewController.m | 6 +- .../UI/Home/Views/Cells/DWFilterHeaderView.h | 6 +- .../UI/Home/Views/Cells/DWFilterHeaderView.m | 6 + .../UI/Menu/Main/DWMainMenuViewController.m | 17 +- .../Menu/Main/Views/DWMainMenuContentView.h | 4 + .../Menu/Main/Views/DWMainMenuContentView.m | 20 + .../UI/Onboarding/Stubs/DWHomeModelStub.m | 2 +- .../UI/Payments/Pay/DWBasePayViewController.m | 2 +- .../UI/Tx/Details/Model/TxDetailModel.swift | 2 +- .../Tx/Details/TxDetailViewController.swift | 2 +- DashWallet/Sources/UI/Views/DWUIKit.h | 3 + .../UI/Views/SharedViews/Buttons/DWButton.h | 2 + .../UI/Views/SharedViews/Buttons/DWButton.m | 8 + .../Popup/DWModalPopupTransition.h | 2 + .../Popup/DWModalPopupTransition.m | 5 + DashWallet/Sources/UI/Views/UIColor+DWStyle.h | 8 + DashWallet/Sources/UI/Views/UIColor+DWStyle.m | 24 + DashWallet/dashwallet-Bridging-Header.h | 7 + Podfile | 2 + Podfile.lock | 10 +- 360 files changed, 15068 insertions(+), 13690 deletions(-) create mode 100644 DashPay/Assets/DPAssets.xcassets/icon_info.imageset/Contents.json create mode 100644 DashPay/Assets/DPAssets.xcassets/icon_info.imageset/icon_info.png create mode 100644 DashPay/Assets/DPAssets.xcassets/icon_info.imageset/icon_info@2x.png create mode 100644 DashPay/Assets/DPAssets.xcassets/icon_info.imageset/icon_info@3x.png create mode 100644 DashPay/Presentation/Containers/DWFullScreenModalControllerViewController.h create mode 100644 DashPay/Presentation/Containers/DWFullScreenModalControllerViewController.m rename DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.xib => DashPay/Presentation/Filter View/DWFilterHeaderView.xib (72%) rename DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPDashpayUserBackedItem.h => DashPay/Presentation/Home/DWDashPayReadyProtocol.h (73%) rename DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.h => DashPay/Presentation/InfoPopup/DWInfoPopupContentView.h (82%) create mode 100644 DashPay/Presentation/InfoPopup/DWInfoPopupContentView.m rename DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.h => DashPay/Presentation/InfoPopup/DWInfoPopupViewController.h (70%) create mode 100644 DashPay/Presentation/InfoPopup/DWInfoPopupViewController.m create mode 100644 DashPay/Presentation/Menu/DWMainMenuViewController+DashPay.h create mode 100644 DashPay/Presentation/Menu/DWMainMenuViewController+DashPay.m create mode 100644 DashPay/Presentation/Profile/EditProfile/Avatar/DWAvatarEditSelectorViewController.h create mode 100644 DashPay/Presentation/Profile/EditProfile/Avatar/DWAvatarEditSelectorViewController.m rename DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.h => DashPay/Presentation/Profile/EditProfile/Avatar/Views/DWAvatarEditSelectorContentView.h (50%) create mode 100644 DashPay/Presentation/Profile/EditProfile/Avatar/Views/DWAvatarEditSelectorContentView.m rename DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.h => DashPay/Presentation/Profile/EditProfile/DWCropAvatarViewController.h (53%) create mode 100644 DashPay/Presentation/Profile/EditProfile/DWCropAvatarViewController.m create mode 100644 DashPay/Presentation/Profile/EditProfile/DWEditProfileViewController.h create mode 100644 DashPay/Presentation/Profile/EditProfile/DWEditProfileViewController.m rename DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.h => DashPay/Presentation/Profile/EditProfile/DWRootEditProfileViewController.h (51%) create mode 100644 DashPay/Presentation/Profile/EditProfile/DWRootEditProfileViewController.m rename DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPIncomingRequestItem.h => DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarGravatarViewController.h (85%) create mode 100644 DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarGravatarViewController.m create mode 100644 DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarPublicURLViewController.h create mode 100644 DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarPublicURLViewController.m create mode 100644 DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/DWExternalSourceViewController.h create mode 100644 DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/DWExternalSourceViewController.m rename DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.h => DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalLoadingView.h (69%) create mode 100644 DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalLoadingView.m rename DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.h => DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceConfig.h (67%) rename DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.m => DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceConfig.m (85%) rename DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.h => DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceView.h (53%) create mode 100644 DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceView.m create mode 100644 DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoChildView.h create mode 100644 DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoChildView.m create mode 100644 DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoViewController.h create mode 100644 DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoViewController.m create mode 100644 DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurItemView.h rename DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.m => DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurItemView.m (50%) create mode 100644 DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertChildView.h create mode 100644 DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertChildView.m create mode 100644 DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertViewController.h create mode 100644 DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertViewController.m rename DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.h => DashPay/Presentation/Profile/EditProfile/Upload/DWHourGlassAnimationView.h (94%) create mode 100644 DashPay/Presentation/Profile/EditProfile/Upload/DWHourGlassAnimationView.m create mode 100644 DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarChildView.h create mode 100644 DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarChildView.m rename DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.h => DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarModel.h (58%) create mode 100644 DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarModel.m rename DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWCreateUsernameViewController.h => DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarViewController.h (63%) create mode 100644 DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarViewController.m rename DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.h => DashPay/Presentation/Profile/EditProfile/Utils/DWFaceDetector.h (79%) create mode 100644 DashPay/Presentation/Profile/EditProfile/Utils/DWFaceDetector.m create mode 100644 DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextFieldFormCellModel.h create mode 100644 DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextFieldFormCellModel.m create mode 100644 DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextInputFormTableViewCell.h create mode 100644 DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextViewFormCellModel.h rename DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.h => DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextViewFormCellModel.m (82%) rename DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPRespondedRequestItem.h => DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileAboutCellModel.h (87%) rename DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.m => DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileAboutCellModel.m (54%) rename DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPPendingRequestItem.h => DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileDisplayNameCellModel.h (86%) rename DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.m => DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileDisplayNameCellModel.m (54%) rename DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWInputUsernameViewController.h => DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileAvatarView.h (60%) create mode 100644 DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileAvatarView.m rename DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.h => DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileBaseCell.h (57%) create mode 100644 DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileBaseCell.m rename DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.h => DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextFieldCell.h (57%) create mode 100644 DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextFieldCell.m rename DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.m => DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextViewCell.h (71%) create mode 100644 DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextViewCell.m rename DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.h => DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.h (61%) create mode 100644 DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.m rename DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.h => DashPay/Presentation/Profile/UserProfile/DWDPWelcomeMenuView.h (87%) create mode 100644 DashPay/Presentation/Profile/UserProfile/DWDPWelcomeMenuView.m rename DashWallet/Sources/UI/DashPay/Setup/UsernamePending/DWUsernamePendingViewController.h => DashPay/Presentation/Profile/UserProfile/DWErrorUpdatingUserProfileView.h (61%) create mode 100644 DashPay/Presentation/Profile/UserProfile/DWErrorUpdatingUserProfileView.m rename DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWTextField.h => DashPay/Presentation/Profile/UserProfile/DWUpdatingUserProfileView.h (93%) create mode 100644 DashPay/Presentation/Profile/UserProfile/DWUpdatingUserProfileView.m rename DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicUserItem.h => DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.h (68%) create mode 100644 DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.m rename DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNotificationItem.h => DashPay/Presentation/Profile/UserProfile/Models/DSBlockchainIdentity+DWDisplayTitleSubtitle.h (83%) create mode 100644 DashPay/Presentation/Profile/UserProfile/Models/DSBlockchainIdentity+DWDisplayTitleSubtitle.m rename DashPay/Presentation/{Home/Model => Profile/UserProfile/Models}/DWDPUpdateProfileModel.h (100%) rename DashPay/Presentation/{Home/Model => Profile/UserProfile/Models}/DWDPUpdateProfileModel.m (100%) create mode 100644 DashPay/Presentation/Profile/UserProfileModalQR/DWDWUserProfileModalQRContentView.h create mode 100644 DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRContentView.h create mode 100644 DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRContentView.m rename DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameValidationView.h => DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRViewController.h (72%) create mode 100644 DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRViewController.m create mode 100644 DashPay/Presentation/Shared/Autolayout/NSArray+DWFlatten.h create mode 100644 DashPay/Presentation/Shared/Autolayout/NSArray+DWFlatten.m create mode 100644 DashPay/Presentation/Shared/Autolayout/NSLayoutConstraint+DWAutolayout.h rename DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.h => DashPay/Presentation/Shared/Autolayout/NSLayoutConstraint+DWAutolayout.m (63%) create mode 100644 DashPay/Presentation/Shared/Autolayout/UIView+DWAutolayout.h create mode 100644 DashPay/Presentation/Shared/Autolayout/UIView+DWAutolayout.m rename DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.h => DashPay/Presentation/Shared/Buttons/DWColoredButton.h (69%) rename DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.m => DashPay/Presentation/Shared/Buttons/DWColoredButton.m (51%) create mode 100644 DashPay/Presentation/Shared/DPAlert/DPAlertChildContentsView.h create mode 100644 DashPay/Presentation/Shared/DPAlert/DPAlertChildContentsView.m rename DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameHeaderView.h => DashPay/Presentation/Shared/DPAlert/DPAlertViewController.h (63%) create mode 100644 DashPay/Presentation/Shared/DPAlert/DPAlertViewController.m rename DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.h => DashPay/Presentation/Shared/DWDPAvatarView.h (57%) create mode 100644 DashPay/Presentation/Shared/DWDPAvatarView.m rename DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.h => DashPay/Presentation/Shared/DWScrollingViewController.h (55%) create mode 100644 DashPay/Presentation/Shared/DWScrollingViewController.m rename DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.h => DashPay/Presentation/Shared/UIImageView+DWDPAvatar.h (82%) create mode 100644 DashPay/Presentation/Shared/UIImageView+DWDPAvatar.m create mode 100644 DashPay/Presentation/Tx Details/DWTxDetailPopupViewController.h create mode 100644 DashPay/Presentation/Tx Details/DWTxDetailPopupViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController+DWProtected.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController+DWProtected.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.m rename DashWallet/Sources/UI/DashPay/Contacts/{DWBaseContactsViewController.h => DWContactsPlaceholderViewController.h} (72%) create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWContactsPlaceholderViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.m rename DashWallet/Sources/UI/DashPay/Contacts/{GlobalSearch/DWUserSearchViewController.h => DWRootContactsViewController.h} (75%) create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel+DWProtected.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsSortModeProtocol.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSource.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.h delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWInvitationSuggestionView.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWInvitationSuggestionView.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.h rename DashWallet/Sources/UI/DashPay/{Items/Protocols/Items Associated Data/DWDPBlockchainIdentityBackedItem.h => Contacts/Views/BaseCollectionReusableView.h} (81%) rename DashWallet/Sources/UI/DashPay/{Items/Views/DWDPTxListCell.h => Contacts/Views/BaseCollectionReusableView.m} (84%) rename DashWallet/Sources/UI/DashPay/{Items/Protocols/DWDPBasicItem.h => Contacts/Views/DWContactsSearchPlaceholderView.h} (59%) create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchPlaceholderView.m rename DashWallet/Sources/UI/DashPay/{Items/Protocols/DWDPEstablishedContactItem.h => Contacts/Views/DWGlobalMatchFailedHeaderView.h} (86%) create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchFailedHeaderView.m rename DashWallet/Sources/UI/DashPay/Contacts/{Models/DWContactsModel.h => Views/DWGlobalMatchHeaderView.h} (80%) create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchHeaderView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.m delete mode 100644 DashWallet/Sources/UI/DashPay/DWDashPayConstants.m rename DashWallet/Sources/UI/DashPay/{Contacts/Requests/DWRequestsViewController.h => Error/DWNetworkErrorViewController.h} (68%) create mode 100644 DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Global/DSBlockchainIdentity+DWDisplayName.h rename DashWallet/Sources/UI/DashPay/{Setup/CreateUsername/Models/DWUsernameValidationRule.m => Global/DSBlockchainIdentity+DWDisplayName.m} (69%) delete mode 100644 DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.h delete mode 100644 DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.m delete mode 100644 DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.h delete mode 100644 DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.m delete mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.h delete mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.m delete mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.h delete mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.m delete mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.m rename DashWallet/Sources/UI/DashPay/{Notifications/Cells/DWNoNotificationsCell.h => Global/UIImageView+DWDPAvatar.h} (82%) create mode 100644 DashWallet/Sources/UI/DashPay/Global/UIImageView+DWDPAvatar.m rename DashWallet/Sources/UI/DashPay/{Items/Views/ContentViews/DWDPTxItemView.h => Invites/Additions/DPAlertViewController+DWInvite.h} (75%) create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Additions/DPAlertViewController+DWInvite.m rename DashWallet/Sources/UI/DashPay/{Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.h => Invites/Confirmation/DWConfirmInvitationContentView.h} (88%) rename DashWallet/Sources/UI/DashPay/{Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.m => Invites/Confirmation/DWConfirmInvitationContentView.m} (77%) rename DashWallet/Sources/UI/DashPay/{Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.xib => Invites/Confirmation/DWConfirmInvitationContentView.xib} (95%) rename DashWallet/Sources/UI/DashPay/{Setup/ConfirmUsername/DWConfirmUsernameViewController.h => Invites/Confirmation/DWConfirmInvitationViewController.h} (58%) create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFirstStepViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFirstStepViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/DWInvitationHistoryViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/DWInvitationHistoryViewController.m rename DashWallet/Sources/UI/DashPay/{Setup/RegistrationCompleted/DWRegistrationCompletedViewController.h => Invites/History/Filter/DWHistoryFilterContentView.h} (60%) create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterContentView.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryFilter.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryModel.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryModel.m rename DashWallet/Sources/UI/DashPay/{DWDashPayConstants.h => Invites/History/Models/DWInvitationItem.h} (77%) create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Views/DWHistoryHeaderView.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Views/DWHistoryHeaderView.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Views/DWInvitationTableViewCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/History/Views/DWInvitationTableViewCell.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/BaseInvitationViewController.swift rename DashWallet/Sources/UI/DashPay/{Items/Factory/DWDPSearchItemsFactory.h => Invites/Invitation/DWInvitationLinkBuilder.h} (69%) create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/DWInvitationLinkBuilder.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/SuccessInvitationViewController.swift rename DashWallet/Sources/UI/DashPay/{Contacts/Views/DWTitleActionHeaderView.h => Invites/Invitation/Views/DWInvitationActionsView.h} (57%) create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationActionsView.m rename DashWallet/Sources/UI/DashPay/{Notifications/DWNotificationsViewController.h => Invites/Invitation/Views/DWInvitationMessageView.h} (84%) create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationMessageView.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.m create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/InvitationBottomView.swift create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/InvitationTopView.swift create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/SuccessInvitationTopView.swift create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Preview/DWInvitationPreviewViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Invites/Preview/DWInvitationPreviewViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNewIncomingRequestItem.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPTxItem.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPFriendRequestBackedItem.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.h delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.m delete mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.m delete mode 100644 DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.m create mode 100644 DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.m delete mode 100644 DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.h delete mode 100644 DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.m delete mode 100644 DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.h delete mode 100644 DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.h delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.h delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.h delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSource.h delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.h delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWPendingContactInfoView.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWPendingContactInfoView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.h delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.h delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/DWConfirmUsernameViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWCreateUsernameViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWInputUsernameViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.h delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.m rename DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/{DWMinLengthUsernameValidationRule.h => DWFirstUsernameSymbolValidationRule.h} (83%) create mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m rename DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/{DWMaxLengthUsernameValidationRule.h => DWLengthUsernameValidationRule.h} (91%) rename DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/{DWMinLengthUsernameValidationRule.m => DWLengthUsernameValidationRule.m} (63%) delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMaxLengthUsernameValidationRule.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule+Protected.h delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule.h delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWPlanetarySystemView.h delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWPlanetarySystemView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWTextField.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameHeaderView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameValidationView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.m create mode 100644 DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupModel.h create mode 100644 DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupModel.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/UsernamePending/DWUsernamePendingViewController.m delete mode 100644 DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.m rename DashWallet/Sources/UI/DashPay/{Items/Views/UIFont+DWDPItem.h => Views/DWNetworkUnavailableView.h} (87%) create mode 100644 DashWallet/Sources/UI/DashPay/Views/DWNetworkUnavailableView.m delete mode 100644 DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.m create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeCollectionViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeCollectionViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomePageViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomePageViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.m rename DashWallet/Sources/UI/DashPay/{Contacts/Requests/DWRequestsContentViewController.h => Welcome/GetStarted/DWGetStarted.h} (69%) create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedContentViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedContentViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/GetStarted/Views/DWGetStartedItemView.h create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/GetStarted/Views/DWGetStartedItemView.m rename DashWallet/Sources/UI/DashPay/{Items/Views/DWDPTextStatusCell.h => Welcome/Views/DWPassthroughStackView.h} (82%) create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughStackView.m create mode 100644 DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughView.h rename DashWallet/Sources/UI/DashPay/{Items/Objects/DWDPPendingRequestObject.m => Welcome/Views/DWPassthroughView.m} (67%) diff --git a/DashPay/Assets/DPAssets.xcassets/icon_info.imageset/Contents.json b/DashPay/Assets/DPAssets.xcassets/icon_info.imageset/Contents.json new file mode 100644 index 000000000..5cf97ee01 --- /dev/null +++ b/DashPay/Assets/DPAssets.xcassets/icon_info.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon_info.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_info@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_info@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DashPay/Assets/DPAssets.xcassets/icon_info.imageset/icon_info.png b/DashPay/Assets/DPAssets.xcassets/icon_info.imageset/icon_info.png new file mode 100644 index 0000000000000000000000000000000000000000..3c55b320d8ea6598dfd6753e5ad9c287ab0d3557 GIT binary patch literal 497 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VV{wqX6T`Z5GB1G~m(&Q)G+$o^ zEg+kNfw4W4fd!-lh^2s-fq{7eBLg##W(0{XV1mnvEMP{kK?*nB{qPs4EZWn>F~mY} zZRke7!wx*Pc0p_o4GGrAygWLl_Ub2S_tY@>@F;{n5{?ShGB&nOadP^@;9SloVrJAN zaJQrCM)^X{+>+4#J0%_#wPqhrmNo#VBS}c8k zvS60Ug86cXda82r{eJ$F5}J_p*y;Y%v#bjZxxzEsH*Y`PFtdD-+_|r+<;Fs94p}6; zU1DuMll$KB%LSbLf88rfM0c6xAAh1K9^`(>?jY}jnU;Ir>pZ$L^JAJf2w@=<6rAIdt>`asms=!`(tp9Q^%ZR)diPQmh5TT zXI!IsH(tBy+B&C=K+(g~@0>n7NL*~M3^x!bg1 z37el_SKK?G6))cX S=BYj?xIJC{T-G@yGywp}Q@*JH literal 0 HcmV?d00001 diff --git a/DashPay/Assets/DPAssets.xcassets/icon_info.imageset/icon_info@2x.png b/DashPay/Assets/DPAssets.xcassets/icon_info.imageset/icon_info@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..418d6e9597a6032851054b1e2abae0c87a73d5e6 GIT binary patch literal 941 zcmV;e15*5nP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91D4+uX1ONa40RR91C;$Ke0D9(TtN;K57fD1xR9FekSvzkNK@k4-?89*| z0)&X;vp`5mi9~@Y5u&05A}k6DIuPNZ5U4?-cRt(LzK4Ayo%YVRGxOc-Yv!{+Sb4cMgJ$D0q&N#mi&_f;mNF+{Lr5DC+JA3*^>zWj8;>CI7*ZAn zHQu$K5W5uV=Qv<4_L5O)gv_AgNn9=j4lme@@ zrv&Un?BLibe=ys2ee?`e_ZKTN4_SLhW-2{DAl?^Mi1JM=ih9s&X9PKNqXoy9D#|DO z4oD}!7xojS5a>99#|m8EZLZo8y9I~BDhWOaA!*@U^Di6^2yZ3(jUS0Dh+)2o zS9i{w)mxA^#A=_5c`F$PnC{D~o3XUSjUb;8_2xQ7FWM$dM)O(TL4aRP?IJ|Wn|VGn z!zYd#!Mbxl4shp+ChS`!*tbhKbDWTd6%Ld-Qt5D1Hl%YUtS!IZ#S=LknJ{pauJh%b z|5b>4b>7pd$XQ8)7Do&+J!xS1!eRVzfQ4^Av9nKO9YsuYHmg#mC`Np`n|ls!eA)5+ z67iPOIm6B8A{t6copMc@gOqH>BqtAL@OAE(x=IJV9W1%Z;2yL6PbmL!|9@S^N#G=GJW)21upN`l4H{yuzEUcLjUq^;VQNJX zK(1TUDum5YMRi&MQo(PuDt;=eQzvVTt+m}##h*HHF}VPlj^HFQEy7qB6Nw9uxe}%$ z(jttd#ou@bX6lP3CUTf7;nHth;+CEBx}swktwqv8#6*t3vvJ{5z5`^EH^dqh7ws#x z_w+a_UPgPaVzGQZID+jF)lWH57Vk( z(3D!RmV%Mf=K4DxGlAJ3Ab(1#DPSFIZWTqsEQBibQ$(WsLF)Sv{SEOK4XPo`R|-T= P00000NkvXXu0mjfLj8tK literal 0 HcmV?d00001 diff --git a/DashPay/Assets/DPAssets.xcassets/icon_info.imageset/icon_info@3x.png b/DashPay/Assets/DPAssets.xcassets/icon_info.imageset/icon_info@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..be49e6bcaf0def7a1dd1f3a5bb522de10097e236 GIT binary patch literal 1353 zcmV-P1-AN$P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91JfH&r1ONa40RR91JOBUy0E^%0TmS$Bvq?ljRA>d|n@vpAKorOSGptMe z0Q?BTqDK?sS&cCoKW-XLNIaO}MQ_B55m0ZE_;FGJgL*TXn8*z=8a2_g;jS$B ziJ}me-KpBDFcH*vn>RaI5d$<+Z5 zqz_Y5y-JY#s|{p%=Zn{n@-li-FPB%|S*kMNp>HXu%m9IQIjam}k9Z83>_bm$+N(G3 zz(eVyP`StCsKu)@pUeM-Z0MqFzwlD8^imjupZlQHb2_KqupD-aj)i0LV9U$!9itK1 z^gS5rw1>KbD=OpzBvRctC+3!Nx-{^ebStvz89iq9E>&4wj|=e@3E7Ersc&v^xHoB? z*$}`oAt!_r!!f&tz)b9gIkk$*0~8SjS@d+6^=dqj(6#KtAO8ICfg`iYi5{dD)gUszde<% z@A|%G4a-M2u9mp6x4Cpd|Mnh%$+!T_(=xu#RFsca7PaDeRlcl0M`#vCGv+hQb5H?V z|C?z{XH3c;vpHOO`=|Ea7Z?S2cJZ(JMII}iaH}ZT^^F4C)+aFbGmGhYqoO)Io|M5l zxgnvaQUiwNK&R#me3-0ZN9DX4NoJLL-q?9CntN!DJfSDa18Gs#HC#o{hL)r?1elsr z$OaJuEeOzam{$aXwS65)jGSy;bh`WD*MOS0+D?~QFMljLkM@RW=XPbT^F*68PuE19 z*22P8epi&giPh>kq6}(dle9|5OP294V&x3S2bfq1m=~ktV#Wjk)5?2nOpfu;GCsh> zN}U#ID&k_qDz$(F$i#p-!>m%PWmrHGbz??S2AKm7R?9@)78p%nmIgtqWg?1AY??7Y zr)BD(h9gSLSi^a(mziPS#X6{{nvKl@)}k-cK}F7p$tGyCfHkP>j~p)x*=$xcB~C{M zzEB5NFTt5DGG8`Hz}B~g78xun$}k;Zg&XkODiYc)S`gPP1prQ9mh-W~4I~=~t+C{R zReDZoq7#@QSm(Fm$1k3qY;T9It zYCMYB#oU2eyU23BFhDO#58d^a?oB#>rD7#6%|r9IR_BQu^qh8Y8TNB(x=p%8f literal 0 HcmV?d00001 diff --git a/DashPay/Presentation/Containers/DWFullScreenModalControllerViewController.h b/DashPay/Presentation/Containers/DWFullScreenModalControllerViewController.h new file mode 100644 index 000000000..4a44f584f --- /dev/null +++ b/DashPay/Presentation/Containers/DWFullScreenModalControllerViewController.h @@ -0,0 +1,44 @@ +// +// 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 DWFullScreenModalControllerViewController; + +@protocol DWFullScreenModalControllerViewControllerDelegate + +- (void)fullScreenModalControllerViewControllerDidCancel:(DWFullScreenModalControllerViewController *)controller; + +@end + +@interface DWFullScreenModalControllerViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +- (instancetype)initWithController:(UIViewController *)controller; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil + bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; +- (instancetype)initWithCoder:(nullable NSCoder *)coder NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Containers/DWFullScreenModalControllerViewController.m b/DashPay/Presentation/Containers/DWFullScreenModalControllerViewController.m new file mode 100644 index 000000000..dab274a32 --- /dev/null +++ b/DashPay/Presentation/Containers/DWFullScreenModalControllerViewController.m @@ -0,0 +1,104 @@ +// +// 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 "DWFullScreenModalControllerViewController.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWFullScreenModalControllerViewController () + +@property (readonly, nonatomic, strong) UIViewController *contentController; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWFullScreenModalControllerViewController + +- (instancetype)initWithController:(UIViewController *)controller { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _contentController = controller; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UIView *header = [[UIView alloc] init]; + header.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:header]; + + UILabel *label = [[UILabel alloc] init]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.textColor = [UIColor dw_darkTitleColor]; + label.textAlignment = NSTextAlignmentCenter; + label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + label.text = self.title; + label.adjustsFontForContentSizeCategory = YES; + [header addSubview:label]; + + UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeSystem]; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + [cancelButton setImage:[[UIImage imageNamed:@"payments_nav_cross"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] + forState:UIControlStateNormal]; + cancelButton.tintColor = [UIColor dw_darkTitleColor]; + [cancelButton addTarget:self action:@selector(cancelButtonAction) forControlEvents:UIControlEventTouchUpInside]; + [header addSubview:cancelButton]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:contentView]; + + [NSLayoutConstraint activateConstraints:@[ + [header.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], + [header.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor + constant:8], + [self.view.trailingAnchor constraintEqualToAnchor:header.trailingAnchor + constant:8], + [header.heightAnchor constraintEqualToConstant:44], + + [contentView.topAnchor constraintEqualToAnchor:header.bottomAnchor], + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [self.view.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + + [label.topAnchor constraintEqualToAnchor:header.topAnchor], + [label.leadingAnchor constraintEqualToAnchor:header.leadingAnchor + constant:44], + [header.bottomAnchor constraintEqualToAnchor:label.bottomAnchor], + + [cancelButton.topAnchor constraintEqualToAnchor:header.topAnchor], + [cancelButton.leadingAnchor constraintEqualToAnchor:label.trailingAnchor], + [cancelButton.trailingAnchor constraintEqualToAnchor:header.trailingAnchor], + [header.bottomAnchor constraintEqualToAnchor:cancelButton.bottomAnchor], + [cancelButton.widthAnchor constraintEqualToConstant:44], + ]]; + + [self dw_embedChild:self.contentController inContainer:contentView]; +} + +- (void)cancelButtonAction { + [self.delegate fullScreenModalControllerViewControllerDidCancel:self]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.xib b/DashPay/Presentation/Filter View/DWFilterHeaderView.xib similarity index 72% rename from DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.xib rename to DashPay/Presentation/Filter View/DWFilterHeaderView.xib index aaea31346..d68e3374d 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.xib +++ b/DashPay/Presentation/Filter View/DWFilterHeaderView.xib @@ -1,5 +1,5 @@ - + @@ -8,10 +8,11 @@ - + - + + @@ -24,18 +25,27 @@ + @@ -43,11 +53,13 @@ - + + - + + - + @@ -69,6 +81,8 @@ + + diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPDashpayUserBackedItem.h b/DashPay/Presentation/Home/DWDashPayReadyProtocol.h similarity index 73% rename from DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPDashpayUserBackedItem.h rename to DashPay/Presentation/Home/DWDashPayReadyProtocol.h index 4d5b3283b..16183feb4 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPDashpayUserBackedItem.h +++ b/DashPay/Presentation/Home/DWDashPayReadyProtocol.h @@ -19,11 +19,12 @@ NS_ASSUME_NONNULL_BEGIN -@class DSDashpayUserEntity; +extern NSNotificationName const DWDashPayAvailabilityStatusUpdatedNotification; -@protocol DWDPDashpayUserBackedItem +@protocol DWDashPayReadyProtocol -@property (readonly, nonatomic, strong) DSDashpayUserEntity *userEntity; +@property (readonly, nonatomic, assign) BOOL isDashPayReady; +@property (readonly, nonatomic, assign) BOOL isDashPayReadyMainSuggestion; @end diff --git a/DashPay/Presentation/Home/Views/DashPayProfileView.swift b/DashPay/Presentation/Home/Views/DashPayProfileView.swift index 12ede0c0a..9ba2c974f 100644 --- a/DashPay/Presentation/Home/Views/DashPayProfileView.swift +++ b/DashPay/Presentation/Home/Views/DashPayProfileView.swift @@ -31,7 +31,9 @@ class DashPayProfileView: UIControl { var username: String? { didSet { - avatarView.username = username + if let username { + avatarView.configure(withUsername: username) + } } } diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.h b/DashPay/Presentation/InfoPopup/DWInfoPopupContentView.h similarity index 82% rename from DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.h rename to DashPay/Presentation/InfoPopup/DWInfoPopupContentView.h index 40d398f62..3b97cfdb5 100644 --- a/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.h +++ b/DashPay/Presentation/InfoPopup/DWInfoPopupContentView.h @@ -17,13 +17,12 @@ #import -#import "DWDPBasicUserItem.h" - NS_ASSUME_NONNULL_BEGIN -@interface DWDPSmallContactView : UIView +@interface DWInfoPopupContentView : UIView -@property (nullable, nonatomic, strong) id item; +@property (nullable, copy, nonatomic) NSString *text; +@property (assign, nonatomic) CGPoint pointerOffset; @end diff --git a/DashPay/Presentation/InfoPopup/DWInfoPopupContentView.m b/DashPay/Presentation/InfoPopup/DWInfoPopupContentView.m new file mode 100644 index 000000000..524226186 --- /dev/null +++ b/DashPay/Presentation/InfoPopup/DWInfoPopupContentView.m @@ -0,0 +1,170 @@ +// +// 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 "DWInfoPopupContentView.h" + +#import "DWUIKit.h" + +@interface DWInfoPopupTextView : UIView + +@property (readonly, nonatomic, strong) UILabel *textLabel; + +@end + +@implementation DWInfoPopupTextView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + self.layer.cornerRadius = 8.0; + self.layer.masksToBounds = YES; + + UILabel *textLabel = [[UILabel alloc] init]; + textLabel.translatesAutoresizingMaskIntoConstraints = NO; + textLabel.textColor = [UIColor dw_darkTitleColor]; + textLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; + textLabel.numberOfLines = 0; + [self addSubview:textLabel]; + _textLabel = textLabel; + + UIImageView *crossImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"payments_nav_cross"]]; + crossImageView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:crossImageView]; + + [NSLayoutConstraint activateConstraints:@[ + [crossImageView.topAnchor constraintEqualToAnchor:self.topAnchor + constant:12.0], + [self.trailingAnchor constraintEqualToAnchor:crossImageView.trailingAnchor + constant:12.0], + + [textLabel.topAnchor constraintEqualToAnchor:self.topAnchor + constant:22.0], + [textLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:22.0], + [self.bottomAnchor constraintEqualToAnchor:textLabel.bottomAnchor + constant:22.0], + [self.trailingAnchor constraintEqualToAnchor:textLabel.trailingAnchor + constant:44.0], + ]]; + } + return self; +} + +@end + +#pragma mark - + +@interface DWInfoPopupContentView () + +@property (readonly, nonatomic, strong) UIImageView *iconImageView; +@property (readonly, nonatomic, strong) UIImageView *arrowImageView; +@property (readonly, nonatomic, strong) DWInfoPopupTextView *textView; + +@property (nonatomic, assign) BOOL isArrowRotated; + +@end + +@implementation DWInfoPopupContentView + +- (NSString *)text { + return self.textView.textLabel.text; +} + +- (void)setText:(NSString *)text { + self.textView.textLabel.text = text; + [self setNeedsLayout]; +} + +- (void)setPointerOffset:(CGPoint)pointerOffset { + _pointerOffset = pointerOffset; + [self setNeedsLayout]; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + + UIImageView *iconImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_info"]]; + [self addSubview:iconImageView]; + _iconImageView = iconImageView; + + UIImageView *arrowImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"info_triangle"]]; + [self addSubview:arrowImageView]; + _arrowImageView = arrowImageView; + + DWInfoPopupTextView *textView = [[DWInfoPopupTextView alloc] initWithFrame:CGRectMake(0, 0, 200, 80)]; + [self addSubview:textView]; + _textView = textView; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + const CGSize size = self.bounds.size; + const CGFloat padding = IS_IPAD ? 100.0 : 30.0; + const CGFloat contentWidth = size.width - padding * 2; + + const CGSize textSize = [self.textView systemLayoutSizeFittingSize:CGSizeMake(contentWidth, UILayoutFittingExpandedSize.height) + withHorizontalFittingPriority:UILayoutPriorityRequired + verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; + + const BOOL hideArrow = textSize.height > size.height / 2.0; + self.iconImageView.hidden = hideArrow; + self.arrowImageView.hidden = hideArrow; + + if (hideArrow) { + self.textView.frame = CGRectMake(padding, (size.height - textSize.height) / 2.0, textSize.width, textSize.height); + } + else { + const CGSize iconSize = self.iconImageView.image.size; + const CGSize arrowSize = self.arrowImageView.image.size; + const CGFloat spacing = 4.0; + + const CGFloat contentHeight = iconSize.height + spacing + arrowSize.height + textSize.height; + const BOOL rotated = (self.pointerOffset.y + contentHeight) > size.height; + if (rotated) { + self.arrowImageView.transform = CGAffineTransformMakeRotation(M_PI); + + CGFloat top = self.pointerOffset.y; + self.iconImageView.frame = CGRectMake(self.pointerOffset.x, top, iconSize.width, iconSize.height); + top -= iconSize.height + spacing; + + self.arrowImageView.frame = CGRectMake(self.pointerOffset.x, top, arrowSize.width, arrowSize.height); + + top -= textSize.height; + self.textView.frame = CGRectMake(padding, top, textSize.width, textSize.height); + } + else { + self.arrowImageView.transform = CGAffineTransformIdentity; + + CGFloat top = self.pointerOffset.y; + self.iconImageView.frame = CGRectMake(self.pointerOffset.x, top, iconSize.width, iconSize.height); + top += iconSize.height + spacing; + + self.arrowImageView.frame = CGRectMake(self.pointerOffset.x, top, arrowSize.width, arrowSize.height); + top += arrowSize.height; + + self.textView.frame = CGRectMake(padding, top, textSize.width, textSize.height); + } + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.h b/DashPay/Presentation/InfoPopup/DWInfoPopupViewController.h similarity index 70% rename from DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.h rename to DashPay/Presentation/InfoPopup/DWInfoPopupViewController.h index b044080cb..2d97bf4ff 100644 --- a/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.h +++ b/DashPay/Presentation/InfoPopup/DWInfoPopupViewController.h @@ -17,25 +17,17 @@ #import -#import "DWDPBasicUserItem.h" - NS_ASSUME_NONNULL_BEGIN -@protocol DWPayModelProtocol; -@protocol DWTransactionListDataProviderProtocol; - -@interface DWModalUserProfileViewController : UIViewController +@interface DWInfoPopupViewController : UIViewController -- (instancetype)initWithItem:(id)item - payModel:(id)payModel - dataProvider:(id)dataProvider NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithText:(NSString *)text offset:(CGPoint)offset; - (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 NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/InfoPopup/DWInfoPopupViewController.m b/DashPay/Presentation/InfoPopup/DWInfoPopupViewController.m new file mode 100644 index 000000000..b313042ee --- /dev/null +++ b/DashPay/Presentation/InfoPopup/DWInfoPopupViewController.m @@ -0,0 +1,73 @@ +// +// 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 "DWInfoPopupViewController.h" + +#import "DWInfoPopupContentView.h" + +@interface DWInfoPopupViewController () + +@property (readonly, nonatomic, copy) NSString *text; +@property (readonly, nonatomic, assign) CGPoint offset; + +@property (nonatomic, strong) DWInfoPopupContentView *contentView; + +@end + +@implementation DWInfoPopupViewController + +- (instancetype)initWithText:(NSString *)text offset:(CGPoint)offset { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _text = text; + _offset = offset; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.4]; + + DWInfoPopupContentView *contentView = [[DWInfoPopupContentView alloc] initWithFrame:CGRectZero]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.text = self.text; + contentView.pointerOffset = self.offset; + [self.view addSubview:contentView]; + self.contentView = contentView; + + [NSLayoutConstraint activateConstraints:@[ + [contentView.topAnchor constraintEqualToAnchor:self.view.topAnchor], + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [self.view.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + ]]; + + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)]; + [self.view addGestureRecognizer:tapRecognizer]; +} + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)tapAction { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/DashPay/Presentation/Menu/DWMainMenuViewController+DashPay.h b/DashPay/Presentation/Menu/DWMainMenuViewController+DashPay.h new file mode 100644 index 000000000..798c45290 --- /dev/null +++ b/DashPay/Presentation/Menu/DWMainMenuViewController+DashPay.h @@ -0,0 +1,26 @@ +// +// 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 "DWMainMenuViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWMainMenuViewController (DashPay) + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Menu/DWMainMenuViewController+DashPay.m b/DashPay/Presentation/Menu/DWMainMenuViewController+DashPay.m new file mode 100644 index 000000000..690ad9b93 --- /dev/null +++ b/DashPay/Presentation/Menu/DWMainMenuViewController+DashPay.m @@ -0,0 +1,40 @@ +// +// 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 "DWMainMenuViewController+DashPay.h" +#import "DWRootEditProfileViewController.h" +#import "DWMainMenuContentView.h" + +@implementation DWMainMenuViewController (DashPay) + +#pragma mark - DWRootEditProfileViewControllerDelegate + +- (void)editProfileViewController:(DWRootEditProfileViewController *)controller + updateDisplayName:(NSString *)rawDisplayName + aboutMe:(NSString *)rawAboutMe + avatarURLString:(nullable NSString *)avatarURLString { + DWMainMenuContentView *view = (DWMainMenuContentView *) self.view; + //TODO: DashPay + //[view.userModel.updateModel updateWithDisplayName:rawDisplayName aboutMe:rawAboutMe avatarURLString:avatarURLString]; + [controller dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)editProfileViewControllerDidCancel:(DWRootEditProfileViewController *)controller { + [controller dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/Avatar/DWAvatarEditSelectorViewController.h b/DashPay/Presentation/Profile/EditProfile/Avatar/DWAvatarEditSelectorViewController.h new file mode 100644 index 000000000..1a83f89e1 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Avatar/DWAvatarEditSelectorViewController.h @@ -0,0 +1,39 @@ +// +// 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 + +@class DWAvatarEditSelectorViewController; + +@protocol DWAvatarEditSelectorViewControllerDelegate + +- (void)avatarEditSelectorViewController:(DWAvatarEditSelectorViewController *)controller photoButtonAction:(UIButton *)sender; +- (void)avatarEditSelectorViewController:(DWAvatarEditSelectorViewController *)controller galleryButtonAction:(UIButton *)sender; +- (void)avatarEditSelectorViewController:(DWAvatarEditSelectorViewController *)controller gravatarButtonAction:(UIButton *)sender; +- (void)avatarEditSelectorViewController:(DWAvatarEditSelectorViewController *)controller urlButtonAction:(UIButton *)sender; + +@end + +@interface DWAvatarEditSelectorViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/Avatar/DWAvatarEditSelectorViewController.m b/DashPay/Presentation/Profile/EditProfile/Avatar/DWAvatarEditSelectorViewController.m new file mode 100644 index 000000000..a29502236 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Avatar/DWAvatarEditSelectorViewController.m @@ -0,0 +1,103 @@ +// +// 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 "DWAvatarEditSelectorViewController.h" + +#import "DWAvatarEditSelectorContentView.h" +#import "DWModalPopupTransition.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWAvatarEditSelectorViewController () + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWAvatarEditSelectorViewController + +- (instancetype)init { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _modalTransition = [[DWModalPopupTransition alloc] init]; + _modalTransition.appearanceStyle = DWModalPopupAppearanceStyle_Fullscreen; + + self.transitioningDelegate = self.modalTransition; + self.modalPresentationStyle = UIModalPresentationCustom; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognizerAction)]; + [self.view addGestureRecognizer:tapRecognizer]; + + DWAvatarEditSelectorContentView *contentView = [[DWAvatarEditSelectorContentView alloc] initWithFrame:CGRectZero]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.delegate = self; + [self.view addSubview:contentView]; + + UIView *overscroll = [[UIView alloc] init]; + overscroll.translatesAutoresizingMaskIntoConstraints = NO; + overscroll.backgroundColor = [UIColor dw_backgroundColor]; + [self.view addSubview:overscroll]; + + [NSLayoutConstraint activateConstraints:@[ + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + + [overscroll.topAnchor constraintEqualToAnchor:contentView.bottomAnchor + constant:-10], + [overscroll.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:overscroll.trailingAnchor], + [overscroll.heightAnchor constraintEqualToConstant:500], + ]]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (void)tapRecognizerAction { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - DWAvatarEditSelectorContentViewDelegate + +- (void)avatarEditSelectorContentView:(DWAvatarEditSelectorContentView *)view photoButtonAction:(UIButton *)sender { + [self.delegate avatarEditSelectorViewController:self photoButtonAction:sender]; +} + +- (void)avatarEditSelectorContentView:(DWAvatarEditSelectorContentView *)view galleryButtonAction:(UIButton *)sender { + [self.delegate avatarEditSelectorViewController:self galleryButtonAction:sender]; +} + +- (void)avatarEditSelectorContentView:(DWAvatarEditSelectorContentView *)view publicURLButtonAction:(UIButton *)sender { + [self.delegate avatarEditSelectorViewController:self urlButtonAction:sender]; +} + +- (void)avatarEditSelectorContentView:(DWAvatarEditSelectorContentView *)view gravatarButtonAction:(UIButton *)sender { + [self.delegate avatarEditSelectorViewController:self gravatarButtonAction:sender]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.h b/DashPay/Presentation/Profile/EditProfile/Avatar/Views/DWAvatarEditSelectorContentView.h similarity index 50% rename from DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.h rename to DashPay/Presentation/Profile/EditProfile/Avatar/Views/DWAvatarEditSelectorContentView.h index 39e0f0dd7..24bf3b589 100644 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.h +++ b/DashPay/Presentation/Profile/EditProfile/Avatar/Views/DWAvatarEditSelectorContentView.h @@ -19,22 +19,20 @@ NS_ASSUME_NONNULL_BEGIN -@class DWUserProfileModel; -@class DWUserProfileContactActionsCell; +@class DWAvatarEditSelectorContentView; -@protocol DWUserProfileContactActionsCellDelegate +@protocol DWAvatarEditSelectorContentViewDelegate -- (void)userProfileContactActionsCell:(DWUserProfileContactActionsCell *)cell mainButtonAction:(UIButton *)sender; -- (void)userProfileContactActionsCell:(DWUserProfileContactActionsCell *)cell secondaryButtonAction:(UIButton *)sender; +- (void)avatarEditSelectorContentView:(DWAvatarEditSelectorContentView *)view photoButtonAction:(UIButton *)sender; +- (void)avatarEditSelectorContentView:(DWAvatarEditSelectorContentView *)view galleryButtonAction:(UIButton *)sender; +- (void)avatarEditSelectorContentView:(DWAvatarEditSelectorContentView *)view publicURLButtonAction:(UIButton *)sender; +- (void)avatarEditSelectorContentView:(DWAvatarEditSelectorContentView *)view gravatarButtonAction:(UIButton *)sender; @end -@interface DWUserProfileContactActionsCell : UICollectionViewCell +@interface DWAvatarEditSelectorContentView : UIView -@property (nonatomic, assign) CGFloat contentWidth; - -@property (nullable, nonatomic, strong) DWUserProfileModel *model; -@property (nullable, nonatomic, weak) id delegate; +@property (nullable, nonatomic, weak) id delegate; @end diff --git a/DashPay/Presentation/Profile/EditProfile/Avatar/Views/DWAvatarEditSelectorContentView.m b/DashPay/Presentation/Profile/EditProfile/Avatar/Views/DWAvatarEditSelectorContentView.m new file mode 100644 index 000000000..1fa0824bb --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Avatar/Views/DWAvatarEditSelectorContentView.m @@ -0,0 +1,128 @@ +// +// 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 "DWAvatarEditSelectorContentView.h" + +#import "DWPressableButton.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWAvatarEditSelectorContentView () + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWAvatarEditSelectorContentView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + self.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner; + self.layer.cornerRadius = 8.0; + self.layer.masksToBounds = YES; + + UIButton *gravatarButton = [self.class button]; + [gravatarButton setImage:[UIImage imageNamed:@"dp_avatar_gravatar"] forState:UIControlStateNormal]; + [gravatarButton setTitle:@"Gravatar" forState:UIControlStateNormal]; + [gravatarButton addTarget:self action:@selector(gravatarButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + + UIButton *urlButton = [self.class button]; + [urlButton setImage:[UIImage imageNamed:@"dp_avatar_url"] forState:UIControlStateNormal]; + [urlButton setTitle:NSLocalizedString(@"Public URL", nil) forState:UIControlStateNormal]; + [urlButton addTarget:self action:@selector(urlButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + + UIButton *photoButton = [self.class button]; + [photoButton setImage:[UIImage imageNamed:@"dp_avatar_photo"] forState:UIControlStateNormal]; + [photoButton setTitle:NSLocalizedString(@"Take a Photo from Camera", nil) forState:UIControlStateNormal]; + [photoButton addTarget:self action:@selector(photoButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + + UIButton *galleryButton = [self.class button]; + [galleryButton setImage:[UIImage imageNamed:@"dp_avatar_gallery"] forState:UIControlStateNormal]; + [galleryButton setTitle:NSLocalizedString(@"Select from Gallery", nil) forState:UIControlStateNormal]; + [galleryButton addTarget:self action:@selector(galleryButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + + UIView *separator1 = [[UIView alloc] init]; + separator1.translatesAutoresizingMaskIntoConstraints = NO; + separator1.backgroundColor = [UIColor dw_separatorLineColor]; + + UIView *separator2 = [[UIView alloc] init]; + separator2.translatesAutoresizingMaskIntoConstraints = NO; + separator2.backgroundColor = [UIColor dw_separatorLineColor]; + + UIView *separator3 = [[UIView alloc] init]; + separator3.translatesAutoresizingMaskIntoConstraints = NO; + separator3.backgroundColor = [UIColor dw_separatorLineColor]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ gravatarButton, separator1, urlButton, separator2, photoButton, separator3, galleryButton ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisVertical; + [self addSubview:stackView]; + + const CGFloat padding = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [stackView.topAnchor constraintEqualToAnchor:self.topAnchor], + [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor + constant:padding], + [self.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor], + + [photoButton.heightAnchor constraintGreaterThanOrEqualToConstant:80], + [galleryButton.heightAnchor constraintGreaterThanOrEqualToConstant:80], + [gravatarButton.heightAnchor constraintGreaterThanOrEqualToConstant:80], + [urlButton.heightAnchor constraintGreaterThanOrEqualToConstant:80], + + [separator1.heightAnchor constraintEqualToConstant:1], + [separator2.heightAnchor constraintEqualToConstant:1], + [separator3.heightAnchor constraintEqualToConstant:1], + ]]; + } + return self; +} + +- (void)photoButtonAction:(UIButton *)sender { + [self.delegate avatarEditSelectorContentView:self photoButtonAction:sender]; +} + +- (void)galleryButtonAction:(UIButton *)sender { + [self.delegate avatarEditSelectorContentView:self galleryButtonAction:sender]; +} + +- (void)gravatarButtonAction:(UIButton *)sender { + [self.delegate avatarEditSelectorContentView:self gravatarButtonAction:sender]; +} + +- (void)urlButtonAction:(UIButton *)sender { + [self.delegate avatarEditSelectorContentView:self publicURLButtonAction:sender]; +} + ++ (UIButton *)button { + DWPressableButton *button = [[DWPressableButton alloc] initWithFrame:CGRectZero]; + button.translatesAutoresizingMaskIntoConstraints = NO; + button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; + button.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; + button.adjustsImageWhenHighlighted = NO; + [button setTitleColor:[UIColor dw_darkTitleColor] forState:UIControlStateNormal]; + [button setInsetsForContentPadding:UIEdgeInsetsMake(20, 20, 20, 20) imageTitlePadding:30]; + return button; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.h b/DashPay/Presentation/Profile/EditProfile/DWCropAvatarViewController.h similarity index 53% rename from DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.h rename to DashPay/Presentation/Profile/EditProfile/DWCropAvatarViewController.h index f532d2205..399c76cc6 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.h +++ b/DashPay/Presentation/Profile/EditProfile/DWCropAvatarViewController.h @@ -17,31 +17,30 @@ #import -#import "DWDashPayProtocol.h" - NS_ASSUME_NONNULL_BEGIN -@class DWDashPaySetupFlowController; +@class DWCropAvatarViewController; -@protocol DWDashPaySetupFlowControllerDelegate +@protocol DWCropAvatarViewControllerDelegate -- (void)dashPaySetupFlowController:(DWDashPaySetupFlowController *)controller - didConfirmUsername:(NSString *)username; +- (void)cropAvatarViewController:(DWCropAvatarViewController *)controller + didCropImage:(UIImage *)croppedImage + urlString:(NSString *)urlString; +- (void)cropAvatarViewControllerDidCancel:(DWCropAvatarViewController *)controller; @end -@interface DWDashPaySetupFlowController : UIViewController +@interface DWCropAvatarViewController : UIViewController -- (instancetype)initWithDashPayModel:(id)dashPayModel - invitation:(nullable NSURL *)invitationURL - definedUsername:(nullable NSString *)definedUsername; +@property (nullable, nonatomic, weak) id delegate; -- (instancetype)initWithConfirmationDelegate:(id)delegate; +- (instancetype)initWithImage:(UIImage *)image imageURL:(nullable NSURL *)imageURL; -- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithCoder:(nullable NSCoder *)coder NS_UNAVAILABLE; +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil + bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; @end diff --git a/DashPay/Presentation/Profile/EditProfile/DWCropAvatarViewController.m b/DashPay/Presentation/Profile/EditProfile/DWCropAvatarViewController.m new file mode 100644 index 000000000..8e4d8436d --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/DWCropAvatarViewController.m @@ -0,0 +1,292 @@ +// +// 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 "DWCropAvatarViewController.h" + +#import +#import + +#import "DWActionButton.h" +#import "DWBaseActionButtonViewController.h" +#import "DWDPAvatarView.h" +#import "DWFaceDetector.h" +#import "DWImgurInfoViewController.h" +#import "DWUIKit.h" +#import "DWUploadAvatarViewController.h" +#import "dashwallet-Swift.h" + +@interface DWTOCropViewController : TOCropViewController +@end + +@implementation DWTOCropViewController + +- (CGRect)frameForToolbarWithVerticalLayout:(BOOL)verticalLayout { + return CGRectZero; +} + +- (CGRect)frameForCropViewWithVerticalLayout:(BOOL)verticalLayout { + return self.view.bounds; +} + +@end + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const PADDING = 38.0; + +@interface DWCropAvatarViewController () + +@property (nullable, nonatomic, strong) DWFaceDetector *faceDetector; + +@property (readonly, nonatomic, strong) TOCropViewController *cropController; +@property (null_resettable, nonatomic, strong) UILabel *titleLabel; +@property (null_resettable, nonatomic, strong) UIButton *selectButton; +@property (null_resettable, nonatomic, strong) UIButton *cancelButton; +@property (null_resettable, nonatomic, strong) UIStackView *buttonsStackView; + +@property (nullable, nonatomic, strong) NSURL *imageURL; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWCropAvatarViewController + +- (instancetype)initWithImage:(UIImage *)image imageURL:(NSURL *)imageURL { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _imageURL = imageURL; + + _cropController = [[DWTOCropViewController alloc] initWithCroppingStyle:TOCropViewCroppingStyleCircular + image:image]; + _cropController.hidesNavigationBar = YES; + _cropController.rotateClockwiseButtonHidden = YES; + _cropController.rotateButtonsHidden = YES; + _cropController.resetButtonHidden = YES; + _cropController.aspectRatioPickerButtonHidden = YES; + _cropController.doneButtonHidden = YES; + _cropController.cancelButtonHidden = YES; + _cropController.cropView.cropViewPadding = PADDING; + + if (image.CGImage) { + __weak typeof(self) weakSelf = self; + _faceDetector = + [[DWFaceDetector alloc] initWithImage:image + completion:^(CGRect roi) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (!CGRectEqualToRect(roi, CGRectZero)) { + strongSelf.cropController.imageCropFrame = roi; + } + + strongSelf.faceDetector = nil; + }]; + } + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self dw_embedChild:self.cropController]; + self.cropController.view.preservesSuperviewLayoutMargins = NO; + + [self.view addSubview:self.titleLabel]; + [self.view addSubview:self.buttonsStackView]; + + UILayoutGuide *marginsGuide = self.view.layoutMarginsGuide; + UILayoutGuide *safeAreaGuide = self.view.safeAreaLayoutGuide; + + const CGFloat bottomPadding = [DWCropAvatarViewController deviceSpecificBottomPadding]; + [NSLayoutConstraint activateConstraints:@[ + [self.titleLabel.topAnchor constraintEqualToAnchor:safeAreaGuide.topAnchor + constant:PADDING * 3], + [self.titleLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor + constant:PADDING], + [self.view.trailingAnchor constraintEqualToAnchor:self.titleLabel.trailingAnchor + constant:PADDING], + + [safeAreaGuide.bottomAnchor constraintEqualToAnchor:self.buttonsStackView.bottomAnchor + constant:bottomPadding], + [self.buttonsStackView.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], + [self.buttonsStackView.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], + ]]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +#pragma mark - Actions + +- (void)selectButtonAction:(UIButton *)sender { + CGRect cropFrame = self.cropController.cropView.imageCropFrame; + NSInteger angle = self.cropController.cropView.angle; + UIImage *croppedImage = [self.cropController.image croppedImageWithFrame:cropFrame angle:angle circularClip:NO]; + + // external image + if (self.imageURL != nil) { + CGSize imageSize = self.cropController.image.size; + + CGRect rectOfInterest = CGRectMake(cropFrame.origin.x / imageSize.width, + cropFrame.origin.y / imageSize.height, + cropFrame.size.width / imageSize.width, + cropFrame.size.height / imageSize.height); + // dashpay-profile-pic-zoom=left,top,right,bottom + NSString *paramSpecifier = nil; + if ([NSURLComponents componentsWithURL:self.imageURL resolvingAgainstBaseURL:NO].queryItems.count > 0) { + paramSpecifier = @"&"; + } + else { + paramSpecifier = @"?"; + } + + NSString *parameter = [NSString stringWithFormat:@"%@%@=%f,%f,%f,%f", + paramSpecifier, + DPCropParameterName, + rectOfInterest.origin.x, // left, + rectOfInterest.origin.y, // top + rectOfInterest.origin.x + rectOfInterest.size.width, // right, + rectOfInterest.origin.y + rectOfInterest.size.height]; // bottom + + [self.delegate cropAvatarViewController:self + didCropImage:croppedImage + urlString:[self.imageURL.absoluteString stringByAppendingString:parameter]]; + + return; + } + + self.titleLabel.hidden = YES; + self.buttonsStackView.hidden = YES; + + DWImgurInfoViewController *controller = [[DWImgurInfoViewController alloc] init]; + controller.croppedImage = croppedImage; + controller.delegate = self; + [self presentViewController:controller animated:YES completion:nil]; +} + +- (void)cancelButtonAction:(UIButton *)sender { + [self.delegate cropAvatarViewControllerDidCancel:self]; +} + +#pragma mark - DWImgurInfoViewControllerDelegate + +- (void)imgurInfoViewControllerDidAccept:(DWImgurInfoViewController *)controller { + UIImage *croppedImage = controller.croppedImage; + [controller + dismissViewControllerAnimated:YES + completion:^{ + if (croppedImage == nil) { + return; + } + + DWUploadAvatarViewController *controller = [[DWUploadAvatarViewController alloc] initWithImage:croppedImage]; + controller.delegate = self; + [self presentViewController:controller animated:YES completion:nil]; + }]; +} + +- (void)imgurInfoViewControllerDidCancel:(DWImgurInfoViewController *)controller { + self.titleLabel.hidden = NO; + self.buttonsStackView.hidden = NO; + + [controller dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - DWUploadAvatarViewControllerDelegate + +- (void)uploadAvatarViewControllerDidCancel:(DWUploadAvatarViewController *)controller { + self.titleLabel.hidden = NO; + self.buttonsStackView.hidden = NO; + + [controller dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)uploadAvatarViewController:(DWUploadAvatarViewController *)controller didFinishWithURLString:(NSString *)urlString { + UIImage *image = controller.image; + [controller dismissViewControllerAnimated:YES + completion:^{ + [self.delegate cropAvatarViewController:self didCropImage:image urlString:urlString]; + }]; +} + +#pragma mark - Private + +- (UILabel *)titleLabel { + if (_titleLabel == nil) { + UILabel *label = [[UILabel alloc] init]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.numberOfLines = 0; + label.textColor = [UIColor whiteColor]; + label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + label.text = NSLocalizedString(@"Move and Zoom your photo to find the perfect fit", nil); + label.lineBreakMode = NSLineBreakByWordWrapping; + label.textAlignment = NSTextAlignmentCenter; + _titleLabel = label; + } + return _titleLabel; +} + +- (UIButton *)selectButton { + if (_selectButton == nil) { + DWActionButton *button = [[DWActionButton alloc] initWithFrame:CGRectZero]; + button.translatesAutoresizingMaskIntoConstraints = NO; + [button setTitle:NSLocalizedString(@"Select", nil) forState:UIControlStateNormal]; + [button addTarget:self + action:@selector(selectButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + _selectButton = button; + } + return _selectButton; +} + +- (UIButton *)cancelButton { + if (_cancelButton == nil) { + DWActionButton *button = [[DWActionButton alloc] initWithFrame:CGRectZero]; + button.translatesAutoresizingMaskIntoConstraints = NO; + button.inverted = YES; + [button setTitle:NSLocalizedString(@"Cancel", nil) forState:UIControlStateNormal]; + [button addTarget:self + action:@selector(cancelButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + _cancelButton = button; + } + return _cancelButton; +} + +- (UIStackView *)buttonsStackView { + if (_buttonsStackView == nil) { + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ self.selectButton, self.cancelButton ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.spacing = 8.0; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.alignment = UIStackViewAlignmentFill; + + [NSLayoutConstraint activateConstraints:@[ + [self.selectButton.heightAnchor constraintEqualToConstant:DWBottomButtonHeight()], + [self.cancelButton.heightAnchor constraintEqualToConstant:DWBottomButtonHeight()], + ]]; + _buttonsStackView = stackView; + } + return _buttonsStackView; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/DWEditProfileViewController.h b/DashPay/Presentation/Profile/EditProfile/DWEditProfileViewController.h new file mode 100644 index 000000000..907c95b89 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/DWEditProfileViewController.h @@ -0,0 +1,47 @@ +// +// 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 + +@class DSBlockchainIdentity; +@class DWEditProfileViewController; + +@protocol DWEditProfileViewControllerDelegate + +- (void)editProfileViewControllerDidUpdate:(DWEditProfileViewController *)controller; + +@end + +@interface DWEditProfileViewController : UITableViewController + +@property (nullable, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +@property (readonly, nonatomic, copy) NSString *displayName; +@property (readonly, nonatomic, copy) NSString *aboutMe; +@property (nullable, readonly, nonatomic, copy) NSString *avatarURLString; +@property (readonly, nonatomic, assign, getter=isValid) BOOL valid; + +@property (nullable, nonatomic, weak) id delegate; + +- (BOOL)hasChanges; +- (void)updateDisplayName:(NSString *)displayName aboutMe:(NSString *)aboutMe; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/DWEditProfileViewController.m b/DashPay/Presentation/Profile/EditProfile/DWEditProfileViewController.m new file mode 100644 index 000000000..1da14495b --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/DWEditProfileViewController.m @@ -0,0 +1,369 @@ +// +// 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 "DWEditProfileViewController.h" + +#import + +#import "DSBlockchainIdentity+DWDisplayName.h" +#import "DWAvatarEditSelectorViewController.h" +#import "DWAvatarGravatarViewController.h" +#import "DWAvatarPublicURLViewController.h" +#import "DWCropAvatarViewController.h" +#import "DWEditProfileAvatarView.h" +#import "DWEditProfileTextFieldCell.h" +#import "DWEditProfileTextViewCell.h" +#import "DWEnvironment.h" +#import "DWProfileAboutCellModel.h" +#import "DWProfileDisplayNameCellModel.h" +#import "DWSharedUIConstants.h" +#import "DWTextInputFormTableViewCell.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWEditProfileViewController () + +@property (nullable, nonatomic, strong) DWEditProfileAvatarView *headerView; + +@property (nullable, nonatomic, copy) NSArray *items; +@property (nullable, nonatomic, strong) DWProfileDisplayNameCellModel *displayNameModel; +@property (nullable, nonatomic, strong) DWProfileAboutCellModel *aboutModel; +@property (nullable, nonatomic, copy) NSString *unsavedAvatarURL; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWEditProfileViewController + +- (instancetype)init { + self = [super initWithStyle:UITableViewStylePlain]; + if (self) { + } + return self; +} + +- (BOOL)hasChanges { + if (![self.displayName isEqualToString:[self.blockchainIdentity dw_displayNameOrUsername]]) { + return YES; + } + + if (self.aboutMe && ![self.aboutMe isEqualToString:self.blockchainIdentity.matchingDashpayUserInViewContext.publicMessage]) { + return YES; + } + + if (self.unsavedAvatarURL != nil) { + return YES; + } + + return NO; +} + +- (NSString *)displayName { + return self.displayNameModel.text; +} + +- (NSString *)aboutMe { + return self.aboutModel.text; +} + +- (NSString *)avatarURLString { + return self.unsavedAvatarURL ?: self.blockchainIdentity.avatarPath; +} + +- (BOOL)isValid { + return [self.aboutModel postValidate].isErrored == NO && [self.displayNameModel postValidate].isErrored == NO; +} + +- (void)updateDisplayName:(NSString *)displayName aboutMe:(NSString *)aboutMe { + self.displayNameModel.text = displayName; + self.aboutModel.text = aboutMe; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + self.blockchainIdentity = [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; + NSParameterAssert(self.blockchainIdentity); + + [self setupItems]; + [self setupView]; +} + +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + + 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 - Private + +- (void)setupView { + self.headerView = [[DWEditProfileAvatarView alloc] initWithFrame:CGRectZero]; + self.headerView.delegate = self; + [self.headerView setImageWithBlockchainIdentity:self.blockchainIdentity]; + + self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; + self.tableView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableView.estimatedRowHeight = 60.0; + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + self.tableView.tableFooterView = [[UIView alloc] init]; + self.tableView.tableHeaderView = self.headerView; + + NSArray *cellClasses = @[ + DWEditProfileTextViewCell.class, + DWEditProfileTextFieldCell.class, + ]; + for (Class cellClass in cellClasses) { + [self.tableView registerClass:cellClass forCellReuseIdentifier:NSStringFromClass(cellClass)]; + } +} + +- (void)setupItems { + NSMutableArray *items = [NSMutableArray array]; + + { + DWProfileDisplayNameCellModel *cellModel = [[DWProfileDisplayNameCellModel alloc] initWithTitle:NSLocalizedString(@"Display Name", nil)]; + self.displayNameModel = cellModel; + cellModel.autocorrectionType = UITextAutocorrectionTypeNo; + cellModel.returnKeyType = UIReturnKeyNext; + cellModel.text = [self.blockchainIdentity dw_displayNameOrUsername]; + __weak typeof(self) weakSelf = self; + cellModel.didChangeValueBlock = ^(DWTextFieldFormCellModel *_Nonnull cellModel) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf.delegate editProfileViewControllerDidUpdate:strongSelf]; + }; + [items addObject:cellModel]; + } + + { + DWProfileAboutCellModel *cellModel = [[DWProfileAboutCellModel alloc] initWithTitle:NSLocalizedString(@"About me", nil)]; + self.aboutModel = cellModel; + cellModel.text = self.blockchainIdentity.matchingDashpayUserInViewContext.publicMessage; + __weak typeof(self) weakSelf = self; + cellModel.didChangeValueBlock = ^(DWTextFieldFormCellModel *_Nonnull cellModel) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf.delegate editProfileViewControllerDidUpdate:strongSelf]; + }; + [items addObject:cellModel]; + } + + self.items = items; +} + +#pragma mark - UITableView + +- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.items.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + DWBaseFormCellModel *cellModel = self.items[indexPath.row]; + + if ([cellModel isKindOfClass:DWTextViewFormCellModel.class]) { + NSString *cellId = NSStringFromClass(DWEditProfileTextViewCell.class); + DWEditProfileTextViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId + forIndexPath:indexPath]; + cell.cellModel = (DWTextViewFormCellModel *)cellModel; + return cell; + } + else if ([cellModel isKindOfClass:DWTextFieldFormCellModel.class]) { + NSString *cellId = NSStringFromClass(DWEditProfileTextFieldCell.class); + DWEditProfileTextFieldCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId + forIndexPath:indexPath]; + cell.cellModel = (DWTextFieldFormCellModel *)cellModel; + cell.delegate = self; + return cell; + } + else { + NSAssert(NO, @"Unknown cell model %@", cellModel); + return [UITableViewCell new]; + } +} + +- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + UIView *view = [[UIView alloc] init]; + return view; +} + +#pragma mark - DWEditProfileAvatarViewDelegate + +- (void)editProfileAvatarView:(DWEditProfileAvatarView *)view editAvatarAction:(UIButton *)sender { + DWAvatarEditSelectorViewController *controller = [[DWAvatarEditSelectorViewController alloc] init]; + controller.delegate = self; + [self presentViewController:controller animated:YES completion:nil]; +} + +#pragma mark - DWAvatarEditSelectorViewControllerDelegate + +- (void)avatarEditSelectorViewController:(DWAvatarEditSelectorViewController *)controller photoButtonAction:(UIButton *)sender { + [controller dismissViewControllerAnimated:YES + completion:^{ + [self showImagePickerWithType:UIImagePickerControllerSourceTypeCamera]; + }]; +} + +- (void)avatarEditSelectorViewController:(DWAvatarEditSelectorViewController *)controller galleryButtonAction:(UIButton *)sender { + [controller dismissViewControllerAnimated:YES + completion:^{ + [self showImagePickerWithType:UIImagePickerControllerSourceTypePhotoLibrary]; + }]; +} + +- (void)avatarEditSelectorViewController:(DWAvatarEditSelectorViewController *)controller gravatarButtonAction:(UIButton *)sender { + [controller dismissViewControllerAnimated:YES + completion:^{ + [self showGravatarSource]; + }]; +} + +- (void)avatarEditSelectorViewController:(DWAvatarEditSelectorViewController *)controller urlButtonAction:(UIButton *)sender { + [controller dismissViewControllerAnimated:YES + completion:^{ + [self showPublicURLSource]; + }]; +} + +#pragma mark - UIImagePickerControllerDelegate + +- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { + UIImage *image = info[UIImagePickerControllerOriginalImage]; + [picker dismissViewControllerAnimated:YES + completion:^{ + if (image == nil) { + return; + } + + DWCropAvatarViewController *cropController = [[DWCropAvatarViewController alloc] initWithImage:image imageURL:nil]; + cropController.delegate = self; + cropController.modalPresentationStyle = UIModalPresentationFullScreen; + [self presentViewController:cropController animated:YES completion:nil]; + }]; +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { + [picker dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - DWCropAvatarViewControllerDelegate + +- (void)cropAvatarViewController:(DWCropAvatarViewController *)controller + didCropImage:(UIImage *)croppedImage + urlString:(NSString *)urlString { + self.headerView.image = croppedImage; + self.unsavedAvatarURL = urlString; + + [controller dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)cropAvatarViewControllerDidCancel:(DWCropAvatarViewController *)controller { + [controller dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - DWTextInputFormTableViewCell + +- (void)editProfileTextFieldCellActivateNextFirstResponder:(DWEditProfileTextFieldCell *)cell { + DWTextFieldFormCellModel *cellModel = cell.cellModel; + NSParameterAssert((cellModel.returnKeyType == UIReturnKeyNext)); + NSIndexPath *indexPath = [self.tableView indexPathForCell:cell]; + if (!indexPath) { + return; + } + + for (NSUInteger i = indexPath.row + 1; i < self.items.count; i++) { + DWBaseFormCellModel *cellModel = self.items[i]; + if ([cellModel isKindOfClass:DWTextFieldFormCellModel.class]) { + NSIndexPath *nextIndexPath = [NSIndexPath indexPathForRow:i inSection:0]; + id cell = [self.tableView cellForRowAtIndexPath:nextIndexPath]; + if ([cell conformsToProtocol:@protocol(DWTextInputFormTableViewCell)]) { + [cell textInputBecomeFirstResponder]; + } + else { + NSAssert(NO, @"Invalid cell class for TextFieldFormCellModel"); + } + + return; // we're done + } + } +} + +#pragma mark - DWExternalSourceViewControllerDelegate + +- (void)externalSourceViewController:(DWExternalSourceViewController *)controller didLoadImage:(UIImage *)image url:(NSURL *)url shouldCrop:(BOOL)shouldCrop { + if (!shouldCrop) { + self.headerView.image = image; + self.unsavedAvatarURL = url.absoluteString; + [controller dismissViewControllerAnimated:YES completion:nil]; + } + else { + [controller dismissViewControllerAnimated:YES + completion:^{ + DWCropAvatarViewController *cropController = [[DWCropAvatarViewController alloc] initWithImage:image imageURL:url]; + cropController.delegate = self; + cropController.modalPresentationStyle = UIModalPresentationFullScreen; + [self presentViewController:cropController animated:YES completion:nil]; + }]; + } +} + +#pragma mark - Private + +- (void)showPublicURLSource { + DWAvatarPublicURLViewController *controller = [[DWAvatarPublicURLViewController alloc] init]; + controller.delegate = self; + [controller setCurrentInput:[self avatarURLString]]; + [self presentViewController:controller animated:YES completion:nil]; +} + +- (void)showGravatarSource { + DWAvatarGravatarViewController *controller = [[DWAvatarGravatarViewController alloc] init]; + controller.delegate = self; + [self presentViewController:controller animated:YES completion:nil]; +} + +- (void)showImagePickerWithType:(UIImagePickerControllerSourceType)sourceType { + if (![UIImagePickerController isSourceTypeAvailable:sourceType]) { + return; + } + + UIImagePickerController *picker = [[UIImagePickerController alloc] init]; + picker.delegate = self; + picker.sourceType = sourceType; + picker.mediaTypes = @[ (id)kUTTypeImage ]; + [self presentViewController:picker animated:YES completion:nil]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.h b/DashPay/Presentation/Profile/EditProfile/DWRootEditProfileViewController.h similarity index 51% rename from DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.h rename to DashPay/Presentation/Profile/EditProfile/DWRootEditProfileViewController.h index b68f0bf72..7c13b95e8 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.h +++ b/DashPay/Presentation/Profile/EditProfile/DWRootEditProfileViewController.h @@ -15,28 +15,27 @@ // limitations under the License. // -#import "DWBaseContactsContentViewController.h" - -#import "DWContactsModel.h" -#import "DWPayModelProtocol.h" -#import "DWTransactionListDataProviderProtocol.h" +#import "DWBaseActionButtonViewController.h" NS_ASSUME_NONNULL_BEGIN -@class DWContactsContentViewController; +@class DWRootEditProfileViewController; + +@protocol DWRootEditProfileViewControllerDelegate -@protocol DWContactsContentControllerDelegate +- (void)editProfileViewController:(DWRootEditProfileViewController *)controller + updateDisplayName:(NSString *)rawDisplayName + aboutMe:(NSString *)rawAboutMe + avatarURLString:(nullable NSString *)avatarURLString; -- (void)contactsContentController:(DWContactsContentViewController *)controller - contactsFilterButtonAction:(UIView *)sender; -- (void)contactsContentController:(DWContactsContentViewController *)controller - contactRequestsButtonAction:(UIView *)sender; +- (void)editProfileViewControllerDidCancel:(DWRootEditProfileViewController *)controller; @end -@interface DWContactsContentViewController : DWBaseContactsContentViewController -@property (nullable, nonatomic, weak) id delegate; +@interface DWRootEditProfileViewController : DWBaseActionButtonViewController + +@property (nullable, nonatomic, weak) id delegate; @end diff --git a/DashPay/Presentation/Profile/EditProfile/DWRootEditProfileViewController.m b/DashPay/Presentation/Profile/EditProfile/DWRootEditProfileViewController.m new file mode 100644 index 000000000..f3b45ee94 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/DWRootEditProfileViewController.m @@ -0,0 +1,117 @@ +// +// 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 "DWRootEditProfileViewController.h" + +#import "DWEditProfileViewController.h" +#import "DWEnvironment.h" +#import "DWSaveAlertViewController.h" +#import "UIViewController+DWEmbedding.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWRootEditProfileViewController () + +@property (nonatomic, strong) DWEditProfileViewController *editController; +@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWRootEditProfileViewController + +- (NSString *)actionButtonTitle { + return NSLocalizedString(@"Save", nil); +} + +- (DSBlockchainIdentity *)blockchainIdentity { + return self.editController.blockchainIdentity; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = NSLocalizedString(@"Edit Profile", nil); + + UIBarButtonItem *cancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel + target:self + action:@selector(cancelButtonAction)]; + self.navigationItem.leftBarButtonItem = cancel; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + [self setupContentView:contentView]; + + self.editController = [[DWEditProfileViewController alloc] init]; + self.editController.delegate = self; + [self dw_embedChild:self.editController inContainer:contentView]; + + [self editProfileViewControllerDidUpdate:self.editController]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (void)performSave { + [self.delegate editProfileViewController:self + updateDisplayName:self.editController.displayName + aboutMe:self.editController.aboutMe + avatarURLString:self.editController.avatarURLString]; +} + +#pragma mark - DWEditProfileViewControllerDelegate + +- (void)editProfileViewControllerDidUpdate:(DWEditProfileViewController *)controller { + self.actionButton.enabled = controller.isValid; +} + +#pragma mark - DWSaveAlertViewController + +- (void)saveAlertViewControllerCancelAction:(DWSaveAlertViewController *)controller { + [controller dismissViewControllerAnimated:YES + completion:^{ + [self.delegate editProfileViewControllerDidCancel:self]; + }]; +} + +- (void)saveAlertViewControllerOKAction:(DWSaveAlertViewController *)controller { + [controller dismissViewControllerAnimated:YES + completion:^{ + [self performSave]; + }]; +} + +#pragma mark - Actions + +- (void)cancelButtonAction { + if ([self.editController hasChanges]) { + DWSaveAlertViewController *saveAlert = [[DWSaveAlertViewController alloc] init]; + saveAlert.delegate = self; + [self presentViewController:saveAlert animated:YES completion:nil]; + } + else { + [self.delegate editProfileViewControllerDidCancel:self]; + } +} + +- (void)actionButtonAction:(id)sender { + [self performSave]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPIncomingRequestItem.h b/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarGravatarViewController.h similarity index 85% rename from DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPIncomingRequestItem.h rename to DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarGravatarViewController.h index 286dc79ab..07d6b5408 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPIncomingRequestItem.h +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarGravatarViewController.h @@ -15,11 +15,11 @@ // limitations under the License. // -#import "DWDPBasicUserItem.h" +#import "DWExternalSourceViewController.h" NS_ASSUME_NONNULL_BEGIN -@protocol DWDPIncomingRequestItem +@interface DWAvatarGravatarViewController : DWExternalSourceViewController @end diff --git a/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarGravatarViewController.m b/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarGravatarViewController.m new file mode 100644 index 000000000..ad08db07f --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarGravatarViewController.m @@ -0,0 +1,120 @@ +// +// 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 "DWAvatarGravatarViewController.h" + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DWAvatarGravatarViewController () + +@property (nullable, weak, nonatomic) SDWebImageDownloadToken *token; + +@end + +NS_ASSUME_NONNULL_END + +@implementation NSString (MD5Gravatar) + +- (NSString *)dw_MD5String { + const char *cStr = [self UTF8String]; + unsigned char result[CC_MD5_DIGEST_LENGTH]; + CC_MD5(cStr, (CC_LONG)strlen(cStr), result); + + return [NSString stringWithFormat: + @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + result[0], result[1], result[2], result[3], + result[4], result[5], result[6], result[7], + result[8], result[9], result[10], result[11], + result[12], result[13], result[14], result[15]]; +} + +@end + +@implementation DWAvatarGravatarViewController + +- (DWAvatarExternalSourceConfig *)config { + DWAvatarExternalSourceConfig *config = [[DWAvatarExternalSourceConfig alloc] init]; + config.icon = [UIImage imageNamed:@"ava_gravatar"]; + config.title = @"Gravatar"; + config.subtitle = NSLocalizedString(@"Enter your Gravatar Email ID", nil); + config.desc = NSLocalizedString(@"Your Email is not stored in the DashPay wallet nor on any servers. It is used once to get your Gravatar account details and then discarded.", nil); + config.keyboardType = UIKeyboardTypeEmailAddress; + config.placeholder = @"example@email.com"; + return config; +} + + +- (BOOL)isInputValid:(NSString *)input { + NSString *trimmed = [input stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + BOOL valid = trimmed.length > 0 && [self validateEmailWithString:trimmed]; + if (valid) { + return YES; + } + else { + [self showError:NSLocalizedString(@"Please enter a valid gravatar email ID.", nil)]; + return NO; + } +} + +- (void)performLoad:(NSString *)email { + [self showLoadingView]; + + NSString *trimmed = [email stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + // fetch size 200px (s=200) and fail if not found (d=404) + NSString *urlString = [NSString stringWithFormat:@"https://www.gravatar.com/avatar/%@?s=200&d=404", + [[trimmed dw_MD5String] lowercaseString]]; + NSURL *url = [NSURL URLWithString:urlString]; + + __weak typeof(self) weakSelf = self; + self.token = [[SDWebImageDownloader sharedDownloader] + downloadImageWithURL:url + options:SDWebImageDownloaderUseNSURLCache + progress:nil + completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (image && finished) { + [strongSelf.delegate externalSourceViewController:self didLoadImage:image url:url shouldCrop:NO]; + } + else { + [strongSelf showError:NSLocalizedString(@"Unable to fetch your Gravatar. Please enter a valid gravatar email ID.", nil)]; + } + }]; +} + +- (void)cancelLoading { + [self.token cancel]; + self.token = nil; + + [self showDefaultSubtitle]; +} + +- (BOOL)validateEmailWithString:(NSString *)email { + NSString *emailRegex = @"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$"; + NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex]; + return [emailTest evaluateWithObject:email]; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarPublicURLViewController.h b/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarPublicURLViewController.h new file mode 100644 index 000000000..254ed9715 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarPublicURLViewController.h @@ -0,0 +1,26 @@ +// +// 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 "DWExternalSourceViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWAvatarPublicURLViewController : DWExternalSourceViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarPublicURLViewController.m b/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarPublicURLViewController.m new file mode 100644 index 000000000..b9704c3c2 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/DWAvatarPublicURLViewController.m @@ -0,0 +1,128 @@ +// +// 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 "DWAvatarPublicURLViewController.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DWAvatarPublicURLViewController () + +@property (nullable, weak, nonatomic) SDWebImageDownloadToken *token; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWAvatarPublicURLViewController + +- (DWAvatarExternalSourceConfig *)config { + DWAvatarExternalSourceConfig *config = [[DWAvatarExternalSourceConfig alloc] init]; + config.icon = [UIImage imageNamed:@"ava_puburl"]; + config.title = NSLocalizedString(@"Public URL", nil); + config.subtitle = NSLocalizedString(@"Paste your image URL", nil); + config.desc = NSLocalizedString(@"You can specify any URL which is publicly available on the internet so other users can see it on the Dash network.", nil); + config.keyboardType = UIKeyboardTypeURL; + return config; +} + +- (BOOL)isInputValid:(NSString *)input { + NSString *trimmed = [input stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if (trimmed.length == 0) { + [self showError:NSLocalizedString(@"Please enter a valid image URL.", nil)]; + return NO; + } + + const NSUInteger maxLength = 256; + if (trimmed.length > maxLength) { + [self showError:[NSString stringWithFormat:NSLocalizedString(@"Image URL can't be longer than %ld characters.", nil), maxLength]]; + return NO; + } + + // regex to check valid url is too complicated, do a dumb check + NSURL *url = [NSURL URLWithString:trimmed]; + if (url) { + return YES; + } + else { + [self showError:NSLocalizedString(@"Please enter a valid image URL.", nil)]; + return NO; + } +} + +- (void)performLoad:(NSString *)urlString { + [self showLoadingView]; + + NSURL *url = [self convertedURLString:urlString]; + + __weak typeof(self) weakSelf = self; + self.token = [[SDWebImageDownloader sharedDownloader] + downloadImageWithURL:url + options:SDWebImageDownloaderUseNSURLCache + progress:nil + completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (image && finished) { + [strongSelf.delegate externalSourceViewController:self didLoadImage:image url:url shouldCrop:YES]; + } + else { + [strongSelf showError:NSLocalizedString(@"Unable to fetch image. Please enter a valid image URL or check your connection.", nil)]; + } + }]; +} + +- (void)cancelLoading { + [self.token cancel]; + self.token = nil; + + [self showDefaultSubtitle]; +} + +- (NSURL *)convertedURLString:(NSString *)urlString { + NSString *trimmed = [urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + // https://drive.google.com/file/d/12rhWM7_wIXwDcFfsANkVGa0ArrbnhrMN/view?usp=sharing + NSString *googlePrefix = @"https://drive.google.com/file/d/"; + if ([trimmed hasPrefix:googlePrefix]) { + NSString *rest = [trimmed stringByReplacingOccurrencesOfString:googlePrefix withString:@""]; + NSRange range = [rest rangeOfString:@"/"]; + if (range.location != NSNotFound) { + NSString *googleID = [rest substringToIndex:range.location]; + NSString *resultFormat = [NSString stringWithFormat:@"https://drive.google.com/uc?export=view&id=%@", + googleID]; + return [NSURL URLWithString:resultFormat]; + } + } + + // https://www.dropbox.com/s/2ldd9fjk02yvyv1/IMG_20201103_220114.jpg?dl=0 + NSString *dropboxPrefix = @"https://www.dropbox.com/s/"; + if ([trimmed hasPrefix:dropboxPrefix]) { + NSString *result = [trimmed stringByReplacingOccurrencesOfString:dropboxPrefix + withString:@"https://dl.dropboxusercontent.com/s/"]; + return [NSURL URLWithString:result]; + } + + return [NSURL URLWithString:trimmed]; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/DWExternalSourceViewController.h b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/DWExternalSourceViewController.h new file mode 100644 index 000000000..9d97975f5 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/DWExternalSourceViewController.h @@ -0,0 +1,53 @@ +// +// 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 + +#import "DWAvatarExternalSourceConfig.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWExternalSourceViewController; + +@protocol DWExternalSourceViewControllerDelegate + +- (void)externalSourceViewController:(DWExternalSourceViewController *)controller + didLoadImage:(UIImage *)image + url:(NSURL *)url + shouldCrop:(BOOL)shouldCrop; + +@end + +@interface DWExternalSourceViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +- (void)setCurrentInput:(NSString *)input; + +- (DWAvatarExternalSourceConfig *)config; + +- (void)performLoad:(NSString *)urlString; +- (BOOL)isInputValid:(NSString *)input; + +- (void)showError:(NSString *)error; +- (void)showDefaultSubtitle; +- (void)showLoadingView; +- (void)cancelLoading; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/DWExternalSourceViewController.m b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/DWExternalSourceViewController.m new file mode 100644 index 000000000..0983624f7 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/DWExternalSourceViewController.m @@ -0,0 +1,185 @@ +// +// 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 "DWExternalSourceViewController.h" + +#import + +#import "DWAvatarExternalLoadingView.h" +#import "DWAvatarExternalSourceView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWExternalSourceViewController () + +@property (nonatomic, strong) UIView *contentView; +@property (nonatomic, strong) DWAvatarExternalSourceView *sourceView; +@property (nonatomic, strong) NSLayoutConstraint *centerYConstraint; +@property (nonatomic, strong) DWAvatarExternalLoadingView *loadingView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWExternalSourceViewController + +- (instancetype)init { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + self.sourceView = [[DWAvatarExternalSourceView alloc] init]; + } + return self; +} + +- (void)setCurrentInput:(NSString *)input { + self.sourceView.input = input; +} + +- (DWAvatarExternalSourceConfig *)config { + DWAvatarExternalSourceConfig *config = [[DWAvatarExternalSourceConfig alloc] init]; + return config; +} + +- (void)performLoad:(NSString *)url { +} + +- (void)cancelButton { +} + +- (BOOL)isInputValid:(NSString *)input { + return YES; +} + +- (void)showError:(NSString *)error { + self.sourceView.hidden = NO; + self.loadingView.hidden = YES; + [self.sourceView showError:error]; +} + +- (void)showDefaultSubtitle { + self.sourceView.hidden = NO; + self.loadingView.hidden = YES; + [self.sourceView showSubtitle]; +} + +- (void)showLoadingView { + self.loadingView.hidden = NO; + self.sourceView.hidden = YES; +} + +- (void)cancelLoading { +} + +- (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]; + self.contentView = contentView; + + self.sourceView.translatesAutoresizingMaskIntoConstraints = NO; + self.sourceView.config = self.config; + self.sourceView.delegate = self; + [contentView addSubview:self.sourceView]; + + DWAvatarExternalLoadingView *loadingView = [[DWAvatarExternalLoadingView alloc] init]; + loadingView.translatesAutoresizingMaskIntoConstraints = NO; + loadingView.delegate = self; + loadingView.hidden = YES; + [contentView addSubview:loadingView]; + self.loadingView = loadingView; + + self.centerYConstraint = [contentView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor]; + [NSLayoutConstraint activateConstraints:@[ + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + self.centerYConstraint, + + [self.sourceView.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:32.0], + [self.sourceView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [self.sourceView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:self.sourceView.bottomAnchor + constant:32.0], + + [loadingView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [loadingView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [loadingView.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor], + ]]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + // pre-layout view to avoid undesired animation if the keyboard is shown while appearing + [self.view layoutIfNeeded]; + [self ka_startObservingKeyboardNotifications]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [self ka_stopObservingKeyboardNotifications]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [self.sourceView activateTextField]; +} + +#pragma mark - Keyboard + +- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height + animationDuration:(NSTimeInterval)animationDuration + animationCurve:(UIViewAnimationCurve)animationCurve { + if (height == 0) { + self.centerYConstraint.constant = 0; + } + else { + self.centerYConstraint.constant = -(CGRectGetHeight(self.view.bounds) - CGRectGetHeight(self.contentView.bounds)) / 2.0; + } + [self.view layoutIfNeeded]; +} + +#pragma mark - DWAvatarExternalSourceViewDelegate + +- (void)avatarExternalSourceViewOKAction:(DWAvatarExternalSourceView *)view { + if ([self isInputValid:view.input]) { + [self.view endEditing:YES]; + [self performLoad:view.input]; + } +} + +- (void)avatarExternalSourceViewCancelAction:(DWAvatarExternalSourceView *)view { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - DWAvatarExternalLoadingViewDelegate + +- (void)avatarExternalLoadingViewCancelAction:(DWAvatarExternalLoadingView *)view { + [self cancelLoading]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.h b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalLoadingView.h similarity index 69% rename from DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.h rename to DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalLoadingView.h index e52129260..ab890f9ac 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.h +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalLoadingView.h @@ -17,14 +17,19 @@ #import -#import "DWDPBasicItem.h" - NS_ASSUME_NONNULL_BEGIN -@interface UICollectionView (DWDPItemDequeue) +@class DWAvatarExternalLoadingView; + +@protocol DWAvatarExternalLoadingViewDelegate + +- (void)avatarExternalLoadingViewCancelAction:(DWAvatarExternalLoadingView *)view; + +@end + +@interface DWAvatarExternalLoadingView : UIView -- (void)dw_registerDPItemCells; -- (__kindof UICollectionViewCell *)dw_dequeueReusableCellForItem:(id)item atIndexPath:(NSIndexPath *)indexPath; +@property (nullable, nonatomic, weak) id delegate; @end diff --git a/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalLoadingView.m b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalLoadingView.m new file mode 100644 index 000000000..443fb6b5f --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalLoadingView.m @@ -0,0 +1,124 @@ +// +// 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 "DWAvatarExternalLoadingView.h" + +#import "DWActionButton.h" +#import "DWHourGlassAnimationView.h" +#import "DWUIKit.h" + + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const ButtonHeight = 39.0; + +@interface DWAvatarExternalLoadingView () + +@property (readonly, nonatomic, strong) DWHourGlassAnimationView *animationView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWAvatarExternalLoadingView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + const CGFloat circleSize = 86.0; + + UIView *bgView = [[UIView alloc] init]; + bgView.translatesAutoresizingMaskIntoConstraints = NO; + bgView.backgroundColor = [UIColor colorWithRed:1.0 green:232.0 / 255.0 blue:194.0 / 255.0 alpha:1.0]; + bgView.layer.cornerRadius = circleSize / 2.0; + bgView.layer.masksToBounds = YES; + [self addSubview:bgView]; + + DWHourGlassAnimationView *animationView = [[DWHourGlassAnimationView alloc] initWithFrame:CGRectZero]; + animationView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:animationView]; + _animationView = animationView; + + UILabel *subtitleLabel = [[UILabel alloc] init]; + subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; + subtitleLabel.numberOfLines = 0; + subtitleLabel.textAlignment = NSTextAlignmentCenter; + subtitleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + subtitleLabel.textColor = [UIColor dw_darkTitleColor]; + subtitleLabel.text = NSLocalizedString(@"Fetching Image", nil); + [self addSubview:subtitleLabel]; + + DWActionButton *cancelButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + cancelButton.usedOnDarkBackground = NO; + cancelButton.small = YES; + cancelButton.inverted = YES; + [cancelButton setTitle:NSLocalizedString(@"Cancel", nil) forState:UIControlStateNormal]; + [cancelButton addTarget:self + action:@selector(cancelButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:cancelButton]; + + [NSLayoutConstraint activateConstraints:@[ + [bgView.topAnchor constraintEqualToAnchor:self.topAnchor], + [bgView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [bgView.widthAnchor constraintEqualToConstant:circleSize], + [bgView.heightAnchor constraintEqualToConstant:circleSize], + + [animationView.centerXAnchor constraintEqualToAnchor:bgView.centerXAnchor], + [animationView.centerYAnchor constraintEqualToAnchor:bgView.centerYAnchor], + + [subtitleLabel.topAnchor constraintEqualToAnchor:bgView.bottomAnchor + constant:16.0], + [subtitleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:8.0], + [self.trailingAnchor constraintEqualToAnchor:subtitleLabel.trailingAnchor + constant:8.0], + + [cancelButton.topAnchor constraintEqualToAnchor:subtitleLabel.bottomAnchor + constant:56.0], + [cancelButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [cancelButton.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [self.bottomAnchor constraintEqualToAnchor:cancelButton.bottomAnchor], + ]]; + } + return self; +} + +- (void)cancelButtonAction { + [self.delegate avatarExternalLoadingViewCancelAction:self]; +} + +- (void)willMoveToWindow:(nullable UIWindow *)newWindow { + [super willMoveToWindow:newWindow]; + + if (newWindow == nil) { + [self.animationView stopAnimating]; + } +} + +- (void)didMoveToWindow { + [super didMoveToWindow]; + + if (self.window) { + [self.animationView startAnimating]; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.h b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceConfig.h similarity index 67% rename from DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.h rename to DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceConfig.h index 800730cc7..c3313f11d 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.h +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceConfig.h @@ -17,17 +17,17 @@ #import -#import "DWDPAvatarView.h" - NS_ASSUME_NONNULL_BEGIN -@interface DWDPGenericItemView : UIView +@interface DWAvatarExternalSourceConfig : NSObject -@property (nonatomic, assign, getter=isAvatarHidden) BOOL avatarHidden; +@property (nonatomic, copy) NSString *title; +@property (nonatomic, strong) UIImage *icon; +@property (nonatomic, copy) NSString *subtitle; +@property (nonatomic, copy) NSString *desc; -@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; -@property (readonly, nonatomic, strong) UILabel *textLabel; -@property (readonly, nonatomic, strong) UIView *accessoryView; +@property (nullable, nonatomic, copy) NSString *placeholder; +@property (nonatomic, assign) UIKeyboardType keyboardType; @end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.m b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceConfig.m similarity index 85% rename from DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.m rename to DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceConfig.m index 65535bf06..cc38e7e69 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.m +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceConfig.m @@ -15,10 +15,8 @@ // limitations under the License. // -#import "DWDPNewIncomingRequestObject.h" +#import "DWAvatarExternalSourceConfig.h" -@implementation DWDPNewIncomingRequestObject - -@synthesize requestState; +@implementation DWAvatarExternalSourceConfig @end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.h b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceView.h similarity index 53% rename from DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.h rename to DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceView.h index c083281c1..af8ac60f5 100644 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.h +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceView.h @@ -17,24 +17,31 @@ #import +#import "DWAvatarExternalSourceConfig.h" + NS_ASSUME_NONNULL_BEGIN -@class DWUserProfileModel; -@class DWUserProfileSendRequestCell; +@class DWAvatarExternalSourceView; -@protocol DWUserProfileSendRequestCellDelegate +@protocol DWAvatarExternalSourceViewDelegate -- (void)userProfileSendRequestCell:(DWUserProfileSendRequestCell *)cell sendRequestButtonAction:(UIButton *)sender; +- (void)avatarExternalSourceViewOKAction:(DWAvatarExternalSourceView *)view; +- (void)avatarExternalSourceViewCancelAction:(DWAvatarExternalSourceView *)view; @end +@interface DWAvatarExternalSourceView : UIView + +@property (nullable, nonatomic, copy) NSString *input; + +@property (nullable, nonatomic, strong) DWAvatarExternalSourceConfig *config; -@interface DWUserProfileSendRequestCell : UICollectionViewCell +@property (nullable, nonatomic, weak) id delegate; -@property (nonatomic, assign) CGFloat contentWidth; +- (void)showError:(NSString *)error; +- (void)showSubtitle; -@property (nullable, nonatomic, strong) DWUserProfileModel *model; -@property (nullable, nonatomic, weak) id delegate; +- (void)activateTextField; @end diff --git a/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceView.m b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceView.m new file mode 100644 index 000000000..078fc00b9 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/External Sources/Skeleton/Views/DWAvatarExternalSourceView.m @@ -0,0 +1,236 @@ +// +// 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 "DWAvatarExternalSourceView.h" + +#import "DWActionButton.h" +#import "DWBorderedActionButton.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGSize const IconSize = {24.0, 24.0}; +static CGFloat const ButtonHeight = 39.0; + +@interface DWAvatarExternalSourceView () + +@property (readonly, nonatomic, strong) UIImageView *iconImageView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) UILabel *subtitleLabel; +@property (readonly, nonatomic, strong) UITextField *textField; +@property (readonly, nonatomic, strong) UILabel *descLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWAvatarExternalSourceView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + UIImageView *iconImageView = [[UIImageView alloc] init]; + iconImageView.translatesAutoresizingMaskIntoConstraints = NO; + iconImageView.contentMode = UIViewContentModeScaleAspectFit; + [self addSubview:iconImageView]; + _iconImageView = iconImageView; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.numberOfLines = 0; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + [self addSubview:titleLabel]; + _titleLabel = titleLabel; + + UILabel *subtitleLabel = [[UILabel alloc] init]; + subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; + subtitleLabel.numberOfLines = 0; + subtitleLabel.textAlignment = NSTextAlignmentCenter; + subtitleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + subtitleLabel.textColor = [UIColor dw_secondaryTextColor]; + [self addSubview:subtitleLabel]; + _subtitleLabel = subtitleLabel; + + UITextField *textField = [[UITextField alloc] init]; + textField.translatesAutoresizingMaskIntoConstraints = NO; + textField.delegate = self; + textField.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + textField.textColor = [UIColor dw_darkTitleColor]; + textField.layer.borderColor = [UIColor dw_separatorLineColor].CGColor; + textField.layer.cornerRadius = 8.0; + textField.layer.masksToBounds = YES; + textField.layer.borderWidth = 1.0; + textField.textAlignment = NSTextAlignmentCenter; + textField.autocorrectionType = UITextAutocorrectionTypeNo; + textField.autocapitalizationType = UITextAutocapitalizationTypeNone; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + [self addSubview:textField]; + _textField = textField; + + UILabel *descLabel = [[UILabel alloc] init]; + descLabel.translatesAutoresizingMaskIntoConstraints = NO; + descLabel.numberOfLines = 0; + descLabel.textAlignment = NSTextAlignmentCenter; + descLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; + descLabel.textColor = [UIColor dw_secondaryTextColor]; + [self addSubview:descLabel]; + _descLabel = descLabel; + + DWActionButton *okButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + okButton.translatesAutoresizingMaskIntoConstraints = NO; + okButton.usedOnDarkBackground = NO; + okButton.small = YES; + okButton.inverted = NO; + [okButton setTitle:NSLocalizedString(@"OK", nil) forState:UIControlStateNormal]; + [okButton addTarget:self + action:@selector(okButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + + DWBorderedActionButton *cancelButton = [[DWBorderedActionButton alloc] initWithFrame:CGRectZero]; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + [cancelButton setTitle:NSLocalizedString(@"Cancel", nil) forState:UIControlStateNormal]; + [cancelButton addTarget:self + action:@selector(cancelButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ okButton, cancelButton ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisHorizontal; + stackView.distribution = UIStackViewDistributionFillEqually; + stackView.spacing = 8.0; + stackView.alignment = UIStackViewAlignmentCenter; + [self addSubview:stackView]; + + [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 forAxis:UILayoutConstraintAxisVertical]; + [subtitleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 forAxis:UILayoutConstraintAxisVertical]; + [descLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 3 forAxis:UILayoutConstraintAxisVertical]; + + + [NSLayoutConstraint activateConstraints:@[ + [iconImageView.topAnchor constraintEqualToAnchor:self.topAnchor], + [iconImageView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [iconImageView.widthAnchor constraintEqualToConstant:IconSize.width], + [iconImageView.heightAnchor constraintEqualToConstant:IconSize.height], + + [titleLabel.topAnchor constraintEqualToAnchor:iconImageView.bottomAnchor + constant:8.0], + [titleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:8.0], + [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor + constant:8.0], + + [subtitleLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:16.0], + [subtitleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:8.0], + [self.trailingAnchor constraintEqualToAnchor:subtitleLabel.trailingAnchor + constant:8.0], + + [textField.topAnchor constraintEqualToAnchor:subtitleLabel.bottomAnchor + constant:16.0], + [textField.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:16.0], + [self.trailingAnchor constraintEqualToAnchor:textField.trailingAnchor + constant:16.0], + [textField.heightAnchor constraintEqualToConstant:52.0], + + [descLabel.topAnchor constraintEqualToAnchor:textField.bottomAnchor + constant:16.0], + [descLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:8.0], + [self.trailingAnchor constraintEqualToAnchor:descLabel.trailingAnchor + constant:8.0], + + [stackView.topAnchor constraintEqualToAnchor:descLabel.bottomAnchor + constant:56.0], + [stackView.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.leadingAnchor + constant:8.0], + [self.trailingAnchor constraintGreaterThanOrEqualToAnchor:stackView.trailingAnchor + constant:8.0], + [self.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor], + [stackView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + + [okButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [cancelButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [stackView.heightAnchor constraintEqualToConstant:ButtonHeight], + ]]; + } + return self; +} + +- (NSString *)input { + return self.textField.text; +} + +- (void)setInput:(NSString *)input { + self.textField.text = input; +} + +- (void)setConfig:(DWAvatarExternalSourceConfig *)config { + _config = config; + + self.iconImageView.image = config.icon; + self.titleLabel.text = config.title; + self.subtitleLabel.text = config.subtitle; + self.descLabel.text = config.desc; + self.textField.placeholder = config.placeholder; + self.textField.keyboardType = config.keyboardType; +} + +- (void)showError:(NSString *)error { + self.subtitleLabel.text = error; + self.subtitleLabel.textColor = [UIColor dw_redColor]; +} + +- (void)showSubtitle { + self.subtitleLabel.text = self.config.subtitle; + self.subtitleLabel.textColor = [UIColor dw_secondaryTextColor]; +} + +- (void)activateTextField { + [self.textField becomeFirstResponder]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + self.textField.layer.borderColor = [UIColor dw_separatorLineColor].CGColor; +} + +- (void)okButtonAction { + [self.delegate avatarExternalSourceViewOKAction:self]; +} + +- (void)cancelButtonAction { + [self.delegate avatarExternalSourceViewCancelAction:self]; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + return YES; +} + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { + [self showSubtitle]; // reset error + return YES; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoChildView.h b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoChildView.h new file mode 100644 index 000000000..7a5f03ad2 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoChildView.h @@ -0,0 +1,37 @@ +// +// 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 DWImgurInfoChildView; + +@protocol DWImgurInfoChildViewDelegate + +- (void)imgurInfoChildViewAcceptAction:(DWImgurInfoChildView *)view; +- (void)imgurInfoChildViewCancelAction:(DWImgurInfoChildView *)view; + +@end + +@interface DWImgurInfoChildView : UIView + +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoChildView.m b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoChildView.m new file mode 100644 index 000000000..9748a99a4 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoChildView.m @@ -0,0 +1,159 @@ +// +// 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 "DWImgurInfoChildView.h" + +#import "DWActionButton.h" +#import "DWBorderedActionButton.h" +#import "DWImgurItemView.h" +#import "DWUIKit.h" + +static CGFloat const ViewCornerRadius = 8.0; +static CGFloat const ButtonHeight = 39.0; + +NS_ASSUME_NONNULL_BEGIN + +@interface DWImgurInfoChildView () + +@property (nonatomic, strong) DWImgurItemView *item1; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWImgurInfoChildView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + self.layer.cornerRadius = ViewCornerRadius; + self.layer.masksToBounds = YES; + + UILabel *title = [[UILabel alloc] init]; + title.translatesAutoresizingMaskIntoConstraints = NO; + title.font = [UIFont dw_fontForTextStyle:UIFontTextStyleTitle2]; + title.textColor = [UIColor dw_darkTitleColor]; + title.text = NSLocalizedString(@"Images Privacy Policy", nil); + title.textAlignment = NSTextAlignmentCenter; + [self addSubview:title]; + + DWImgurItemView *item1 = [[DWImgurItemView alloc] init]; + item1.translatesAutoresizingMaskIntoConstraints = NO; + _item1 = item1; + + DWImgurItemView *item2 = [[DWImgurItemView alloc] init]; + item2.translatesAutoresizingMaskIntoConstraints = NO; + item2.text = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Image uploaded can be viewed publicly by anyone.", nil)]; + + DWImgurItemView *item3 = [[DWImgurItemView alloc] init]; + item3.translatesAutoresizingMaskIntoConstraints = NO; + item3.text = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"You can always delete the image uploaded, as long as you have access to this wallet.", nil)]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ item1, item2, item3 ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.spacing = 20.0; + [self addSubview:stackView]; + + DWActionButton *okButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + okButton.translatesAutoresizingMaskIntoConstraints = NO; + okButton.usedOnDarkBackground = NO; + okButton.small = YES; + okButton.inverted = NO; + [okButton setTitle:NSLocalizedString(@"Agree", nil) forState:UIControlStateNormal]; + [okButton addTarget:self + action:@selector(okButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + + DWBorderedActionButton *cancelButton = [[DWBorderedActionButton alloc] initWithFrame:CGRectZero]; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + [cancelButton setTitle:NSLocalizedString(@"Cancel", nil) forState:UIControlStateNormal]; + [cancelButton addTarget:self + action:@selector(cancelButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + + UIStackView *buttonsStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ okButton, cancelButton ]]; + buttonsStackView.translatesAutoresizingMaskIntoConstraints = NO; + buttonsStackView.axis = UILayoutConstraintAxisHorizontal; + buttonsStackView.distribution = UIStackViewDistributionFillEqually; + buttonsStackView.spacing = 8.0; + buttonsStackView.alignment = UIStackViewAlignmentCenter; + [self addSubview:buttonsStackView]; + + [NSLayoutConstraint activateConstraints:@[ + [title.topAnchor constraintEqualToAnchor:self.topAnchor + constant:32.0], + [title.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:16.0], + [self.trailingAnchor constraintEqualToAnchor:title.trailingAnchor + constant:16.0], + + [stackView.topAnchor constraintEqualToAnchor:title.bottomAnchor + constant:18.0], + [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:25.0], + [self.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor + constant:25.0], + + + [buttonsStackView.topAnchor constraintEqualToAnchor:stackView.bottomAnchor + constant:40], + [buttonsStackView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [self.bottomAnchor constraintEqualToAnchor:buttonsStackView.bottomAnchor + constant:32.0], + + [okButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [cancelButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [buttonsStackView.heightAnchor constraintEqualToConstant:ButtonHeight], + ]]; + + [self updateItem1Text]; + } + return self; +} + +- (void)okButtonAction { + [self.delegate imgurInfoChildViewAcceptAction:self]; +} + +- (void)cancelButtonAction { + [self.delegate imgurInfoChildViewCancelAction:self]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + [self updateItem1Text]; +} + +- (void)updateItem1Text { + NSString *s1 = NSLocalizedString(@"The image you select will be uploaded to Imgur anonymously.", + @"Don't translate 'Imgur'"); + NSRange range = [s1 rangeOfString:@"Imgur"]; + NSMutableAttributedString *att1 = [[NSMutableAttributedString alloc] initWithString:s1]; + if (range.location != NSNotFound) { + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + attachment.image = [UIImage imageNamed:@"logo_imgur_small"]; + attachment.bounds = CGRectMake(0.0, -4.0, 45.0, 16.0); + NSAttributedString *imageString = [NSAttributedString attributedStringWithAttachment:attachment]; + [att1 replaceCharactersInRange:range withAttributedString:imageString]; + } + self.item1.text = att1; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoViewController.h b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoViewController.h new file mode 100644 index 000000000..3e1cab240 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoViewController.h @@ -0,0 +1,38 @@ +// +// 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 DWImgurInfoViewController; + +@protocol DWImgurInfoViewControllerDelegate + +- (void)imgurInfoViewControllerDidAccept:(DWImgurInfoViewController *)controller; +- (void)imgurInfoViewControllerDidCancel:(DWImgurInfoViewController *)controller; + +@end + +@interface DWImgurInfoViewController : UIViewController + +@property (nonatomic, strong) UIImage *croppedImage; +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoViewController.m b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoViewController.m new file mode 100644 index 000000000..8bda70ffb --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurInfoViewController.m @@ -0,0 +1,72 @@ +// +// 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 "DWImgurInfoViewController.h" + +#import "DWImgurInfoChildView.h" +#import "DWModalPopupTransition.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWImgurInfoViewController () + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWImgurInfoViewController + +- (instancetype)init { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _modalTransition = [[DWModalPopupTransition alloc] initWithInteractiveTransitionAllowed:YES]; + + self.transitioningDelegate = self.modalTransition; + self.modalPresentationStyle = UIModalPresentationCustom; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + DWImgurInfoChildView *childView = [[DWImgurInfoChildView alloc] init]; + childView.translatesAutoresizingMaskIntoConstraints = NO; + childView.delegate = self; + [self.view addSubview:childView]; + + [NSLayoutConstraint activateConstraints:@[ + [childView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], + [childView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [childView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + ]]; +} + +#pragma mark DWImgurInfoChildViewDelegate + +- (void)imgurInfoChildViewAcceptAction:(DWImgurInfoChildView *)view { + [self.delegate imgurInfoViewControllerDidAccept:self]; +} + +- (void)imgurInfoChildViewCancelAction:(DWImgurInfoChildView *)view { + [self.delegate imgurInfoViewControllerDidCancel:self]; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurItemView.h b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurItemView.h new file mode 100644 index 000000000..cfab9343a --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurItemView.h @@ -0,0 +1,28 @@ +// +// 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 + +@interface DWImgurItemView : UIView + +@property (nullable, nonatomic, strong) NSAttributedString *text; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.m b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurItemView.m similarity index 50% rename from DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.m rename to DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurItemView.m index eb3b3a636..ca631e03b 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.m +++ b/DashPay/Presentation/Profile/EditProfile/Imgur/DWImgurItemView.m @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,41 +15,60 @@ // limitations under the License. // -#import "DWContactsSearchInfoHeaderView.h" +#import "DWImgurItemView.h" #import "DWUIKit.h" -@implementation DWContactsSearchInfoHeaderView +NS_ASSUME_NONNULL_BEGIN + +@interface DWImgurItemView () + +@property (readonly, nonatomic, strong) UILabel *label; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWImgurItemView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { - self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - UILabel *label = [[UILabel alloc] init]; label.translatesAutoresizingMaskIntoConstraints = NO; - label.backgroundColor = self.backgroundColor; label.textColor = [UIColor dw_darkTitleColor]; label.numberOfLines = 0; + label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; [self addSubview:label]; - _titleLabel = label; + _label = label; - [label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + UIImageView *imageView = [[UIImageView alloc] init]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.image = [UIImage imageNamed:@"icon_info"]; + [self addSubview:imageView]; - const CGFloat spacing = 10.0; - const CGFloat margin = 16.0; [NSLayoutConstraint activateConstraints:@[ - [label.topAnchor constraintEqualToAnchor:self.topAnchor - constant:spacing], - [label.leadingAnchor constraintEqualToAnchor:self.leadingAnchor - constant:margin], - [self.trailingAnchor constraintEqualToAnchor:label.trailingAnchor - constant:margin], - [self.bottomAnchor constraintEqualToAnchor:label.bottomAnchor - constant:spacing], + [imageView.topAnchor constraintEqualToAnchor:self.topAnchor], + [imageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [imageView.widthAnchor constraintEqualToConstant:20.0], + [imageView.heightAnchor constraintEqualToConstant:20.0], + + [label.leadingAnchor constraintEqualToAnchor:imageView.trailingAnchor + constant:12.0], + [label.topAnchor constraintEqualToAnchor:self.topAnchor], + [self.trailingAnchor constraintEqualToAnchor:label.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:label.bottomAnchor], ]]; } return self; } +- (NSAttributedString *)text { + return self.label.attributedText; +} + +- (void)setText:(NSAttributedString *)text { + self.label.attributedText = text; +} + @end diff --git a/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertChildView.h b/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertChildView.h new file mode 100644 index 000000000..ce8146673 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertChildView.h @@ -0,0 +1,37 @@ +// +// 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 DWSaveAlertChildView; + +@protocol DWSaveAlertChildViewDelegate + +- (void)saveAlertChildViewCancelAction:(DWSaveAlertChildView *)view; +- (void)saveAlertChildViewOKAction:(DWSaveAlertChildView *)view; + +@end + +@interface DWSaveAlertChildView : UIView + +@property (nullable, weak, nonatomic) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertChildView.m b/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertChildView.m new file mode 100644 index 000000000..d9acb1012 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertChildView.m @@ -0,0 +1,146 @@ +// +// 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 "DWSaveAlertChildView.h" +#import "DWActionButton.h" +#import "DWBorderedActionButton.h" +#import "DWUIKit.h" + +static CGFloat const ViewCornerRadius = 8.0; +static CGSize const IconSize = {36.0, 36.0}; +static CGFloat const ButtonHeight = 39.0; + +NS_ASSUME_NONNULL_BEGIN + +@interface DWSaveAlertChildView () + +@property (readonly, nonatomic, strong) UIImageView *iconImageView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) UILabel *subtitleLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWSaveAlertChildView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + UIImageView *iconImageView = [[UIImageView alloc] init]; + iconImageView.translatesAutoresizingMaskIntoConstraints = NO; + iconImageView.contentMode = UIViewContentModeScaleAspectFit; + iconImageView.image = [UIImage imageNamed:@"icon_exclamation_light"]; + [self addSubview:iconImageView]; + _iconImageView = iconImageView; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.numberOfLines = 0; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.text = NSLocalizedString(@"Save Changes", nil); + [self addSubview:titleLabel]; + _titleLabel = titleLabel; + + UILabel *subtitleLabel = [[UILabel alloc] init]; + subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; + subtitleLabel.numberOfLines = 0; + subtitleLabel.textAlignment = NSTextAlignmentCenter; + subtitleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + subtitleLabel.textColor = [UIColor dw_secondaryTextColor]; + subtitleLabel.text = NSLocalizedString(@"Would you like to save the changes you made to your profile?", nil); + [self addSubview:subtitleLabel]; + _subtitleLabel = subtitleLabel; + + DWActionButton *okButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + okButton.translatesAutoresizingMaskIntoConstraints = NO; + okButton.usedOnDarkBackground = NO; + okButton.small = YES; + okButton.inverted = NO; + [okButton setTitle:NSLocalizedString(@"Yes", nil) forState:UIControlStateNormal]; + [okButton addTarget:self + action:@selector(okButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + + DWBorderedActionButton *cancelButton = [[DWBorderedActionButton alloc] initWithFrame:CGRectZero]; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + [cancelButton setTitle:NSLocalizedString(@"No", nil) forState:UIControlStateNormal]; + [cancelButton addTarget:self + action:@selector(cancelButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ okButton, cancelButton ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisHorizontal; + stackView.distribution = UIStackViewDistributionFillEqually; + stackView.spacing = 8.0; + stackView.alignment = UIStackViewAlignmentCenter; + [self addSubview:stackView]; + + [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 forAxis:UILayoutConstraintAxisVertical]; + [subtitleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 forAxis:UILayoutConstraintAxisVertical]; + + [NSLayoutConstraint activateConstraints:@[ + [iconImageView.topAnchor constraintEqualToAnchor:self.topAnchor], + [iconImageView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [iconImageView.widthAnchor constraintEqualToConstant:IconSize.width], + [iconImageView.heightAnchor constraintEqualToConstant:IconSize.height], + + [titleLabel.topAnchor constraintEqualToAnchor:iconImageView.bottomAnchor + constant:8.0], + [titleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:8.0], + [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor + constant:8.0], + + [subtitleLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:16.0], + [subtitleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:8.0], + [self.trailingAnchor constraintEqualToAnchor:subtitleLabel.trailingAnchor + constant:8.0], + + [stackView.topAnchor constraintEqualToAnchor:subtitleLabel.bottomAnchor + constant:44.0], + [stackView.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.leadingAnchor + constant:8.0], + [self.trailingAnchor constraintGreaterThanOrEqualToAnchor:stackView.trailingAnchor + constant:8.0], + [self.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor], + [stackView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + + [okButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [cancelButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [stackView.heightAnchor constraintEqualToConstant:ButtonHeight], + ]]; + } + return self; +} + +- (void)okButtonAction { + [self.delegate saveAlertChildViewOKAction:self]; +} + +- (void)cancelButtonAction { + [self.delegate saveAlertChildViewCancelAction:self]; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertViewController.h b/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertViewController.h new file mode 100644 index 000000000..3ec65688e --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertViewController.h @@ -0,0 +1,37 @@ +// +// 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 DWSaveAlertViewController; + +@protocol DWSaveAlertViewControllerDelegate + +- (void)saveAlertViewControllerOKAction:(DWSaveAlertViewController *)controller; +- (void)saveAlertViewControllerCancelAction:(DWSaveAlertViewController *)controller; + +@end + +@interface DWSaveAlertViewController : UIViewController + +@property (weak, nonatomic) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertViewController.m b/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertViewController.m new file mode 100644 index 000000000..693b61f9a --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/SaveAlert/DWSaveAlertViewController.m @@ -0,0 +1,89 @@ +// +// 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 "DWSaveAlertViewController.h" + +#import "DWModalPopupTransition.h" +#import "DWSaveAlertChildView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWSaveAlertViewController () + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWSaveAlertViewController + +- (instancetype)init { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _modalTransition = [[DWModalPopupTransition alloc] initWithInteractiveTransitionAllowed:NO]; + + self.transitioningDelegate = self.modalTransition; + self.modalPresentationStyle = UIModalPresentationCustom; + } + return self; +} + +- (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]; + + DWSaveAlertChildView *childView = [[DWSaveAlertChildView alloc] init]; + childView.translatesAutoresizingMaskIntoConstraints = NO; + childView.delegate = self; + [contentView addSubview:childView]; + + [NSLayoutConstraint activateConstraints:@[ + [contentView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], + + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + + [childView.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:32.0], + [childView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [childView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:childView.bottomAnchor + constant:32.0], + ]]; +} + +#pragma mark - DWSaveAlertChildViewDelegate + +- (void)saveAlertChildViewCancelAction:(DWSaveAlertChildView *)view { + [self.delegate saveAlertViewControllerCancelAction:self]; +} + +- (void)saveAlertChildViewOKAction:(DWSaveAlertChildView *)view { + [self.delegate saveAlertViewControllerOKAction:self]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.h b/DashPay/Presentation/Profile/EditProfile/Upload/DWHourGlassAnimationView.h similarity index 94% rename from DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.h rename to DashPay/Presentation/Profile/EditProfile/Upload/DWHourGlassAnimationView.h index c88dc8b42..4d60c5b44 100644 --- a/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.h +++ b/DashPay/Presentation/Profile/EditProfile/Upload/DWHourGlassAnimationView.h @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWDashPayAnimationView : UIView +@interface DWHourGlassAnimationView : UIView - (void)startAnimating; - (void)stopAnimating; diff --git a/DashPay/Presentation/Profile/EditProfile/Upload/DWHourGlassAnimationView.m b/DashPay/Presentation/Profile/EditProfile/Upload/DWHourGlassAnimationView.m new file mode 100644 index 000000000..670a61318 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Upload/DWHourGlassAnimationView.m @@ -0,0 +1,103 @@ +// +// 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 "DWHourGlassAnimationView.h" + +NS_ASSUME_NONNULL_BEGIN + +static CFTimeInterval const Step = 1.2; + +@interface DWHourGlassAnimationView () + +@property (readonly, nonatomic, strong) CALayer *hgLayer; +@property (nonatomic, assign) BOOL animating; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWHourGlassAnimationView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + CALayer *hgLayer = [CALayer layer]; + hgLayer.contentsGravity = kCAGravityResizeAspect; + [self.layer addSublayer:hgLayer]; + _hgLayer = hgLayer; + } + return self; +} + +- (void)startAnimating { + if (self.animating) { + return; + } + self.animating = YES; + + self.hgLayer.contents = nil; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self performOneStep]; + }); +} + +- (void)stopAnimating { + self.animating = NO; + [self.hgLayer removeAllAnimations]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + CGSize size = self.bounds.size; + CGSize hgsize = CGSizeMake(18, 24); + self.hgLayer.frame = CGRectMake((size.width - hgsize.width) / 2, (size.height - hgsize.height) / 2, hgsize.width, hgsize.height); +} + +- (void)performOneStep { + const CFTimeInterval subStep = 0.235; + + CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"contents"]; + keyframeAnimation.values = @[ + (id)[UIImage imageNamed:@"hourglass_1"].CGImage, + (id)[UIImage imageNamed:@"hourglass_2"].CGImage, + (id)[UIImage imageNamed:@"hourglass_3"].CGImage, + (id)[UIImage imageNamed:@"hourglass_4"].CGImage, + (id)[UIImage imageNamed:@"hourglass_5"].CGImage, + ]; + keyframeAnimation.duration = Step; + keyframeAnimation.removedOnCompletion = NO; + keyframeAnimation.fillMode = kCAFillModeForwards; + + CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; + rotationAnimation.toValue = @(M_PI); + rotationAnimation.duration = subStep * 2; + rotationAnimation.beginTime = Step + subStep; + rotationAnimation.removedOnCompletion = NO; + rotationAnimation.fillMode = kCAFillModeForwards; + + CAAnimationGroup *groupAnimation = [CAAnimationGroup animation]; + groupAnimation.animations = @[ keyframeAnimation, rotationAnimation ]; + groupAnimation.duration = Step + subStep * 4; + groupAnimation.beginTime = CACurrentMediaTime(); + groupAnimation.repeatCount = HUGE_VALF; + groupAnimation.removedOnCompletion = NO; + + [self.hgLayer addAnimation:groupAnimation forKey:@"hg_animation"]; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarChildView.h b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarChildView.h new file mode 100644 index 000000000..94c926dfd --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarChildView.h @@ -0,0 +1,39 @@ +// +// 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 + +@class DWUploadAvatarModel; +@class DWUploadAvatarChildView; + +@protocol DWUploadAvatarChildViewDelegate + +- (void)uploadAvatarChildViewDidFinish:(DWUploadAvatarChildView *)view; +- (void)uploadAvatarChildViewDidCancel:(DWUploadAvatarChildView *)view; + +@end + +@interface DWUploadAvatarChildView : KVOUIView + +@property (nullable, nonatomic, strong) DWUploadAvatarModel *model; +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarChildView.m b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarChildView.m new file mode 100644 index 000000000..6f4224109 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarChildView.m @@ -0,0 +1,239 @@ +// +// 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 "DWUploadAvatarChildView.h" + +#import "DWActionButton.h" +#import "DWHourGlassAnimationView.h" +#import "DWUIKit.h" +#import "DWUploadAvatarModel.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const ViewCornerRadius = 8.0; +static CGSize const ImageSize = {87.0, 87.0}; +static CGSize const IconSize = {28.0, 28.0}; +static CGFloat const ButtonHeight = 39.0; + +@interface DWUploadAvatarChildView () + +@property (readonly, nonatomic, strong) UIImageView *imageView; +@property (readonly, nonatomic, strong) DWHourGlassAnimationView *animationView; +@property (readonly, nonatomic, strong) UIImageView *errorImageView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) UILabel *subtitleLabel; +@property (readonly, strong, nonatomic) UIButton *cancelButton; +@property (readonly, strong, nonatomic) UIButton *retryButton; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUploadAvatarChildView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + self.clipsToBounds = YES; + self.layer.cornerRadius = ViewCornerRadius; + + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = self.backgroundColor; + + UIImageView *imageView = [[UIImageView alloc] init]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.layer.cornerRadius = ImageSize.width / 2.0; + imageView.layer.masksToBounds = YES; + [contentView addSubview:imageView]; + _imageView = imageView; + + UIView *titleContentView = [[UIView alloc] init]; + titleContentView.translatesAutoresizingMaskIntoConstraints = NO; + titleContentView.backgroundColor = self.backgroundColor; + [contentView addSubview:titleContentView]; + + UIView *iconView = [[UIView alloc] init]; + iconView.translatesAutoresizingMaskIntoConstraints = NO; + [titleContentView addSubview:iconView]; + + DWHourGlassAnimationView *animationView = [[DWHourGlassAnimationView alloc] initWithFrame:CGRectZero]; + animationView.translatesAutoresizingMaskIntoConstraints = NO; + [iconView addSubview:animationView]; + _animationView = animationView; + + UIImageView *errorImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_error"]]; + errorImageView.translatesAutoresizingMaskIntoConstraints = NO; + errorImageView.contentMode = UIViewContentModeScaleAspectFit; + [iconView addSubview:errorImageView]; + _errorImageView = errorImageView; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.adjustsFontSizeToFitWidth = YES; + [titleContentView addSubview:titleLabel]; + _titleLabel = titleLabel; + + UILabel *subtitleLabel = [[UILabel alloc] init]; + subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; + subtitleLabel.numberOfLines = 0; + subtitleLabel.textColor = [UIColor dw_secondaryTextColor]; + subtitleLabel.textAlignment = NSTextAlignmentCenter; + subtitleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + subtitleLabel.adjustsFontForContentSizeCategory = YES; + subtitleLabel.adjustsFontSizeToFitWidth = YES; + [contentView addSubview:subtitleLabel]; + _subtitleLabel = subtitleLabel; + + DWActionButton *cancelButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + cancelButton.usedOnDarkBackground = NO; + cancelButton.small = YES; + cancelButton.inverted = YES; + [cancelButton setTitle:NSLocalizedString(@"Cancel", nil) forState:UIControlStateNormal]; + [cancelButton addTarget:self + action:@selector(cancelButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + _cancelButton = cancelButton; + + DWActionButton *retryButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + retryButton.translatesAutoresizingMaskIntoConstraints = NO; + retryButton.usedOnDarkBackground = NO; + retryButton.small = YES; + retryButton.inverted = NO; + [retryButton setTitle:NSLocalizedString(@"Try again", nil) forState:UIControlStateNormal]; + [retryButton addTarget:self + action:@selector(retryButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + _retryButton = retryButton; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ contentView, cancelButton, retryButton ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.spacing = 54.0; + stackView.alignment = UIStackViewAlignmentCenter; + [self addSubview:stackView]; + + const CGFloat padding = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [stackView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], + [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:stackView.bottomAnchor], + [stackView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor + constant:padding], + + [imageView.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:padding], + [imageView.widthAnchor constraintEqualToConstant:ImageSize.width], + [imageView.heightAnchor constraintEqualToConstant:ImageSize.height], + [imageView.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + + [titleContentView.topAnchor constraintEqualToAnchor:imageView.bottomAnchor + constant:12], + [titleContentView.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + [titleContentView.leadingAnchor constraintGreaterThanOrEqualToAnchor:contentView.leadingAnchor], + [contentView.trailingAnchor constraintGreaterThanOrEqualToAnchor:titleContentView.trailingAnchor], + + [iconView.topAnchor constraintEqualToAnchor:titleContentView.topAnchor], + [iconView.leadingAnchor constraintEqualToAnchor:titleContentView.leadingAnchor], + [titleContentView.bottomAnchor constraintEqualToAnchor:iconView.bottomAnchor], + [iconView.widthAnchor constraintEqualToConstant:IconSize.width], + [iconView.heightAnchor constraintEqualToConstant:IconSize.height], + + [animationView.topAnchor constraintEqualToAnchor:iconView.topAnchor], + [animationView.leadingAnchor constraintEqualToAnchor:iconView.leadingAnchor], + [iconView.trailingAnchor constraintEqualToAnchor:animationView.trailingAnchor], + [iconView.bottomAnchor constraintEqualToAnchor:animationView.bottomAnchor], + + [errorImageView.topAnchor constraintEqualToAnchor:iconView.topAnchor], + [errorImageView.leadingAnchor constraintEqualToAnchor:iconView.leadingAnchor], + [iconView.trailingAnchor constraintEqualToAnchor:errorImageView.trailingAnchor], + [iconView.bottomAnchor constraintEqualToAnchor:errorImageView.bottomAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:titleContentView.topAnchor], + [titleLabel.leadingAnchor constraintEqualToAnchor:iconView.trailingAnchor], + [titleContentView.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor], + [titleContentView.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + + [subtitleLabel.topAnchor constraintEqualToAnchor:titleContentView.bottomAnchor + constant:2], + [subtitleLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:subtitleLabel.trailingAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:subtitleLabel.bottomAnchor], + + [retryButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [cancelButton.heightAnchor constraintEqualToConstant:ButtonHeight], + ]]; + + [self mvvm_observe:DW_KEYPATH(self, model.state) + with:^(typeof(self) self, id value) { + switch (self.model.state) { + case DWUploadAvatarModelState_Loading: + self.titleLabel.text = NSLocalizedString(@"Please Wait", nil); + self.subtitleLabel.text = NSLocalizedString(@"Uploading your picture to the network", nil); + self.retryButton.hidden = YES; + self.cancelButton.hidden = NO; + self.errorImageView.hidden = YES; + + self.animationView.hidden = NO; + [self.animationView startAnimating]; + + break; + case DWUploadAvatarModelState_Error: + self.titleLabel.text = NSLocalizedString(@"Upload Error", nil); + self.subtitleLabel.text = NSLocalizedString(@"Unable to upload your picture. Please try again.", nil); + self.retryButton.hidden = NO; + self.cancelButton.hidden = YES; + self.errorImageView.hidden = NO; + + [self.animationView stopAnimating]; + self.animationView.hidden = YES; + + break; + case DWUploadAvatarModelState_Success: + [self.delegate uploadAvatarChildViewDidFinish:self]; + break; + } + }]; + } + return self; +} + +- (void)setModel:(DWUploadAvatarModel *)model { + _model = model; + + self.imageView.image = model.image; +} + +- (void)cancelButtonAction:(UIButton *)sender { + [self.model cancel]; + [self.delegate uploadAvatarChildViewDidCancel:self]; +} + +- (void)retryButtonAction:(UIButton *)sender { + [self.model retry]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.h b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarModel.h similarity index 58% rename from DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.h rename to DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarModel.h index a331fd1e5..b02368a50 100644 --- a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.h +++ b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarModel.h @@ -19,22 +19,25 @@ NS_ASSUME_NONNULL_BEGIN -extern NSNotificationName const DWNotificationsProviderWillUpdateNotification; -extern NSNotificationName const DWNotificationsProviderDidUpdateNotification; +@class UIImage; -@class DWNotificationsData; -@class NSManagedObjectID; +typedef NS_ENUM(NSUInteger, DWUploadAvatarModelState) { + DWUploadAvatarModelState_Loading, + DWUploadAvatarModelState_Error, + DWUploadAvatarModelState_Success, +}; -@interface DWNotificationsProvider : NSObject +@interface DWUploadAvatarModel : NSObject -@property (readonly, nonatomic, copy) DWNotificationsData *data; +@property (readonly, nonatomic, assign) DWUploadAvatarModelState state; +@property (readonly, nonatomic, strong) UIImage *image; -+ (instancetype)sharedInstance; +@property (readonly, nullable, nonatomic, copy) NSString *resultURLString; -- (void)forceUpdate; +- (instancetype)initWithImage:(UIImage *)image; -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; +- (void)retry; +- (void)cancel; @end diff --git a/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarModel.m b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarModel.m new file mode 100644 index 000000000..3ec405ee2 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarModel.m @@ -0,0 +1,169 @@ +// +// 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 "DWUploadAvatarModel.h" + +#import "DWEnvironment.h" +#import "UIImage+Utils.h" + +NS_ASSUME_NONNULL_BEGIN + +NSString *const ImageDeleteHash = @"ImgurImageDeleteHash"; + +@interface DWUploadAvatarModel () + +@property (nonatomic, assign) DWUploadAvatarModelState state; + +@property (atomic, assign) BOOL cancelled; +@property (nullable, nonatomic, copy) NSString *resultURLString; +@property (nullable, weak, nonatomic) id uploadOperation; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUploadAvatarModel + +- (instancetype)initWithImage:(UIImage *)image { + self = [super init]; + if (self) { + _image = image; + + [self retry]; + } + return self; +} + +- (void)retry { + self.cancelled = NO; + self.state = DWUploadAvatarModelState_Loading; + + NSString *imgurClientID = @"imgurId"; //TODO: DashPay + + NSString *deleteHash = [[NSUserDefaults standardUserDefaults] stringForKey:ImageDeleteHash]; + if (deleteHash.length > 0) { + NSString *urlString = [NSString stringWithFormat:@"https://api.imgur.com/3/image/%@", deleteHash]; + NSURL *url = [NSURL URLWithString:urlString]; + HTTPRequest *request = [HTTPRequest requestWithURL:url method:HTTPRequestMethod_DELETE parameters:nil]; + [request addValue:[NSString stringWithFormat:@"Client-ID %@", imgurClientID] forHeader:@"Authorization"]; + request.maximumRetryCount = 3; + + HTTPLoaderManager *loaderManager = [DSNetworkingCoordinator sharedInstance].loaderManager; + __weak typeof(self) weakSelf = self; + [loaderManager + sendRequest:request + completion:^(id _Nullable parsedData, NSDictionary *_Nullable responseHeaders, NSInteger statusCode, NSError *_Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [strongSelf upload]; + }); + }]; + } + else { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self upload]; + }); + } +} + +- (void)cancel { + self.cancelled = YES; + [self.uploadOperation cancel]; +} + +- (void)upload { + if (self.cancelled) { + return; + } + + const CGFloat maxImageSide = 600; + UIImage *resultImage = self.image; + if (self.image.size.width > maxImageSide || self.image.size.height > maxImageSide) { + resultImage = [self.image dw_resize:CGSizeMake(maxImageSide, maxImageSide) + withInterpolationQuality:kCGInterpolationHigh]; + } + + NSURL *url = [NSURL URLWithString:@"https://api.imgur.com/3/upload"]; + NSString *imgurClientID = @"imgurId"; //TODO: DashPay + + NSString *boundary = [NSUUID UUID].UUIDString; + NSData *body = [self createBodyWithBoundary:boundary image:resultImage]; + HTTPRequest *request = [[HTTPRequest alloc] initWithURL:url method:HTTPRequestMethod_POST contentType:HTTPContentType_JSON parameters:nil body:body sourceIdentifier:nil]; + [request addValue:[NSString stringWithFormat:@"Client-ID %@", imgurClientID] + forHeader:@"Authorization"]; + [request addValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] + forHeader:@"Content-Type"]; + + HTTPLoaderManager *loaderManager = [DSNetworkingCoordinator sharedInstance].loaderManager; + + __weak typeof(self) weakSelf = self; + self.uploadOperation = [loaderManager + sendRequest:request + completion:^(id _Nullable parsedData, NSDictionary *_Nullable responseHeaders, NSInteger statusCode, NSError *_Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (error) { + strongSelf.state = DWUploadAvatarModelState_Error; + } + else { + NSDictionary *response = (NSDictionary *)parsedData; + if ([response[@"success"] boolValue]) { + NSDictionary *data = response[@"data"]; + + NSDictionary *deleteHash = data[@"deletehash"]; + [[NSUserDefaults standardUserDefaults] setObject:deleteHash forKey:ImageDeleteHash]; + + strongSelf.resultURLString = data[@"link"]; + + strongSelf.state = DWUploadAvatarModelState_Success; + } + else { + strongSelf.state = DWUploadAvatarModelState_Error; + } + } + }]; +} + +- (NSData *)createBodyWithBoundary:(NSString *)boundary + image:(UIImage *)image { + NSMutableData *httpBody = [NSMutableData data]; + + NSString *fieldName = @"image"; // Imgur field + + NSString *filename = @"image.jpg"; + NSData *data = UIImageJPEGRepresentation(image, 0.5); + NSString *mimetype = @"image/jpeg"; + + [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, filename] dataUsingEncoding:NSUTF8StringEncoding]]; + [httpBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype] dataUsingEncoding:NSUTF8StringEncoding]]; + [httpBody appendData:data]; + [httpBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + + [httpBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + + return httpBody; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWCreateUsernameViewController.h b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarViewController.h similarity index 63% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWCreateUsernameViewController.h rename to DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarViewController.h index 5199baa0f..14219b23b 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWCreateUsernameViewController.h +++ b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarViewController.h @@ -17,26 +17,23 @@ #import -#import "DWDashPayProtocol.h" - NS_ASSUME_NONNULL_BEGIN -@class DWCreateUsernameViewController; +@class DWUploadAvatarViewController; -@protocol DWCreateUsernameViewControllerDelegate +@protocol DWUploadAvatarViewControllerDelegate -- (void)createUsernameViewController:(DWCreateUsernameViewController *)controller - registerUsername:(NSString *)username; +- (void)uploadAvatarViewControllerDidCancel:(DWUploadAvatarViewController *)controller; +- (void)uploadAvatarViewController:(DWUploadAvatarViewController *)controller didFinishWithURLString:(NSString *)urlString; @end -@interface DWCreateUsernameViewController : UIViewController - -@property (nullable, nonatomic, weak) id delegate; +@interface DWUploadAvatarViewController : UIViewController -- (NSAttributedString *)attributedTitle; +@property (readonly, nonatomic, strong) UIImage *image; +@property (nullable, nonatomic, weak) id delegate; -- (instancetype)initWithDashPayModel:(id)dashPayModel; +- (instancetype)initWithImage:(UIImage *)image; - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; diff --git a/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarViewController.m b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarViewController.m new file mode 100644 index 000000000..77841a6e2 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Upload/DWUploadAvatarViewController.m @@ -0,0 +1,92 @@ +// +// 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 "DWUploadAvatarViewController.h" + +#import "DWUIKit.h" +#import "DWUploadAvatarChildView.h" +#import "DWUploadAvatarModel.h" + +static CGFloat VerticalPadding(void) { + if (IS_IPAD) { + return 32.0; + } + else if (IS_IPHONE_6 || IS_IPHONE_5_OR_LESS) { + return 16.0; + } + else { + return 24.0; + } +} + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUploadAvatarViewController () + +@property (nonatomic, strong) DWUploadAvatarModel *model; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUploadAvatarViewController + +- (instancetype)initWithImage:(UIImage *)image { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _model = [[DWUploadAvatarModel alloc] initWithImage:image]; + } + return self; +} + +- (UIImage *)image { + return self.model.image; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor clearColor]; + + UIView *contentView = self.view; + + DWUploadAvatarChildView *childView = [[DWUploadAvatarChildView alloc] initWithFrame:CGRectZero]; + childView.model = self.model; + childView.delegate = self; + childView.translatesAutoresizingMaskIntoConstraints = NO; + [contentView addSubview:childView]; + + const CGFloat padding = VerticalPadding(); + [NSLayoutConstraint activateConstraints:@[ + [childView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [childView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [childView.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor], + [childView.heightAnchor constraintEqualToAnchor:childView.widthAnchor], + ]]; +} + +#pragma mark - DWUploadAvatarChildViewDelegate + +- (void)uploadAvatarChildViewDidFinish:(DWUploadAvatarChildView *)view { + [self.delegate uploadAvatarViewController:self didFinishWithURLString:self.model.resultURLString]; +} + +- (void)uploadAvatarChildViewDidCancel:(DWUploadAvatarChildView *)view { + [self.delegate uploadAvatarViewControllerDidCancel:self]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.h b/DashPay/Presentation/Profile/EditProfile/Utils/DWFaceDetector.h similarity index 79% rename from DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.h rename to DashPay/Presentation/Profile/EditProfile/Utils/DWFaceDetector.h index fb2767948..d5d3b4ada 100644 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.h +++ b/DashPay/Presentation/Profile/EditProfile/Utils/DWFaceDetector.h @@ -19,12 +19,10 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWUserProfileNavigationTitleView : UIView +@interface DWFaceDetector : NSObject -- (void)updateWithUsername:(NSString *)username; -- (void)setScrollingPercent:(float)percent; +- (instancetype)initWithImage:(UIImage *)image completion:(void (^)(CGRect roi))completion; -- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; diff --git a/DashPay/Presentation/Profile/EditProfile/Utils/DWFaceDetector.m b/DashPay/Presentation/Profile/EditProfile/Utils/DWFaceDetector.m new file mode 100644 index 000000000..f47bb8a0a --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Utils/DWFaceDetector.m @@ -0,0 +1,95 @@ +// +// 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 "DWFaceDetector.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +static CGImagePropertyOrientation UIImageOrientationToCGImageOrientation(UIImageOrientation orientation) { + switch (orientation) { + case UIImageOrientationUp: + return kCGImagePropertyOrientationUp; + case UIImageOrientationDown: + return kCGImagePropertyOrientationDown; + case UIImageOrientationRight: + return kCGImagePropertyOrientationRight; + case UIImageOrientationUpMirrored: + return kCGImagePropertyOrientationUpMirrored; + case UIImageOrientationDownMirrored: + return kCGImagePropertyOrientationDownMirrored; + case UIImageOrientationLeftMirrored: + return kCGImagePropertyOrientationLeftMirrored; + case UIImageOrientationRightMirrored: + return kCGImagePropertyOrientationRightMirrored; + default: + return kCGImagePropertyOrientationUp; + } +} + +@interface DWFaceDetector () + +@property (nonatomic, readonly, strong) VNDetectFaceRectanglesRequest *request; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWFaceDetector + +- (instancetype)initWithImage:(UIImage *)image completion:(void (^)(CGRect roi))completion { + NSParameterAssert(image.CGImage); + + self = [super init]; + if (self) { + CGSize imageSize = image.size; + _request = [[VNDetectFaceRectanglesRequest alloc] initWithCompletionHandler:^(VNRequest *_Nonnull request, NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + VNFaceObservation *observation = request.results.firstObject; + if (error != nil || ![observation isKindOfClass:VNFaceObservation.class]) { + completion(CGRectZero); + } + else { + CGRect rect = observation.boundingBox; + CGFloat x = rect.origin.x * imageSize.width; + CGFloat w = rect.size.width * imageSize.width; + CGFloat h = rect.size.height * imageSize.height; + CGFloat y = imageSize.height * (1 - rect.origin.y) - h; + completion(CGRectMake(x, y, w, h)); + } + }); + }]; + + VNImageRequestHandler *requestHandler = + [[VNImageRequestHandler alloc] initWithCGImage:image.CGImage + orientation:UIImageOrientationToCGImageOrientation(image.imageOrientation) + options:@{}]; + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + NSError *error = nil; + [requestHandler performRequests:@[ self.request ] error:&error]; + if (error != nil) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(CGRectZero); + }); + } + }); + } + return self; +} + +@end diff --git a/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextFieldFormCellModel.h b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextFieldFormCellModel.h new file mode 100644 index 000000000..65b8dd36e --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextFieldFormCellModel.h @@ -0,0 +1,62 @@ +// +// Created by Andrew Podkovyrin +// Copyright © 2018 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 "DWBaseFormCellModel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWTextFieldFormValidationResult : NSObject + +@property (readonly, nonatomic, assign, getter=isErrored) BOOL errored; +@property (readonly, nonatomic, copy) NSString *info; + +- (instancetype)initWithInfo:(NSString *)info; +- (instancetype)initWithError:(NSString *)info; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +@interface DWTextFieldFormCellModel : DWBaseFormCellModel + +@property (nullable, copy, nonatomic) NSString *placeholder; +@property (nullable, copy, nonatomic) NSString *text; + +@property (nullable, copy, nonatomic) void (^didChangeValueBlock)(DWTextFieldFormCellModel *cellModel); +@property (nullable, copy, nonatomic) void (^didReturnValueBlock)(DWTextFieldFormCellModel *cellModel); + +// Some of UITextInputTraits protocol params +@property (assign, nonatomic) UITextAutocapitalizationType autocapitalizationType; +@property (assign, nonatomic) UITextAutocorrectionType autocorrectionType; +@property (assign, nonatomic) UIKeyboardType keyboardType; +@property (assign, nonatomic) UIReturnKeyType returnKeyType; +@property (assign, nonatomic) BOOL enablesReturnKeyAutomatically; +@property (assign, nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; + +- (instancetype)initWithTitle:(nullable NSString *)title placeholder:(nullable NSString *)placeholder NS_DESIGNATED_INITIALIZER; + +- (BOOL)validateReplacementString:(NSString *)string text:(nullable NSString *)text; + +/// Non-null! +- (DWTextFieldFormValidationResult *)postValidate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextFieldFormCellModel.m b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextFieldFormCellModel.m new file mode 100644 index 000000000..40e274269 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextFieldFormCellModel.m @@ -0,0 +1,68 @@ +// +// Created by Andrew Podkovyrin +// Copyright © 2018 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 "DWTextFieldFormCellModel.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation DWTextFieldFormValidationResult + +- (instancetype)initWithInfo:(NSString *)info { + self = [super init]; + if (self) { + _info = info; + } + return self; +} + +- (instancetype)initWithError:(NSString *)info { + self = [super init]; + if (self) { + _info = info; + _errored = YES; + } + return self; +} + + +@end + +@implementation DWTextFieldFormCellModel + +- (instancetype)initWithTitle:(nullable NSString *)title placeholder:(nullable NSString *)placeholder { + self = [super initWithTitle:title]; + if (self) { + _placeholder = [placeholder copy]; + } + return self; +} + +- (instancetype)initWithTitle:(nullable NSString *)title { + return [self initWithTitle:title placeholder:nil]; +} + +- (BOOL)validateReplacementString:(NSString *)string text:(nullable NSString *)text { + return YES; +} + +- (DWTextFieldFormValidationResult *)postValidate { + return [[DWTextFieldFormValidationResult alloc] initWithInfo:@""]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextInputFormTableViewCell.h b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextInputFormTableViewCell.h new file mode 100644 index 000000000..3687039b8 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextInputFormTableViewCell.h @@ -0,0 +1,28 @@ +// +// 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 + +@protocol DWTextInputFormTableViewCell + +- (void)textInputBecomeFirstResponder; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextViewFormCellModel.h b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextViewFormCellModel.h new file mode 100644 index 000000000..c1683361e --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextViewFormCellModel.h @@ -0,0 +1,26 @@ +// +// 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 "DWTextFieldFormCellModel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWTextViewFormCellModel : DWTextFieldFormCellModel + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.h b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextViewFormCellModel.m similarity index 82% rename from DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.h rename to DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextViewFormCellModel.m index 2261a2e79..8a4738564 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.h +++ b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/Base/DWTextViewFormCellModel.m @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,11 +15,11 @@ // limitations under the License. // -#import "DWDPBasicCell.h" +#import "DWTextViewFormCellModel.h" NS_ASSUME_NONNULL_BEGIN -@interface DWDPImageStatusCell : DWDPBasicCell +@implementation DWTextViewFormCellModel @end diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPRespondedRequestItem.h b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileAboutCellModel.h similarity index 87% rename from DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPRespondedRequestItem.h rename to DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileAboutCellModel.h index 78f80b7c2..2dbd659f4 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPRespondedRequestItem.h +++ b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileAboutCellModel.h @@ -15,11 +15,11 @@ // limitations under the License. // -#import "DWDPBasicUserItem.h" +#import "DWTextViewFormCellModel.h" NS_ASSUME_NONNULL_BEGIN -@protocol DWDPRespondedRequestItem +@interface DWProfileAboutCellModel : DWTextViewFormCellModel @end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.m b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileAboutCellModel.m similarity index 54% rename from DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.m rename to DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileAboutCellModel.m index a3980e54f..6414185f4 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.m +++ b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileAboutCellModel.m @@ -15,29 +15,19 @@ // limitations under the License. // -#import "DWRequestsModel.h" +#import "DWProfileAboutCellModel.h" -#import "DWBaseContactsModel+DWProtected.h" +@implementation DWProfileAboutCellModel -@implementation DWRequestsModel - -@synthesize requestsDataSource = _requestsDataSource; - -- (instancetype)initWithRequestsDataSource:(DWFetchedResultsDataSource *)requestsDataSource { - self = [super init]; - if (self) { - _requestsDataSource = requestsDataSource; +- (DWTextFieldFormValidationResult *)postValidate { + NSUInteger len = self.text.length; + const NSUInteger max = 250; + NSString *info = [NSString stringWithFormat:NSLocalizedString(@"%ld/%ld Characters", @"10/20 Characters"), + len, max]; + if (len > max) { + return [[DWTextFieldFormValidationResult alloc] initWithError:info]; } - return self; -} - -- (DWFetchedResultsDataSource *)contactsDataSource { - // Ignored requests are not implemented - return nil; -} - -- (BOOL)shouldFetchData { - return NO; + return [[DWTextFieldFormValidationResult alloc] initWithInfo:info]; } @end diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPPendingRequestItem.h b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileDisplayNameCellModel.h similarity index 86% rename from DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPPendingRequestItem.h rename to DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileDisplayNameCellModel.h index 44d3526a5..7088e68ae 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPPendingRequestItem.h +++ b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileDisplayNameCellModel.h @@ -15,11 +15,11 @@ // limitations under the License. // -#import "DWDPBasicUserItem.h" +#import "DWTextFieldFormCellModel.h" NS_ASSUME_NONNULL_BEGIN -@protocol DWDPPendingRequestItem +@interface DWProfileDisplayNameCellModel : DWTextFieldFormCellModel @end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.m b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileDisplayNameCellModel.m similarity index 54% rename from DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.m rename to DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileDisplayNameCellModel.m index f00f7c000..e0eb839f7 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.m +++ b/DashPay/Presentation/Profile/EditProfile/Views/CellModels/DWProfileDisplayNameCellModel.m @@ -15,19 +15,19 @@ // limitations under the License. // -#import "DWDPEstablishedContactObject.h" +#import "DWProfileDisplayNameCellModel.h" -#import +@implementation DWProfileDisplayNameCellModel -@implementation DWDPEstablishedContactObject - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - self = [super init]; - if (self) { - self.blockchainIdentity = blockchainIdentity; - self.username = blockchainIdentity.currentDashpayUsername; +- (DWTextFieldFormValidationResult *)postValidate { + NSUInteger len = self.text.length; + const NSUInteger max = 25; + NSString *info = [NSString stringWithFormat:NSLocalizedString(@"%ld/%ld Characters", @"10/20 Characters"), + len, max]; + if (len > max) { + return [[DWTextFieldFormValidationResult alloc] initWithError:info]; } - return self; + return [[DWTextFieldFormValidationResult alloc] initWithInfo:info]; } @end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWInputUsernameViewController.h b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileAvatarView.h similarity index 60% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWInputUsernameViewController.h rename to DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileAvatarView.h index 9b34ea0cd..54880e80d 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWInputUsernameViewController.h +++ b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileAvatarView.h @@ -19,18 +19,22 @@ NS_ASSUME_NONNULL_BEGIN -@class DWInputUsernameViewController; +@class DWEditProfileAvatarView; +@class DSBlockchainIdentity; -@protocol DWInputUsernameViewControllerDelegate +@protocol DWEditProfileAvatarViewDelegate -- (void)inputUsernameViewControllerRegisterAction:(DWInputUsernameViewController *)controller; +- (void)editProfileAvatarView:(DWEditProfileAvatarView *)view editAvatarAction:(UIButton *)sender; @end -@interface DWInputUsernameViewController : UIViewController +@interface DWEditProfileAvatarView : UIView -@property (nullable, nonatomic, copy) NSString *text; -@property (nullable, nonatomic, weak) id delegate; +@property (nullable, nonatomic, strong) UIImage *image; + +@property (nullable, nonatomic, weak) id delegate; + +- (void)setImageWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; @end diff --git a/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileAvatarView.m b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileAvatarView.m new file mode 100644 index 000000000..53354ba45 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileAvatarView.m @@ -0,0 +1,105 @@ +// +// 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 "DWEditProfileAvatarView.h" + +#import "UIImageView+DWDPAvatar.h" +#import + +static CGFloat const AvatarSize = 134.0; +static CGSize const EditSize = {46.0, 45.0}; + +NS_ASSUME_NONNULL_BEGIN + +@interface DWEditProfileAvatarView () + +@property (readonly, nonatomic, strong) UIImageView *avatarImageView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWEditProfileAvatarView + +- (UIImage *)image { + return self.avatarImageView.image; +} + +- (void)setImage:(UIImage *)image { + self.avatarImageView.image = image; +} + +- (void)setImageWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + __weak typeof(self) weakSelf = self; + [self.avatarImageView dw_setAvatarWithURLString:blockchainIdentity.avatarPath + completion:^(UIImage *_Nullable image) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (image) { + strongSelf.avatarImageView.image = image; + } + else { + strongSelf.avatarImageView.image = [UIImage imageNamed:@"dp_current_user_placeholder"]; + } + }]; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UIImageView *avatarImageView = [[UIImageView alloc] init]; + avatarImageView.translatesAutoresizingMaskIntoConstraints = NO; + avatarImageView.image = [UIImage imageNamed:@"dp_current_user_placeholder"]; + avatarImageView.layer.cornerRadius = AvatarSize / 2.0; + avatarImageView.layer.masksToBounds = YES; + [self addSubview:avatarImageView]; + _avatarImageView = avatarImageView; + + UIButton *editButton = [UIButton buttonWithType:UIButtonTypeCustom]; + editButton.translatesAutoresizingMaskIntoConstraints = NO; + [editButton setImage:[UIImage imageNamed:@"dp_avatar_edit"] forState:UIControlStateNormal]; + [editButton addTarget:self action:@selector(editButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:editButton]; + + const CGFloat topSpacing = 32.0; + const CGFloat bottomSpacing = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [avatarImageView.topAnchor constraintEqualToAnchor:self.topAnchor + constant:topSpacing], + [avatarImageView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [avatarImageView.widthAnchor constraintEqualToConstant:AvatarSize], + [avatarImageView.heightAnchor constraintEqualToConstant:AvatarSize], + [self.bottomAnchor constraintEqualToAnchor:avatarImageView.bottomAnchor + constant:bottomSpacing], + + [editButton.trailingAnchor constraintEqualToAnchor:avatarImageView.trailingAnchor], + [editButton.bottomAnchor constraintEqualToAnchor:avatarImageView.bottomAnchor], + [editButton.widthAnchor constraintEqualToConstant:EditSize.width], + [editButton.heightAnchor constraintEqualToConstant:EditSize.height], + ]]; + } + return self; +} + +- (void)editButtonAction:(UIButton *)sender { + [self.delegate editProfileAvatarView:self editAvatarAction:sender]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.h b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileBaseCell.h similarity index 57% rename from DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.h rename to DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileBaseCell.h index 9dcaa710e..3803439e7 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.h +++ b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileBaseCell.h @@ -15,20 +15,22 @@ // limitations under the License. // -#import "DWFetchedResultsDataSource.h" +#import -NS_ASSUME_NONNULL_BEGIN +#import "DWTextFieldFormCellModel.h" +#import "DWTextInputFormTableViewCell.h" -@class DSBlockchainIdentity; +NS_ASSUME_NONNULL_BEGIN -@interface DWIncomingFetchedDataSource : DWFetchedResultsDataSource +@interface DWEditProfileBaseCell : KVOUITableViewCell -@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) UIView *inputContentView; +@property (readonly, nonatomic, strong) UILabel *validationLabel; -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - inContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; +- (void)textInputBecomeFirstResponder; -- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_UNAVAILABLE; +- (void)provideValidationResult:(DWTextFieldFormValidationResult *)validationResult; @end diff --git a/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileBaseCell.m b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileBaseCell.m new file mode 100644 index 000000000..f381ee69d --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileBaseCell.m @@ -0,0 +1,114 @@ +// +// 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 "DWEditProfileBaseCell.h" + +#import "DWShadowView.h" +#import "DWUIKit.h" + + +NS_ASSUME_NONNULL_BEGIN + +@interface DWEditProfileBaseCell () + + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWEditProfileBaseCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; + titleLabel.textColor = [UIColor dw_secondaryTextColor]; + titleLabel.numberOfLines = 0; + titleLabel.adjustsFontSizeToFitWidth = YES; + [self.contentView addSubview:titleLabel]; + _titleLabel = titleLabel; + + DWShadowView *shadowView = [[DWShadowView alloc] initWithFrame:CGRectZero]; + shadowView.translatesAutoresizingMaskIntoConstraints = NO; + [self.contentView addSubview:shadowView]; + + UIView *inputContentView = [[UIView alloc] initWithFrame:CGRectZero]; + inputContentView.translatesAutoresizingMaskIntoConstraints = NO; + inputContentView.backgroundColor = [UIColor dw_backgroundColor]; + inputContentView.layer.cornerRadius = 8.0; + inputContentView.layer.masksToBounds = YES; + [shadowView addSubview:inputContentView]; + _inputContentView = inputContentView; + + UILabel *validationLabel = [[UILabel alloc] init]; + validationLabel.translatesAutoresizingMaskIntoConstraints = NO; + validationLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; + validationLabel.textColor = [UIColor dw_secondaryTextColor]; + validationLabel.numberOfLines = 0; + validationLabel.adjustsFontSizeToFitWidth = YES; + [self.contentView addSubview:validationLabel]; + _validationLabel = validationLabel; + + const CGFloat verticalPadding = 5.0; + + UILayoutGuide *guide = self.contentView.layoutMarginsGuide; + const CGFloat spacing = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [titleLabel.topAnchor constraintEqualToAnchor:self.contentView.topAnchor + constant:verticalPadding], + [titleLabel.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + + [shadowView.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:spacing], + [shadowView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:shadowView.trailingAnchor], + + [validationLabel.topAnchor constraintEqualToAnchor:shadowView.bottomAnchor + constant:spacing], + [validationLabel.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:validationLabel.trailingAnchor], + [self.contentView.bottomAnchor constraintEqualToAnchor:validationLabel.bottomAnchor + constant:verticalPadding], + + [inputContentView.topAnchor constraintEqualToAnchor:shadowView.topAnchor], + [inputContentView.leadingAnchor constraintEqualToAnchor:shadowView.leadingAnchor], + [shadowView.trailingAnchor constraintEqualToAnchor:inputContentView.trailingAnchor], + [shadowView.bottomAnchor constraintEqualToAnchor:inputContentView.bottomAnchor], + ]]; + } + return self; +} + +- (void)textInputBecomeFirstResponder { +} + +- (void)provideValidationResult:(DWTextFieldFormValidationResult *)validationResult { + self.validationLabel.textColor = validationResult.isErrored ? [UIColor dw_redColor] : [UIColor dw_secondaryTextColor]; + if (validationResult.isErrored || self.isFirstResponder) { + self.validationLabel.text = validationResult.info; + } + else { + self.validationLabel.text = @" "; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.h b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextFieldCell.h similarity index 57% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.h rename to DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextFieldCell.h index af579bdca..0fccfefcf 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.h +++ b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextFieldCell.h @@ -15,24 +15,24 @@ // limitations under the License. // -#import "DWUsernameValidationRule.h" +#import "DWEditProfileBaseCell.h" + +#import "DWTextFieldFormCellModel.h" NS_ASSUME_NONNULL_BEGIN -@class DWCheckExistenceUsernameValidationRule; +@class DWEditProfileTextFieldCell; -@protocol DWCheckExistenceUsernameValidationRuleDelegate +@protocol DWEditProfileTextFieldCellDelegate -- (void)checkExistenceUsernameValidationRuleDidValidate:(DWCheckExistenceUsernameValidationRule *)rule; +- (void)editProfileTextFieldCellActivateNextFirstResponder:(DWEditProfileTextFieldCell *)cell; @end -@interface DWCheckExistenceUsernameValidationRule : DWUsernameValidationRule - -- (instancetype)initWithDelegate:(id)delegate; +@interface DWEditProfileTextFieldCell : DWEditProfileBaseCell -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; +@property (nullable, strong, nonatomic) DWTextFieldFormCellModel *cellModel; +@property (nullable, weak, nonatomic) id delegate; @end diff --git a/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextFieldCell.m b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextFieldCell.m new file mode 100644 index 000000000..c3bf6f596 --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextFieldCell.m @@ -0,0 +1,159 @@ +// +// 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 "DWEditProfileTextFieldCell.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWEditProfileTextFieldCell () + +@property (readonly, nonatomic, strong) UITextField *textField; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWEditProfileTextFieldCell + + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + self.selectionStyle = UITableViewCellSelectionStyleNone; + + UITextField *textField = [[UITextField alloc] initWithFrame:CGRectZero]; + textField.translatesAutoresizingMaskIntoConstraints = NO; + textField.backgroundColor = [UIColor dw_backgroundColor]; + textField.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + textField.adjustsFontSizeToFitWidth = YES; + textField.textColor = [UIColor dw_darkTitleColor]; + textField.delegate = self; + [self.inputContentView addSubview:textField]; + _textField = textField; + + const CGFloat padding = 20.0; + const CGFloat verticalPadding = 5.0; + [NSLayoutConstraint activateConstraints:@[ + [textField.topAnchor constraintEqualToAnchor:self.inputContentView.topAnchor + constant:verticalPadding], + [textField.leadingAnchor constraintEqualToAnchor:self.inputContentView.leadingAnchor + constant:padding], + [self.inputContentView.trailingAnchor constraintEqualToAnchor:textField.trailingAnchor + constant:padding], + [self.inputContentView.bottomAnchor constraintEqualToAnchor:textField.bottomAnchor + constant:verticalPadding], + + [textField.heightAnchor constraintEqualToConstant:40], + ]]; + + [self mvvm_observe:@"cellModel.title" + with:^(typeof(self) self, NSString *value) { + self.titleLabel.text = value; + }]; + + [self mvvm_observe:@"cellModel.placeholder" + with:^(typeof(self) self, NSString *value) { + self.textField.placeholder = value; + }]; + + [self mvvm_observe:@"cellModel.text" + with:^(typeof(self) self, NSString *value) { + self.textField.text = value; + + [self provideValidationResult:[self.cellModel postValidate]]; + }]; + } + return self; +} + +- (BOOL)isFirstResponder { + return self.textField.isFirstResponder; +} + +- (void)setCellModel:(nullable DWTextFieldFormCellModel *)cellModel { + _cellModel = cellModel; + + self.textField.autocapitalizationType = cellModel.autocapitalizationType; + self.textField.autocorrectionType = cellModel.autocorrectionType; + self.textField.keyboardType = cellModel.keyboardType; + self.textField.returnKeyType = cellModel.returnKeyType; + self.textField.enablesReturnKeyAutomatically = cellModel.enablesReturnKeyAutomatically; + self.textField.secureTextEntry = cellModel.secureTextEntry; + + [self provideValidationResult:[cellModel postValidate]]; +} + +#pragma mark - TextInputFormTableViewCell + +- (void)textInputBecomeFirstResponder { + [self.textField becomeFirstResponder]; +} + + +#pragma mark UITextFieldDelegate + +- (void)textFieldDidBeginEditing:(UITextField *)textField { + [self provideValidationResult:[self.cellModel postValidate]]; +} + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { + BOOL allowed = [self.cellModel validateReplacementString:string text:textField.text]; + if (!allowed) { + return NO; + } + + self.cellModel.text = [textField.text stringByReplacingCharactersInRange:range withString:string]; + if (self.cellModel.didChangeValueBlock) { + self.cellModel.didChangeValueBlock(self.cellModel); + } + + [self provideValidationResult:[self.cellModel postValidate]]; + + return NO; +} + +- (BOOL)textFieldShouldClear:(UITextField *)textField { + self.cellModel.text = @""; + if (self.cellModel.didChangeValueBlock) { + self.cellModel.didChangeValueBlock(self.cellModel); + } + + return NO; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + if (textField.returnKeyType == UIReturnKeyNext) { + [self.delegate editProfileTextFieldCellActivateNextFirstResponder:self]; + } + else if (textField.returnKeyType == UIReturnKeyDone) { + [self endEditing:YES]; + } + + return YES; +} + +- (void)textFieldDidEndEditing:(UITextField *)textField reason:(UITextFieldDidEndEditingReason)reason { + [self provideValidationResult:[self.cellModel postValidate]]; + + if (reason == UITextFieldDidEndEditingReasonCommitted && self.cellModel.didReturnValueBlock) { + self.cellModel.didReturnValueBlock(self.cellModel); + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.m b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextViewCell.h similarity index 71% rename from DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.m rename to DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextViewCell.h index b35eb6af1..0b0392fcc 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.m +++ b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextViewCell.h @@ -15,16 +15,17 @@ // limitations under the License. // -#import "DWRequestsContentViewController.h" +#import "DWEditProfileBaseCell.h" + +#import "DWTextInputFormTableViewCell.h" +#import "DWTextViewFormCellModel.h" NS_ASSUME_NONNULL_BEGIN -@interface DWRequestsContentViewController () +@interface DWEditProfileTextViewCell : DWEditProfileBaseCell + +@property (nullable, strong, nonatomic) DWTextViewFormCellModel *cellModel; @end NS_ASSUME_NONNULL_END - -@implementation DWRequestsContentViewController - -@end diff --git a/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextViewCell.m b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextViewCell.m new file mode 100644 index 000000000..8c98f16ce --- /dev/null +++ b/DashPay/Presentation/Profile/EditProfile/Views/DWEditProfileTextViewCell.m @@ -0,0 +1,132 @@ +// +// 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 "DWEditProfileTextViewCell.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWEditProfileTextViewCell () + +@property (readonly, nonatomic, strong) UITextView *textView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWEditProfileTextViewCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + self.selectionStyle = UITableViewCellSelectionStyleNone; + + UITextView *textView = [[UITextView alloc] initWithFrame:CGRectZero textContainer:nil]; + textView.translatesAutoresizingMaskIntoConstraints = NO; + textView.textContainerInset = UIEdgeInsetsZero; + textView.textContainer.lineFragmentPadding = 0.0; + textView.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + textView.delegate = self; + textView.backgroundColor = [UIColor clearColor]; + [self.inputContentView addSubview:textView]; + _textView = textView; + + const CGFloat padding = 20.0; + const CGFloat verticalPadding = 10.0; + [NSLayoutConstraint activateConstraints:@[ + [textView.topAnchor constraintEqualToAnchor:self.inputContentView.topAnchor + constant:verticalPadding], + [textView.leadingAnchor constraintEqualToAnchor:self.inputContentView.leadingAnchor + constant:padding], + [self.inputContentView.trailingAnchor constraintEqualToAnchor:textView.trailingAnchor + constant:padding], + [self.inputContentView.bottomAnchor constraintEqualToAnchor:textView.bottomAnchor + constant:verticalPadding], + [textView.heightAnchor constraintEqualToConstant:125], + ]]; + + [self mvvm_observe:@"cellModel.title" + with:^(typeof(self) self, NSString *value) { + self.titleLabel.text = value; + }]; + + [self mvvm_observe:@"cellModel.placeholder" + with:^(typeof(self) self, NSString *value){ + // not supported + }]; + + [self mvvm_observe:@"cellModel.text" + with:^(typeof(self) self, NSString *value) { + self.textView.text = value; + + [self provideValidationResult:[self.cellModel postValidate]]; + }]; + } + return self; +} + +- (BOOL)isFirstResponder { + return self.textView.isFirstResponder; +} + +- (void)setCellModel:(nullable DWTextViewFormCellModel *)cellModel { + _cellModel = cellModel; + + self.textView.autocapitalizationType = cellModel.autocapitalizationType; + self.textView.autocorrectionType = cellModel.autocorrectionType; + self.textView.keyboardType = cellModel.keyboardType; + self.textView.returnKeyType = cellModel.returnKeyType; + self.textView.enablesReturnKeyAutomatically = cellModel.enablesReturnKeyAutomatically; + self.textView.secureTextEntry = cellModel.secureTextEntry; + + [self provideValidationResult:[self.cellModel postValidate]]; +} + +#pragma mark - TextInputFormTableViewCell + +- (void)textInputBecomeFirstResponder { + [self.textView becomeFirstResponder]; +} + +#pragma mark - UITextViewDelegate + +- (void)textViewDidBeginEditing:(UITextView *)textView { + [self provideValidationResult:[self.cellModel postValidate]]; +} + +- (void)textViewDidEndEditing:(UITextView *)textView { + [self provideValidationResult:[self.cellModel postValidate]]; +} + +- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { + BOOL allowed = [self.cellModel validateReplacementString:text text:textView.text]; + if (!allowed) { + return NO; + } + + self.cellModel.text = [textView.text stringByReplacingCharactersInRange:range withString:text]; + if (self.cellModel.didChangeValueBlock) { + self.cellModel.didChangeValueBlock(self.cellModel); + } + + [self provideValidationResult:[self.cellModel postValidate]]; + + return NO; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.h b/DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.h similarity index 61% rename from DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.h rename to DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.h index ef47cfb74..4c223248d 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.h +++ b/DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.h @@ -15,21 +15,24 @@ // limitations under the License. // -#import "DWDPBasicUserItem.h" -#import "DWDPDashpayUserBackedItem.h" +#import NS_ASSUME_NONNULL_BEGIN -@class DSDashpayUserEntity; +@class DWCurrentUserProfileView; +@class DSBlockchainIdentity; -@interface DWDPContactObject : NSObject +@protocol DWCurrentUserProfileViewDelegate -@property (readonly, nonatomic, strong) DSDashpayUserEntity *userEntity; +- (void)currentUserProfileView:(DWCurrentUserProfileView *)view showQRAction:(UIButton *)sender; +- (void)currentUserProfileView:(DWCurrentUserProfileView *)view editProfileAction:(UIButton *)sender; -@property (nonatomic, strong) NSString *username; -@property (nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; +@end -- (instancetype)initWithDashpayUserEntity:(DSDashpayUserEntity *)userEntity; +@interface DWCurrentUserProfileView : UIView + +@property (nullable, nonatomic, weak) id delegate; +@property (nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; @end diff --git a/DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.m b/DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.m new file mode 100644 index 000000000..41f315e21 --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.m @@ -0,0 +1,158 @@ +// +// 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 "DWCurrentUserProfileView.h" + +#import "DSBlockchainIdentity+DWDisplayTitleSubtitle.h" +#import "DWButton.h" +#import "DWUIKit.h" + +#import "UIImageView+DWDPAvatar.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const AvatarSize = 134.0; +static CGSize const ShowQRSize = {46.0, 45.0}; + +@interface DWCurrentUserProfileView () + +@property (readonly, nonatomic, strong) UIImageView *avatarImageView; +@property (readonly, nonatomic, strong) UILabel *infoLabel; +@property (readonly, nonatomic, strong) UIButton *editProfileButton; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWCurrentUserProfileView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UIImageView *avatarImageView = [[UIImageView alloc] init]; + avatarImageView.translatesAutoresizingMaskIntoConstraints = NO; + avatarImageView.image = [UIImage imageNamed:@"dp_current_user_placeholder"]; + avatarImageView.layer.cornerRadius = AvatarSize / 2.0; + avatarImageView.layer.masksToBounds = YES; + [self addSubview:avatarImageView]; + _avatarImageView = avatarImageView; + + UIButton *showQRButton = [UIButton buttonWithType:UIButtonTypeCustom]; + showQRButton.translatesAutoresizingMaskIntoConstraints = NO; + [showQRButton setImage:[UIImage imageNamed:@"dp_show_qr"] forState:UIControlStateNormal]; + [showQRButton addTarget:self action:@selector(showQRButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:showQRButton]; + + UILabel *infoLabel = [[UILabel alloc] init]; + infoLabel.translatesAutoresizingMaskIntoConstraints = NO; + infoLabel.numberOfLines = 0; + infoLabel.textAlignment = NSTextAlignmentCenter; + [self addSubview:infoLabel]; + _infoLabel = infoLabel; + + DWButton *editProfileButton = [DWButton buttonWithType:UIButtonTypeSystem]; + editProfileButton.translatesAutoresizingMaskIntoConstraints = NO; + editProfileButton.tintColor = [UIColor dw_dashBlueColor]; + [editProfileButton setTitle:NSLocalizedString(@"Edit Profile", nil) forState:UIControlStateNormal]; + [editProfileButton addTarget:self action:@selector(editProfileButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:editProfileButton]; + _editProfileButton = editProfileButton; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reloadAttributedData) + name:UIContentSizeCategoryDidChangeNotification + object:nil]; + + const CGFloat padding = 20.0; + const CGFloat spacing = 32.0; + [NSLayoutConstraint activateConstraints:@[ + [avatarImageView.topAnchor constraintEqualToAnchor:self.topAnchor + constant:spacing], + [avatarImageView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [avatarImageView.widthAnchor constraintEqualToConstant:AvatarSize], + [avatarImageView.heightAnchor constraintEqualToConstant:AvatarSize], + + [showQRButton.trailingAnchor constraintEqualToAnchor:avatarImageView.trailingAnchor], + [showQRButton.bottomAnchor constraintEqualToAnchor:avatarImageView.bottomAnchor], + [showQRButton.widthAnchor constraintEqualToConstant:ShowQRSize.width], + [showQRButton.heightAnchor constraintEqualToConstant:ShowQRSize.height], + + [infoLabel.topAnchor constraintEqualToAnchor:avatarImageView.bottomAnchor + constant:20], + [infoLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.trailingAnchor constraintEqualToAnchor:infoLabel.trailingAnchor + constant:padding], + + [editProfileButton.topAnchor constraintEqualToAnchor:infoLabel.bottomAnchor + constant:8.0], + [editProfileButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.trailingAnchor constraintEqualToAnchor:editProfileButton.trailingAnchor + constant:padding], + [self.bottomAnchor constraintEqualToAnchor:editProfileButton.bottomAnchor + constant:44.0], + [editProfileButton.heightAnchor constraintEqualToConstant:spacing], + ]]; + } + return self; +} + +- (void)setBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + _blockchainIdentity = blockchainIdentity; + + __weak typeof(self) weakSelf = self; + [self.avatarImageView dw_setAvatarWithURLString:blockchainIdentity.avatarPath + completion:^(UIImage *_Nullable image) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (image) { + strongSelf.avatarImageView.image = image; + } + else { + strongSelf.avatarImageView.image = [UIImage imageNamed:@"dp_current_user_placeholder"]; + } + }]; + + [self reloadAttributedData]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + [self reloadAttributedData]; +} + +- (void)showQRButtonAction:(UIButton *)sender { + [self.delegate currentUserProfileView:self showQRAction:sender]; +} + +- (void)editProfileButtonAction:(UIButton *)sender { + [self.delegate currentUserProfileView:self editProfileAction:sender]; +} + +#pragma mark - Private + +- (void)reloadAttributedData { + self.infoLabel.attributedText = [self.blockchainIdentity dw_asTitleSubtitle]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.h b/DashPay/Presentation/Profile/UserProfile/DWDPWelcomeMenuView.h similarity index 87% rename from DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.h rename to DashPay/Presentation/Profile/UserProfile/DWDPWelcomeMenuView.h index 6683ad72f..b9177f4df 100644 --- a/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.h +++ b/DashPay/Presentation/Profile/UserProfile/DWDPWelcomeMenuView.h @@ -19,9 +19,9 @@ NS_ASSUME_NONNULL_BEGIN -@interface UIColor (DWDashPay) +@interface DWDPWelcomeMenuView : UIView -+ (UIColor *)dw_colorWithUsername:(NSString *)username; +@property (readonly, nonatomic, strong) UIButton *joinButton; @end diff --git a/DashPay/Presentation/Profile/UserProfile/DWDPWelcomeMenuView.m b/DashPay/Presentation/Profile/UserProfile/DWDPWelcomeMenuView.m new file mode 100644 index 000000000..831f3a2d0 --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfile/DWDPWelcomeMenuView.m @@ -0,0 +1,129 @@ +// +// 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 "DWDPWelcomeMenuView.h" + +#import "DWActionButton.h" +#import "DWShadowView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const ButtonHeight = 39.0; + +@interface DWDPWelcomeMenuView () + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPWelcomeMenuView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + + DWShadowView *shadowView = [[DWShadowView alloc] initWithFrame:CGRectZero]; + shadowView.translatesAutoresizingMaskIntoConstraints = NO; + shadowView.insetsLayoutMarginsFromSafeArea = YES; + [self addSubview:shadowView]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = [UIColor dw_backgroundColor]; + contentView.layer.cornerRadius = 8.0; + contentView.layer.masksToBounds = YES; + [shadowView addSubview:contentView]; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.numberOfLines = 0; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + titleLabel.text = NSLocalizedString(@"Join DashPay", nil); + titleLabel.textAlignment = NSTextAlignmentCenter; + [contentView addSubview:titleLabel]; + + UILabel *subtitleLabel = [[UILabel alloc] init]; + subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; + subtitleLabel.textColor = [UIColor dw_tertiaryTextColor]; + subtitleLabel.numberOfLines = 0; + subtitleLabel.adjustsFontForContentSizeCategory = YES; + subtitleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; + subtitleLabel.text = NSLocalizedString(@"Create a username, add your friends.", nil); + subtitleLabel.textAlignment = NSTextAlignmentCenter; + [contentView addSubview:subtitleLabel]; + + DWActionButton *joinButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + joinButton.translatesAutoresizingMaskIntoConstraints = NO; + joinButton.usedOnDarkBackground = NO; + joinButton.small = YES; + joinButton.inverted = NO; + [joinButton setTitle:NSLocalizedString(@"Join", nil) forState:UIControlStateNormal]; + [contentView addSubview:joinButton]; + _joinButton = joinButton; + + [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 forAxis:UILayoutConstraintAxisVertical]; + [subtitleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 forAxis:UILayoutConstraintAxisVertical]; + + const CGFloat padding = 16.0; + const CGFloat horizontalPadding = 12; + const CGFloat verticalPadding = 16; + [NSLayoutConstraint activateConstraints:@[ + [shadowView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [shadowView.topAnchor constraintEqualToAnchor:self.topAnchor], + [self.trailingAnchor constraintEqualToAnchor:shadowView.trailingAnchor + constant:padding], + [self.bottomAnchor constraintEqualToAnchor:shadowView.bottomAnchor + constant:8.0], + + [contentView.leadingAnchor constraintEqualToAnchor:shadowView.leadingAnchor], + [contentView.topAnchor constraintEqualToAnchor:shadowView.topAnchor], + [shadowView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [shadowView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:25.0], + [titleLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:horizontalPadding], + [contentView.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor + constant:horizontalPadding], + + [subtitleLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:2.0], + [subtitleLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:horizontalPadding], + [contentView.trailingAnchor constraintEqualToAnchor:subtitleLabel.trailingAnchor + constant:horizontalPadding], + + + [joinButton.topAnchor constraintEqualToAnchor:subtitleLabel.bottomAnchor + constant:22.0], + [joinButton.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:joinButton.bottomAnchor + constant:12.0], + + [joinButton.heightAnchor constraintEqualToConstant:ButtonHeight], + ]]; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/UsernamePending/DWUsernamePendingViewController.h b/DashPay/Presentation/Profile/UserProfile/DWErrorUpdatingUserProfileView.h similarity index 61% rename from DashWallet/Sources/UI/DashPay/Setup/UsernamePending/DWUsernamePendingViewController.h rename to DashPay/Presentation/Profile/UserProfile/DWErrorUpdatingUserProfileView.h index 070ad6711..2b4165713 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/UsernamePending/DWUsernamePendingViewController.h +++ b/DashPay/Presentation/Profile/UserProfile/DWErrorUpdatingUserProfileView.h @@ -19,18 +19,18 @@ NS_ASSUME_NONNULL_BEGIN -@protocol DWUsernamePendingViewControllerDelegate +@class DWErrorUpdatingUserProfileView; -- (void)usernamePendingViewControllerAction:(UIViewController *)controller; +@protocol DWErrorUpdatingUserProfileViewDelegate -@end +- (void)errorUpdatingUserProfileView:(DWErrorUpdatingUserProfileView *)view retryAction:(UIButton *)sender; +- (void)errorUpdatingUserProfileView:(DWErrorUpdatingUserProfileView *)view cancelAction:(UIButton *)sender; -@interface DWUsernamePendingViewController : UIViewController +@end -- (NSAttributedString *)attributedTitle; +@interface DWErrorUpdatingUserProfileView : UIView -@property (nullable, nonatomic, weak) id delegate; -@property (nonatomic, copy) NSString *username; +@property (nullable, nonatomic, weak) id delegate; @end diff --git a/DashPay/Presentation/Profile/UserProfile/DWErrorUpdatingUserProfileView.m b/DashPay/Presentation/Profile/UserProfile/DWErrorUpdatingUserProfileView.m new file mode 100644 index 000000000..2951281ff --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfile/DWErrorUpdatingUserProfileView.m @@ -0,0 +1,129 @@ +// +// 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 "DWErrorUpdatingUserProfileView.h" + +#import "DWActionButton.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const ButtonHeight = 39.0; + +@interface DWErrorUpdatingUserProfileView () +@end + +NS_ASSUME_NONNULL_END + +@implementation DWErrorUpdatingUserProfileView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + self.layer.cornerRadius = 8.0; + self.layer.masksToBounds = YES; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:contentView]; + + UIImageView *iconImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_error"]]; + iconImageView.translatesAutoresizingMaskIntoConstraints = NO; + [contentView addSubview:iconImageView]; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + titleLabel.textColor = [UIColor dw_secondaryTextColor]; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.numberOfLines = 0; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.adjustsFontSizeToFitWidth = YES; + titleLabel.minimumScaleFactor = 0.5; + titleLabel.text = NSLocalizedString(@"Error updating your profile", nil); + [contentView addSubview:titleLabel]; + + DWActionButton *retryButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + retryButton.translatesAutoresizingMaskIntoConstraints = NO; + retryButton.usedOnDarkBackground = NO; + retryButton.small = YES; + retryButton.inverted = NO; + [retryButton setTitle:NSLocalizedString(@"Try again", nil) forState:UIControlStateNormal]; + [retryButton addTarget:self + action:@selector(retryButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + + DWActionButton *cancelButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + cancelButton.usedOnDarkBackground = NO; + cancelButton.small = YES; + cancelButton.inverted = YES; + [cancelButton setTitle:NSLocalizedString(@"Cancel", nil) forState:UIControlStateNormal]; + [cancelButton addTarget:self + action:@selector(cancelButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ retryButton, cancelButton ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisHorizontal; + stackView.distribution = UIStackViewDistributionFillEqually; + stackView.spacing = 9.0; + stackView.alignment = UIStackViewAlignmentCenter; + [contentView addSubview:stackView]; + + const CGFloat padding = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [contentView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], + [contentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:contentView.bottomAnchor], + [self.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor + constant:padding], + [contentView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + + [iconImageView.topAnchor constraintEqualToAnchor:contentView.topAnchor], + [iconImageView.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:iconImageView.bottomAnchor + constant:padding], + [titleLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + + [stackView.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:50], + [stackView.leadingAnchor constraintGreaterThanOrEqualToAnchor:contentView.leadingAnchor], + [contentView.trailingAnchor constraintGreaterThanOrEqualToAnchor:stackView.trailingAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor], + [stackView.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + + [retryButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [cancelButton.heightAnchor constraintEqualToConstant:ButtonHeight], + ]]; + } + return self; +} + +- (void)cancelButtonAction:(UIButton *)sender { + [self.delegate errorUpdatingUserProfileView:self cancelAction:sender]; +} + +- (void)retryButtonAction:(UIButton *)sender { + [self.delegate errorUpdatingUserProfileView:self retryAction:sender]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWTextField.h b/DashPay/Presentation/Profile/UserProfile/DWUpdatingUserProfileView.h similarity index 93% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWTextField.h rename to DashPay/Presentation/Profile/UserProfile/DWUpdatingUserProfileView.h index 017848598..657868c9c 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWTextField.h +++ b/DashPay/Presentation/Profile/UserProfile/DWUpdatingUserProfileView.h @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWTextField : UITextField +@interface DWUpdatingUserProfileView : UIView @end diff --git a/DashPay/Presentation/Profile/UserProfile/DWUpdatingUserProfileView.m b/DashPay/Presentation/Profile/UserProfile/DWUpdatingUserProfileView.m new file mode 100644 index 000000000..6def9b37a --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfile/DWUpdatingUserProfileView.m @@ -0,0 +1,103 @@ +// +// 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 "DWUpdatingUserProfileView.h" + +#import "DWDashPayAnimationView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUpdatingUserProfileView () + +@property (readonly, nonatomic, strong) DWDashPayAnimationView *loadingView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUpdatingUserProfileView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + self.layer.cornerRadius = 8.0; + self.layer.masksToBounds = YES; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = self.backgroundColor; + [self addSubview:contentView]; + + DWDashPayAnimationView *loadingView = [[DWDashPayAnimationView alloc] init]; + loadingView.translatesAutoresizingMaskIntoConstraints = NO; + [contentView addSubview:loadingView]; + _loadingView = loadingView; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + titleLabel.textColor = [UIColor dw_secondaryTextColor]; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.numberOfLines = 0; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.adjustsFontSizeToFitWidth = YES; + titleLabel.minimumScaleFactor = 0.5; + titleLabel.text = NSLocalizedString(@"Updating Profile on Dash Network", nil); + [contentView addSubview:titleLabel]; + + const CGFloat padding = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [contentView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], + [contentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:contentView.bottomAnchor], + [self.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor + constant:padding], + [contentView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + + [loadingView.topAnchor constraintEqualToAnchor:contentView.topAnchor], + [loadingView.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:loadingView.bottomAnchor + constant:padding], + [titleLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor], + ]]; + } + return self; +} + +- (void)willMoveToWindow:(nullable UIWindow *)newWindow { + [super willMoveToWindow:newWindow]; + + if (newWindow == nil) { + [self.loadingView stopAnimating]; + } +} + +- (void)didMoveToWindow { + [super didMoveToWindow]; + + if (self.window) { + [self.loadingView startAnimating]; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicUserItem.h b/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.h similarity index 68% rename from DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicUserItem.h rename to DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.h index c06cf0013..2530ac46e 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicUserItem.h +++ b/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.h @@ -15,18 +15,20 @@ // limitations under the License. // -#import +#import -#import "DWDPBasicItem.h" -#import "DWDPBlockchainIdentityBackedItem.h" +#import "DWCurrentUserProfileView.h" NS_ASSUME_NONNULL_BEGIN -@class DSFriendRequestEntity; +@class DWCurrentUserProfileModel; -@protocol DWDPBasicUserItem +@interface DWUserProfileContainerView : KVOUIView -- (nullable DSFriendRequestEntity *)friendRequestToPay; +@property (nonatomic, strong) DWCurrentUserProfileModel *userModel; +@property (nullable, nonatomic, weak) id delegate; + +- (void)update; @end diff --git a/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.m b/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.m new file mode 100644 index 000000000..6cee51d73 --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.m @@ -0,0 +1,130 @@ +// +// 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 "DWUserProfileContainerView.h" + +#import "DWCurrentUserProfileModel.h" +#import "DWErrorUpdatingUserProfileView.h" +#import "DWUIKit.h" +#import "DWUpdatingUserProfileView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserProfileContainerView () + +@property (readonly, nonatomic, strong) DWCurrentUserProfileView *profileView; +@property (readonly, nonatomic, strong) DWUpdatingUserProfileView *updatingView; +@property (readonly, nonatomic, strong) DWErrorUpdatingUserProfileView *errorView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileContainerView + +- (id)delegate { + return self.profileView.delegate; +} + +- (void)setDelegate:(id)delegate { + self.profileView.delegate = delegate; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + DWCurrentUserProfileView *profileView = [[DWCurrentUserProfileView alloc] init]; + profileView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:profileView]; + _profileView = profileView; + + DWUpdatingUserProfileView *updatingView = [[DWUpdatingUserProfileView alloc] init]; + updatingView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:updatingView]; + _updatingView = updatingView; + + DWErrorUpdatingUserProfileView *errorView = [[DWErrorUpdatingUserProfileView alloc] init]; + errorView.translatesAutoresizingMaskIntoConstraints = NO; + errorView.delegate = self; + [self addSubview:errorView]; + _errorView = errorView; + + const CGFloat padding = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [profileView.topAnchor constraintEqualToAnchor:self.topAnchor], + [profileView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.bottomAnchor constraintEqualToAnchor:profileView.bottomAnchor], + [self.trailingAnchor constraintEqualToAnchor:profileView.trailingAnchor], + + [updatingView.topAnchor constraintEqualToAnchor:self.topAnchor], + [updatingView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.bottomAnchor constraintEqualToAnchor:updatingView.bottomAnchor + constant:5.0], + [self.trailingAnchor constraintEqualToAnchor:updatingView.trailingAnchor + constant:padding], + + [errorView.topAnchor constraintEqualToAnchor:self.topAnchor], + [errorView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.bottomAnchor constraintEqualToAnchor:errorView.bottomAnchor + constant:5.0], + [self.trailingAnchor constraintEqualToAnchor:errorView.trailingAnchor + constant:padding], + ]]; + + [self mvvm_observe:DW_KEYPATH(self, userModel.updateModel.state) + with:^(typeof(self) self, id value) { + switch (self.userModel.updateModel.state) { + case DWDPUpdateProfileModelState_Ready: + [self update]; + self.profileView.hidden = NO; + self.updatingView.hidden = YES; + self.errorView.hidden = YES; + break; + case DWDPUpdateProfileModelState_Loading: + self.profileView.hidden = YES; + self.updatingView.hidden = NO; + self.errorView.hidden = YES; + break; + case DWDPUpdateProfileModelState_Error: + self.profileView.hidden = YES; + self.updatingView.hidden = YES; + self.errorView.hidden = NO; + break; + } + }]; + } + return self; +} + +- (void)update { + self.profileView.blockchainIdentity = self.userModel.blockchainIdentity; +} + +#pragma mark - DWErrorUpdatingUserProfileViewDelegate + +- (void)errorUpdatingUserProfileView:(DWErrorUpdatingUserProfileView *)view retryAction:(UIButton *)sender { + [self.userModel.updateModel retry]; +} + +- (void)errorUpdatingUserProfileView:(DWErrorUpdatingUserProfileView *)view cancelAction:(UIButton *)sender { + [self.userModel.updateModel reset]; + [self.userModel update]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNotificationItem.h b/DashPay/Presentation/Profile/UserProfile/Models/DSBlockchainIdentity+DWDisplayTitleSubtitle.h similarity index 83% rename from DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNotificationItem.h rename to DashPay/Presentation/Profile/UserProfile/Models/DSBlockchainIdentity+DWDisplayTitleSubtitle.h index 9e5d8ec2f..1a520cda5 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNotificationItem.h +++ b/DashPay/Presentation/Profile/UserProfile/Models/DSBlockchainIdentity+DWDisplayTitleSubtitle.h @@ -15,13 +15,13 @@ // limitations under the License. // -#import +#import "DSBlockchainIdentity.h" NS_ASSUME_NONNULL_BEGIN -@protocol DWDPNotificationItem +@interface DSBlockchainIdentity (DWDisplayTitleSubtitle) -@property (readonly, nonatomic, strong) NSDate *date; +- (NSAttributedString *)dw_asTitleSubtitle; @end diff --git a/DashPay/Presentation/Profile/UserProfile/Models/DSBlockchainIdentity+DWDisplayTitleSubtitle.m b/DashPay/Presentation/Profile/UserProfile/Models/DSBlockchainIdentity+DWDisplayTitleSubtitle.m new file mode 100644 index 000000000..784dc557c --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfile/Models/DSBlockchainIdentity+DWDisplayTitleSubtitle.m @@ -0,0 +1,59 @@ +// +// 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 "DSBlockchainIdentity+DWDisplayTitleSubtitle.h" + +#import + +#import "DWUIKit.h" + +@implementation DSBlockchainIdentity (DWDisplayTitleSubtitle) + +- (NSAttributedString *)dw_asTitleSubtitle { + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + NSString *title = nil; + NSString *subtitle = nil; + if (self.displayName != nil) { + title = self.displayName; + subtitle = self.currentDashpayUsername; + } + else { + title = self.currentDashpayUsername; + } + if (title != nil) { + [result appendAttributedString:[[NSAttributedString alloc] + initWithString:title + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleTitle3], + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + }]]; + } + if (subtitle != nil) { + [result appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; + [result appendAttributedString:[[NSAttributedString alloc] + initWithString:subtitle + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleCallout], + NSForegroundColorAttributeName : [UIColor dw_tertiaryTextColor], + }]]; + } + [result endEditing]; + return result; +} + +@end diff --git a/DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.h b/DashPay/Presentation/Profile/UserProfile/Models/DWDPUpdateProfileModel.h similarity index 100% rename from DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.h rename to DashPay/Presentation/Profile/UserProfile/Models/DWDPUpdateProfileModel.h diff --git a/DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.m b/DashPay/Presentation/Profile/UserProfile/Models/DWDPUpdateProfileModel.m similarity index 100% rename from DashPay/Presentation/Home/Model/DWDPUpdateProfileModel.m rename to DashPay/Presentation/Profile/UserProfile/Models/DWDPUpdateProfileModel.m diff --git a/DashPay/Presentation/Profile/UserProfileModalQR/DWDWUserProfileModalQRContentView.h b/DashPay/Presentation/Profile/UserProfileModalQR/DWDWUserProfileModalQRContentView.h new file mode 100644 index 000000000..84542e865 --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfileModalQR/DWDWUserProfileModalQRContentView.h @@ -0,0 +1,46 @@ +// +// 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 + +@protocol DWReceiveModelProtocol; +@class DWUserProfileModalQRContentView; + +@protocol DWDWUserProfileModalQRContentViewDelegate + +- (void)userProfileModalQRContentView:(DWUserProfileModalQRContentView *)view shareButtonAction:(UIButton *)sender; +- (void)userProfileModalQRContentView:(DWUserProfileModalQRContentView *)view closeButtonAction:(UIButton *)sender; + +@end + +@interface DWUserProfileModalQRContentView : KVOUIView + +@property (nullable, weak, nonatomic) id delegate; + +- (void)viewDidAppear; + +- (instancetype)initWithModel:(id)model; + +- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRContentView.h b/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRContentView.h new file mode 100644 index 000000000..827485425 --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRContentView.h @@ -0,0 +1,46 @@ +// +// 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 + +@protocol DWReceiveModelProtocol; +@class DWUserProfileModalQRContentView; + +@protocol DWUserProfileModalQRContentViewDelegate + +- (void)userProfileModalQRContentView:(DWUserProfileModalQRContentView *)view shareButtonAction:(UIButton *)sender; +- (void)userProfileModalQRContentView:(DWUserProfileModalQRContentView *)view closeButtonAction:(UIButton *)sender; + +@end + +@interface DWUserProfileModalQRContentView : KVOUIView + +@property (nullable, weak, nonatomic) id delegate; + +- (void)viewDidAppear; + +- (instancetype)initWithModel:(id)model; + +- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRContentView.m b/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRContentView.m new file mode 100644 index 000000000..7a1b310dd --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRContentView.m @@ -0,0 +1,179 @@ +// +// 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 "DWUserProfileModalQRContentView.h" + +#import "DSBlockchainIdentity+DWDisplayTitleSubtitle.h" +#import "DWActionButton.h" +#import "DWEnvironment.h" +#import "DWReceiveModelProtocol.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const ButtonHeight = 39.0; + +@interface DWUserProfileModalQRContentView () + +@property (nonatomic, strong) id model; + +@property (readonly, strong, nonatomic) UIButton *qrCodeButton; +@property (readonly, strong, nonatomic) UILabel *infoLabel; + +@property (nonatomic, strong) UINotificationFeedbackGenerator *feedbackGenerator; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileModalQRContentView + +- (instancetype)initWithModel:(id)model { + self = [super initWithFrame:CGRectZero]; + if (self) { + _model = model; + + self.backgroundColor = [UIColor dw_backgroundColor]; + + _feedbackGenerator = [[UINotificationFeedbackGenerator alloc] init]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = self.backgroundColor; + [self addSubview:contentView]; + + UIButton *qrCodeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + qrCodeButton.translatesAutoresizingMaskIntoConstraints = NO; + qrCodeButton.backgroundColor = [UIColor whiteColor]; + [qrCodeButton addTarget:self action:@selector(qrCodeButtonAction) forControlEvents:UIControlEventTouchUpInside]; + [contentView addSubview:qrCodeButton]; + _qrCodeButton = qrCodeButton; + + UILabel *infoLabel = [[UILabel alloc] init]; + infoLabel.translatesAutoresizingMaskIntoConstraints = NO; + infoLabel.numberOfLines = 0; + infoLabel.textAlignment = NSTextAlignmentCenter; + [contentView addSubview:infoLabel]; + _infoLabel = infoLabel; + + DWActionButton *shareButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + shareButton.usedOnDarkBackground = NO; + shareButton.small = YES; + [shareButton setTitle:NSLocalizedString(@"Share", nil) forState:UIControlStateNormal]; + [shareButton addTarget:self + action:@selector(shareButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + + DWActionButton *closeButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + closeButton.usedOnDarkBackground = NO; + closeButton.small = YES; + closeButton.inverted = YES; + [closeButton setTitle:NSLocalizedString(@"Close", nil) forState:UIControlStateNormal]; + [closeButton addTarget:self + action:@selector(closeButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ shareButton, closeButton ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.spacing = 10.0; + [contentView addSubview:stackView]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reloadAttributedData) + name:UIContentSizeCategoryDidChangeNotification + object:nil]; + + const CGFloat spacing = 44.0; + const CGSize qrSize = model.qrCodeSize; + const CGFloat interitem = 24.0; + const CGFloat padding = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [contentView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], + [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:contentView.bottomAnchor], + [contentView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + [contentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor + constant:padding], + + [qrCodeButton.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:spacing], + [qrCodeButton.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + [qrCodeButton.widthAnchor constraintEqualToConstant:qrSize.width], + [qrCodeButton.heightAnchor constraintEqualToConstant:qrSize.height], + + [infoLabel.topAnchor constraintEqualToAnchor:qrCodeButton.bottomAnchor + constant:interitem], + [infoLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:infoLabel.trailingAnchor], + + [stackView.topAnchor constraintEqualToAnchor:infoLabel.bottomAnchor + constant:interitem], + [stackView.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + // [stackView.centerXAnchor ] + // [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + // constant:padding], + // [self.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor + // constant:padding], + [contentView.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor + constant:spacing], + + [shareButton.heightAnchor constraintEqualToConstant:ButtonHeight], + [closeButton.heightAnchor constraintEqualToConstant:ButtonHeight], + ]]; + + [self mvvm_observe:DW_KEYPATH(self, model.qrCodeImage) + with:^(typeof(self) self, UIImage *value) { + [self.qrCodeButton setImage:value forState:UIControlStateNormal]; + self.qrCodeButton.hidden = (value == nil); + }]; + } + return self; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + [self reloadAttributedData]; +} + +- (void)viewDidAppear { + [self reloadAttributedData]; + [self.feedbackGenerator prepare]; +} + +- (void)reloadAttributedData { + DSBlockchainIdentity *blockchainIdentity = [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; + self.infoLabel.attributedText = [blockchainIdentity dw_asTitleSubtitle]; +} + +- (void)qrCodeButtonAction { + [self.feedbackGenerator notificationOccurred:UINotificationFeedbackTypeSuccess]; + + [self.model copyQRImageToPasteboard]; +} + +- (void)shareButtonAction:(UIButton *)sender { + [self.delegate userProfileModalQRContentView:self shareButtonAction:sender]; +} + +- (void)closeButtonAction:(UIButton *)sender { + [self.delegate userProfileModalQRContentView:self closeButtonAction:sender]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameValidationView.h b/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRViewController.h similarity index 72% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameValidationView.h rename to DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRViewController.h index 4c00e97c9..2f50e60cc 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameValidationView.h +++ b/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRViewController.h @@ -15,18 +15,19 @@ // limitations under the License. // -#import - -#import "DWUsernameValidationRule.h" +#import NS_ASSUME_NONNULL_BEGIN -@interface DWUsernameValidationView : KVOUIView +@protocol DWReceiveModelProtocol; -@property (nullable, nonatomic, strong) DWUsernameValidationRule *rule; +@interface DWUserProfileModalQRViewController : UIViewController -- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; +- (instancetype)initWithModel:(id)model; + +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end diff --git a/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRViewController.m b/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRViewController.m new file mode 100644 index 000000000..7cb3f11bc --- /dev/null +++ b/DashPay/Presentation/Profile/UserProfileModalQR/DWUserProfileModalQRViewController.m @@ -0,0 +1,102 @@ +// +// 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 "DWUserProfileModalQRViewController.h" + +#import "DWModalPopupTransition.h" +#import "DWUIKit.h" +#import "DWUserProfileModalQRContentView.h" +#import "UIViewController+DWShareReceiveInfo.h" + +static CGFloat const CORNER_RADIUS = 8.0; + +static CGFloat VerticalPadding(void) { + if (IS_IPAD) { + return 32.0; + } + else if (IS_IPHONE_6 || IS_IPHONE_5_OR_LESS) { + return 16.0; + } + else { + return 24.0; + } +} + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserProfileModalQRViewController () + +@property (readonly, nonatomic, strong) id model; + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileModalQRViewController + +- (instancetype)initWithModel:(id)model { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _model = model; + + _modalTransition = [[DWModalPopupTransition alloc] init]; + + self.transitioningDelegate = self.modalTransition; + self.modalPresentationStyle = UIModalPresentationCustom; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_backgroundColor]; + self.view.clipsToBounds = YES; + self.view.layer.cornerRadius = CORNER_RADIUS; + + UIView *contentView = self.view; + + DWUserProfileModalQRContentView *childView = [[DWUserProfileModalQRContentView alloc] initWithModel:self.model]; + childView.translatesAutoresizingMaskIntoConstraints = NO; + childView.delegate = self; + [contentView addSubview:childView]; + + const CGFloat padding = VerticalPadding(); + [NSLayoutConstraint activateConstraints:@[ + [childView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [childView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [childView.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor], + [childView.topAnchor constraintGreaterThanOrEqualToAnchor:contentView.topAnchor + constant:padding], + [childView.bottomAnchor constraintGreaterThanOrEqualToAnchor:contentView.bottomAnchor + constant:-padding], + ]]; +} + +#pragma mark - DWUserProfileModalQRContentViewDelegate + +- (void)userProfileModalQRContentView:(DWUserProfileModalQRContentView *)view closeButtonAction:(UIButton *)sender { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)userProfileModalQRContentView:(DWUserProfileModalQRContentView *)view shareButtonAction:(UIButton *)sender { + [self dw_shareReceiveInfo:self.model sender:sender]; +} + +@end diff --git a/DashPay/Presentation/Shared/Autolayout/NSArray+DWFlatten.h b/DashPay/Presentation/Shared/Autolayout/NSArray+DWFlatten.h new file mode 100644 index 000000000..ce7d5e9c2 --- /dev/null +++ b/DashPay/Presentation/Shared/Autolayout/NSArray+DWFlatten.h @@ -0,0 +1,28 @@ +// +// 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 + +@interface NSArray (DWFlatten) + +- (NSArray *)dw_flatten; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Shared/Autolayout/NSArray+DWFlatten.m b/DashPay/Presentation/Shared/Autolayout/NSArray+DWFlatten.m new file mode 100644 index 000000000..d9f00a077 --- /dev/null +++ b/DashPay/Presentation/Shared/Autolayout/NSArray+DWFlatten.m @@ -0,0 +1,47 @@ +// +// 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 "NSArray+DWFlatten.h" + +@implementation NSArray (DWFlatten) + +- (NSArray *)dw_flatten { + BOOL needsFlatten = ^{ + for (id object in self) + if ([object respondsToSelector:@selector(dw_flatten)]) + return YES; + return NO; + }(); + + if (!needsFlatten) { + return self; + } + + NSMutableArray *arr = [NSMutableArray arrayWithCapacity:self.count]; + for (id object in self) { + if ([object respondsToSelector:@selector(dw_flatten)]) { + [arr addObjectsFromArray:[object dw_flatten]]; + } + else { + [arr addObject:object]; + } + } + + return arr; +} + +@end diff --git a/DashPay/Presentation/Shared/Autolayout/NSLayoutConstraint+DWAutolayout.h b/DashPay/Presentation/Shared/Autolayout/NSLayoutConstraint+DWAutolayout.h new file mode 100644 index 000000000..7685aad9f --- /dev/null +++ b/DashPay/Presentation/Shared/Autolayout/NSLayoutConstraint+DWAutolayout.h @@ -0,0 +1,28 @@ +// +// 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 + +@interface NSLayoutConstraint (DWAutolayout) + ++ (void)dw_activate:(NSArray *)constraints; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.h b/DashPay/Presentation/Shared/Autolayout/NSLayoutConstraint+DWAutolayout.m similarity index 63% rename from DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.h rename to DashPay/Presentation/Shared/Autolayout/NSLayoutConstraint+DWAutolayout.m index c8f5dce83..1ab3257fd 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.h +++ b/DashPay/Presentation/Shared/Autolayout/NSLayoutConstraint+DWAutolayout.m @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,16 +15,15 @@ // limitations under the License. // -#import "DWBaseContactsViewController.h" +#import "NSLayoutConstraint+DWAutolayout.h" -#import "DWContactsModel.h" +#import "NSArray+DWFlatten.h" -NS_ASSUME_NONNULL_BEGIN +@implementation NSLayoutConstraint (DWAutolayout) -@interface DWContactsViewController : DWBaseContactsViewController - -@property (nonatomic, strong) DWContactsModel *model; ++ (void)dw_activate:(NSArray *)constraints { + NSArray *flattened = [constraints dw_flatten]; + [NSLayoutConstraint activateConstraints:flattened]; +} @end - -NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Shared/Autolayout/UIView+DWAutolayout.h b/DashPay/Presentation/Shared/Autolayout/UIView+DWAutolayout.h new file mode 100644 index 000000000..cfe322668 --- /dev/null +++ b/DashPay/Presentation/Shared/Autolayout/UIView+DWAutolayout.h @@ -0,0 +1,82 @@ +// +// 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 + +typedef NS_ENUM(NSUInteger, DWAnchor) { + DWAnchorTop, + DWAnchorLeft, + DWAnchorBottom, + DWAnchorRight, +}; + +@protocol DWHorizontalAnchors + +@property (nonatomic, readonly, strong) NSLayoutXAxisAnchor *leadingAnchor; +@property (nonatomic, readonly, strong) NSLayoutXAxisAnchor *trailingAnchor; + +@end + +@protocol DWVerticalAnchors + +@property (nonatomic, readonly, strong) NSLayoutYAxisAnchor *topAnchor; +@property (nonatomic, readonly, strong) NSLayoutYAxisAnchor *bottomAnchor; + +@end + +@protocol DWAnchors +@end + +#pragma mark - + +@interface UIView (DWAutolayout) + +- (NSArray *)pinHorizontally:(id)horizontalAnchors; +- (NSArray *)pinHorizontally:(id)horizontalAnchors + left:(CGFloat)left + right:(CGFloat)right; +- (NSArray *)pinHorizontally:(id)horizontalAnchors + left:(CGFloat)left + right:(CGFloat)right + except:(DWAnchor)exceptAnchor; + +- (NSArray *)pinVertically:(id)verticalAnchors; +- (NSArray *)pinVertically:(id)verticalAnchors + top:(CGFloat)top + bottom:(CGFloat)bottom; +- (NSArray *)pinVertically:(id)verticalAnchors + top:(CGFloat)top + bottom:(CGFloat)bottom + except:(DWAnchor)exceptAnchor; + +- (NSArray *)pinEdges:(id)anchors; +- (NSArray *)pinEdges:(id)anchors + insets:(UIEdgeInsets)insets; +- (NSArray *)pinEdges:(id)anchors + insets:(UIEdgeInsets)insets + except:(DWAnchor)exceptAnchor; + +- (NSArray *)pinSize:(CGSize)size; + +@end + +@interface UILayoutGuide (DWAutolayout) +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Shared/Autolayout/UIView+DWAutolayout.m b/DashPay/Presentation/Shared/Autolayout/UIView+DWAutolayout.m new file mode 100644 index 000000000..bbb7051f7 --- /dev/null +++ b/DashPay/Presentation/Shared/Autolayout/UIView+DWAutolayout.m @@ -0,0 +1,119 @@ +// +// 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 "UIView+DWAutolayout.h" + +@implementation UIView (DWAutolayout) + +- (NSArray *)pinHorizontally:(id)horizontalAnchors { + return [self pinHorizontally:horizontalAnchors left:0 right:0 except:-1]; +} + +- (NSArray *)pinHorizontally:(id)horizontalAnchors + left:(CGFloat)left + right:(CGFloat)right { + return [self pinHorizontally:horizontalAnchors left:left right:right except:-1]; +} + +- (NSArray *)pinHorizontally:(id)horizontalAnchors + left:(CGFloat)left + right:(CGFloat)right + except:(DWAnchor)exceptAnchor { + NSAssert(self.translatesAutoresizingMaskIntoConstraints == NO, + @"translatesAutoresizingMaskIntoConstraints is invalid"); + + NSMutableArray *res = [NSMutableArray array]; + + if (exceptAnchor != DWAnchorLeft) { + [res addObject:[self.leadingAnchor constraintEqualToAnchor:horizontalAnchors.leadingAnchor constant:left]]; + } + + if (exceptAnchor != DWAnchorRight) { + [res addObject:[horizontalAnchors.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:right]]; + } + + return res; +} + +- (NSArray *)pinVertically:(id)verticalAnchors { + return [self pinVertically:verticalAnchors top:0 bottom:0 except:-1]; +} + +- (NSArray *)pinVertically:(id)verticalAnchors + top:(CGFloat)top + bottom:(CGFloat)bottom { + return [self pinVertically:verticalAnchors top:top bottom:bottom except:-1]; +} + +- (NSArray *)pinVertically:(id)verticalAnchors + top:(CGFloat)top + bottom:(CGFloat)bottom + except:(DWAnchor)exceptAnchor { + NSAssert(self.translatesAutoresizingMaskIntoConstraints == NO, + @"translatesAutoresizingMaskIntoConstraints is invalid"); + + NSMutableArray *res = [NSMutableArray array]; + + if (exceptAnchor != DWAnchorTop) { + [res addObject:[self.topAnchor constraintEqualToAnchor:verticalAnchors.topAnchor constant:top]]; + } + + if (exceptAnchor != DWAnchorBottom) { + [res addObject:[verticalAnchors.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:bottom]]; + } + + return res; +} + +- (NSArray *)pinEdges:(id)anchors { + return [self pinEdges:anchors insets:UIEdgeInsetsZero except:-1]; +} + +- (NSArray *)pinEdges:(id)anchors + insets:(UIEdgeInsets)insets { + return [self pinEdges:anchors insets:insets except:-1]; +} + +- (NSArray *)pinEdges:(id)anchors + insets:(UIEdgeInsets)insets + except:(DWAnchor)exceptAnchor { + NSAssert(self.translatesAutoresizingMaskIntoConstraints == NO, + @"translatesAutoresizingMaskIntoConstraints is invalid"); + + NSMutableArray *res = [NSMutableArray array]; + + [res addObjectsFromArray:[self pinHorizontally:anchors left:insets.left right:insets.right except:exceptAnchor]]; + [res addObjectsFromArray:[self pinVertically:anchors top:insets.top bottom:insets.bottom except:exceptAnchor]]; + + return res; +} + +- (NSArray *)pinSize:(CGSize)size { + NSAssert(self.translatesAutoresizingMaskIntoConstraints == NO, + @"translatesAutoresizingMaskIntoConstraints is invalid"); + + return @[ + [self.widthAnchor constraintEqualToConstant:size.width], + [self.heightAnchor constraintEqualToConstant:size.height], + ]; +} + +@end + +@implementation UILayoutGuide (DWAutolayout) + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.h b/DashPay/Presentation/Shared/Buttons/DWColoredButton.h similarity index 69% rename from DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.h rename to DashPay/Presentation/Shared/Buttons/DWColoredButton.h index 612d6177b..50eebfea3 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.h +++ b/DashPay/Presentation/Shared/Buttons/DWColoredButton.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,16 +15,17 @@ // limitations under the License. // -#import -#import - -#import "DWDPBasicUserItem.h" +#import "DWPressableButton.h" NS_ASSUME_NONNULL_BEGIN -@interface DWDPContactsItemsFactory : NSObject +typedef NS_ENUM(NSUInteger, DWColoredButtonStyle) { + DWColoredButtonStyle_Black, +}; + +@interface DWColoredButton : DWPressableButton -- (id)itemForEntity:(NSManagedObject *)entity; +@property (nonatomic, assign) DWColoredButtonStyle style; @end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.m b/DashPay/Presentation/Shared/Buttons/DWColoredButton.m similarity index 51% rename from DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.m rename to DashPay/Presentation/Shared/Buttons/DWColoredButton.m index 1c626f005..9240ea6e9 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.m +++ b/DashPay/Presentation/Shared/Buttons/DWColoredButton.m @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,35 +15,36 @@ // limitations under the License. // -#import "DWDPTextStatusCell.h" +#import "DWColoredButton.h" -#import "DWDPGenericStatusItemView.h" +#import "DWUIKit.h" -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPTextStatusCell () - -@property (readonly, nonatomic, strong) DWDPGenericStatusItemView *itemView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPTextStatusCell - -@dynamic itemView; - -+ (Class)itemViewClass { - return DWDPGenericStatusItemView.class; -} +@implementation DWColoredButton - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { - NSString *text = NSLocalizedString(@"Pending", nil); - self.itemView.statusLabel.text = text; + self.layer.cornerRadius = 10; + self.layer.masksToBounds = YES; + + self.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; } return self; } +- (void)setStyle:(DWColoredButtonStyle)style { + _style = style; + + switch (style) { + case DWColoredButtonStyle_Black: { + [self setBackgroundColor:[UIColor dw_buttonBlackColor]]; + [self setTitleColor:[UIColor dw_buttonBlackTitleColor] forState:UIControlStateNormal]; + + break; + } + default: + break; + } +} + @end diff --git a/DashPay/Presentation/Shared/DPAlert/DPAlertChildContentsView.h b/DashPay/Presentation/Shared/DPAlert/DPAlertChildContentsView.h new file mode 100644 index 000000000..c3cf86be4 --- /dev/null +++ b/DashPay/Presentation/Shared/DPAlert/DPAlertChildContentsView.h @@ -0,0 +1,30 @@ +// +// 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 + +@interface DPAlertChildContentsView : UIView + +@property (nullable, nonatomic, strong) UIImage *icon; +@property (nullable, nonatomic, strong) NSString *title; +@property (nullable, nonatomic, strong) NSString *desc; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Shared/DPAlert/DPAlertChildContentsView.m b/DashPay/Presentation/Shared/DPAlert/DPAlertChildContentsView.m new file mode 100644 index 000000000..8887a3ac0 --- /dev/null +++ b/DashPay/Presentation/Shared/DPAlert/DPAlertChildContentsView.m @@ -0,0 +1,113 @@ +// +// 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 "DPAlertChildContentsView.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DPAlertChildContentsView () + +@property (readonly, nonatomic, strong) UIImageView *iconImageView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) UILabel *descriptionLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DPAlertChildContentsView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UIImageView *iconImageView = [[UIImageView alloc] init]; + iconImageView.translatesAutoresizingMaskIntoConstraints = NO; + iconImageView.contentMode = UIViewContentModeCenter; + [self addSubview:iconImageView]; + _iconImageView = iconImageView; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.numberOfLines = 0; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + titleLabel.adjustsFontForContentSizeCategory = YES; + [self addSubview:titleLabel]; + _titleLabel = titleLabel; + + UILabel *descLabel = [[UILabel alloc] init]; + descLabel.translatesAutoresizingMaskIntoConstraints = NO; + descLabel.textAlignment = NSTextAlignmentCenter; + descLabel.numberOfLines = 0; + descLabel.textColor = [UIColor dw_secondaryTextColor]; + descLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; + descLabel.adjustsFontForContentSizeCategory = YES; + [self addSubview:descLabel]; + _descriptionLabel = descLabel; + + [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + [descLabel setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + + [NSLayoutConstraint activateConstraints:@[ + [iconImageView.topAnchor constraintEqualToAnchor:self.topAnchor], + [iconImageView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:iconImageView.bottomAnchor + constant:16.0], + [titleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + + [descLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:16.0], + [descLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:descLabel.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:descLabel.bottomAnchor], + ]]; + } + return self; +} + +- (UIImage *)icon { + return self.iconImageView.image; +} + +- (void)setIcon:(UIImage *)icon { + self.iconImageView.image = icon; +} + +- (NSString *)title { + return self.titleLabel.text; +} + +- (void)setTitle:(NSString *)title { + self.titleLabel.text = title; +} + +- (NSString *)desc { + return self.descriptionLabel.text; +} + +- (void)setDesc:(NSString *)desc { + self.descriptionLabel.text = desc; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameHeaderView.h b/DashPay/Presentation/Shared/DPAlert/DPAlertViewController.h similarity index 63% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameHeaderView.h rename to DashPay/Presentation/Shared/DPAlert/DPAlertViewController.h index 29bc75ae5..20e08b816 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameHeaderView.h +++ b/DashPay/Presentation/Shared/DPAlert/DPAlertViewController.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -19,21 +19,16 @@ NS_ASSUME_NONNULL_BEGIN -typedef NSAttributedString *_Nonnull (^DWTitleStringBuilder)(void); +@interface DPAlertViewController : UIViewController -@interface DWUsernameHeaderView : UIView +- (instancetype)initWithIcon:(UIImage *)icon + title:(NSString *)title + description:(NSString *)description; -@property (readonly, nonatomic, strong) UIButton *cancelButton; -@property (nullable, nonatomic, copy) DWTitleStringBuilder titleBuilder; -@property (nonatomic, assign) BOOL landscapeMode; - -- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; - -- (void)configurePlanetsViewWithUsername:(NSString *)username; - -- (void)showInitialAnimation; +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; +- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; @end diff --git a/DashPay/Presentation/Shared/DPAlert/DPAlertViewController.m b/DashPay/Presentation/Shared/DPAlert/DPAlertViewController.m new file mode 100644 index 000000000..f1f183b59 --- /dev/null +++ b/DashPay/Presentation/Shared/DPAlert/DPAlertViewController.m @@ -0,0 +1,104 @@ +// +// 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 "DPAlertViewController.h" + +#import "DPAlertChildContentsView.h" +#import "DWActionButton.h" +#import "DWModalPopupTransition.h" +#import "DWUIKit.h" + +@interface DPAlertViewController () + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; + +@property (nullable, nonatomic, strong) UIImage *icon; +@property (nullable, nonatomic, strong) NSString *title_; +@property (nullable, nonatomic, strong) NSString *desc; + +@end + +@implementation DPAlertViewController + +- (instancetype)initWithIcon:(UIImage *)icon + title:(NSString *)title + description:(NSString *)description { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _icon = icon; + _title_ = title; + _desc = description; + } + return self; +} + +- (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]; + + DPAlertChildContentsView *childView = [[DPAlertChildContentsView alloc] initWithFrame:CGRectZero]; + childView.translatesAutoresizingMaskIntoConstraints = NO; + childView.icon = self.icon; + childView.title = self.title_; + childView.desc = self.desc; + [contentView addSubview:childView]; + + DWActionButton *okButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + okButton.translatesAutoresizingMaskIntoConstraints = NO; + okButton.usedOnDarkBackground = NO; + okButton.small = YES; + okButton.inverted = NO; + [okButton setTitle:NSLocalizedString(@"OK", nil) forState:UIControlStateNormal]; + [okButton addTarget:self action:@selector(closeButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + [contentView addSubview:okButton]; + + [NSLayoutConstraint activateConstraints:@[ + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + [contentView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], + + [childView.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:32.0], + [childView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:16.0], + [contentView.trailingAnchor constraintEqualToAnchor:childView.trailingAnchor + constant:16.0], + + [okButton.topAnchor constraintEqualToAnchor:childView.bottomAnchor + constant:32.0], + [okButton.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:okButton.bottomAnchor + constant:20.0], + [okButton.heightAnchor constraintGreaterThanOrEqualToConstant:40.0], + [okButton.widthAnchor constraintEqualToAnchor:childView.widthAnchor + multiplier:0.285], + ]]; +} + +- (void)closeButtonAction:(UIButton *)sender { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.h b/DashPay/Presentation/Shared/DWDPAvatarView.h similarity index 57% rename from DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.h rename to DashPay/Presentation/Shared/DWDPAvatarView.h index e27c4553b..515e8eccb 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.h +++ b/DashPay/Presentation/Shared/DWDPAvatarView.h @@ -15,20 +15,27 @@ // limitations under the License. // -#import "DWFetchedResultsDataSource.h" +#import NS_ASSUME_NONNULL_BEGIN +extern NSString *const DPCropParameterName; + @class DSBlockchainIdentity; -@interface DWContactsFetchedDataSource : DWFetchedResultsDataSource +typedef NS_ENUM(NSUInteger, DWDPAvatarBackgroundMode) { + DWDPAvatarBackgroundMode_DashBlue, + DWDPAvatarBackgroundMode_Random, +}; -@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; +@interface DWDPAvatarView : UIView -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - inContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; +@property (nonatomic, assign) DWDPAvatarBackgroundMode backgroundMode; +@property (nullable, nonatomic, copy) DSBlockchainIdentity *blockchainIdentity; +@property (nonatomic, assign, getter=isSmall) BOOL small; -- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_UNAVAILABLE; +- (void)setAsDashPlaceholder; +- (void)configureWithUsername:(NSString *)username; @end diff --git a/DashPay/Presentation/Shared/DWDPAvatarView.m b/DashPay/Presentation/Shared/DWDPAvatarView.m new file mode 100644 index 000000000..fac3338f6 --- /dev/null +++ b/DashPay/Presentation/Shared/DWDPAvatarView.m @@ -0,0 +1,184 @@ +// +// 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 "DWDPAvatarView.h" + +#import "DWEnvironment.h" +#import "DWUIKit.h" +#import "UIColor+DWDashPay.h" + +#import "UIImageView+DWDPAvatar.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +NSString *const DPCropParameterName = @"dashpay-profile-pic-zoom"; + +@interface DWDPAvatarView () + +@property (readonly, nonatomic, strong) UIImageView *imageView; +@property (readonly, nonatomic, strong) UILabel *letterLabel; +@property (nonatomic, assign) CGSize imageSize; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPAvatarView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setup]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self setup]; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + self.layer.cornerRadius = CGRectGetWidth(self.bounds) / 2.0; + + self.imageView.frame = self.bounds; + self.letterLabel.frame = self.bounds; +} + +- (void)setBackgroundMode:(DWDPAvatarBackgroundMode)backgroundMode { + _backgroundMode = backgroundMode; + + [self updateBackgroundColor]; +} + +- (void)setBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + _blockchainIdentity = blockchainIdentity; + + [self.imageView sd_cancelCurrentImageLoad]; + + NSString *username = blockchainIdentity.currentDashpayUsername; + NSString *avatarUrlString = [blockchainIdentity.avatarPath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + + [self setUsername:username]; + + __block typeof(self) weakSelf = self; + + [self.imageView dw_setAvatarWithURLString:avatarUrlString + completion:^(UIImage *_Nullable image) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (image) { + strongSelf.imageView.hidden = NO; + strongSelf.letterLabel.hidden = YES; + strongSelf.imageView.image = image; + } + else { + [strongSelf setUsername:username]; + } + }]; + +} + +- (void)configureWithUsername:(NSString *)username { + [self setUsername:username]; +} + +- (void)setUsername:(NSString *)username { + self.letterLabel.hidden = NO; + self.imageView.hidden = YES; + + if (username.length >= 1) { + NSString *firstLetter = [username substringToIndex:1]; + self.letterLabel.text = [firstLetter uppercaseString]; + + [self updateBackgroundColor]; + } + else { + self.letterLabel.text = nil; + self.layer.backgroundColor = [UIColor dw_dashBlueColor].CGColor; + } +} + +- (void)setAsDashPlaceholder { + self.letterLabel.hidden = YES; + self.imageView.hidden = NO; + + self.layer.backgroundColor = [UIColor dw_dashBlueColor].CGColor; + + self.imageView.tintColor = [UIColor whiteColor]; + self.imageView.contentMode = UIViewContentModeCenter; + self.imageView.image = [UIImage imageNamed:@"icon_dash_small"]; +} + +- (void)setSmall:(BOOL)small { + _small = small; + + if (small) { + self.letterLabel.font = [UIFont dw_regularFontOfSize:20]; + } + else { + self.letterLabel.font = [UIFont dw_regularFontOfSize:30]; + } +} + +#pragma mark - Private + +- (void)setup { + self.layer.backgroundColor = [UIColor dw_dashBlueColor].CGColor; + self.layer.masksToBounds = YES; + + UIImageView *imageView = [[UIImageView alloc] init]; + [self addSubview:imageView]; + _imageView = imageView; + + UILabel *letterLabel = [[UILabel alloc] init]; + letterLabel.font = [UIFont dw_regularFontOfSize:30]; + letterLabel.textAlignment = NSTextAlignmentCenter; + letterLabel.textColor = [UIColor dw_lightTitleColor]; + [self addSubview:letterLabel]; + _letterLabel = letterLabel; + + _imageSize = CGSizeMake(256, 256); +} + +- (void)updateBackgroundColor { + UIColor *color = nil; + switch (self.backgroundMode) { + case DWDPAvatarBackgroundMode_DashBlue: + color = [UIColor dw_dashBlueColor]; + break; + + case DWDPAvatarBackgroundMode_Random: { + color = [UIColor dw_colorWithUsername:self.letterLabel.text]; + break; + } + } + NSParameterAssert(color); + self.layer.backgroundColor = color.CGColor; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.h b/DashPay/Presentation/Shared/DWScrollingViewController.h similarity index 55% rename from DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.h rename to DashPay/Presentation/Shared/DWScrollingViewController.h index 0be908233..2208e0e0b 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.h +++ b/DashPay/Presentation/Shared/DWScrollingViewController.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,21 +15,21 @@ // limitations under the License. // -#import "dashwallet-Swift.h" #import NS_ASSUME_NONNULL_BEGIN -@interface DWSearchViewController : UIViewController +@interface DWScrollingViewController : UIViewController -@property (readonly, nonatomic, strong) UISearchBar *searchBar; +@property (readonly, nonatomic, strong) UIScrollView *scrollView; @property (readonly, nonatomic, strong) UIView *contentView; -@property (nonatomic, assign) BOOL disableSearchBarBecomesFirstResponderOnFirstAppearance; +/// Enabled by default +@property (nonatomic, assign) BOOL keyboardNotificationsEnabled; -- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height - animationDuration:(NSTimeInterval)animationDuration - animationCurve:(UIViewAnimationCurve)animationCurve; +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil + bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; +- (instancetype)initWithCoder:(nullable NSCoder *)coder NS_UNAVAILABLE; @end diff --git a/DashPay/Presentation/Shared/DWScrollingViewController.m b/DashPay/Presentation/Shared/DWScrollingViewController.m new file mode 100644 index 000000000..0edb3aca9 --- /dev/null +++ b/DashPay/Presentation/Shared/DWScrollingViewController.m @@ -0,0 +1,120 @@ +// +// 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 "DWScrollingViewController.h" + +#import + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWScrollingViewController () + +@property (null_resettable, nonatomic, strong) UIScrollView *scrollView; +@property (null_resettable, nonatomic, strong) UIView *contentView; +@property (null_resettable, nonatomic, strong) NSLayoutConstraint *bottomConstraint; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWScrollingViewController + +- (instancetype)init { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + self.keyboardNotificationsEnabled = YES; + } + return self; +} + +- (UIScrollView *)scrollView { + if (_scrollView == nil) { + _scrollView = [[UIScrollView alloc] init]; + _scrollView.translatesAutoresizingMaskIntoConstraints = NO; + _scrollView.alwaysBounceVertical = YES; + _scrollView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; + _scrollView.preservesSuperviewLayoutMargins = YES; + } + return _scrollView; +} + +- (UIView *)contentView { + if (_contentView == nil) { + _contentView = [[UIView alloc] init]; + _contentView.translatesAutoresizingMaskIntoConstraints = NO; + _contentView.preservesSuperviewLayoutMargins = YES; + } + return _contentView; +} + +- (NSLayoutConstraint *)bottomConstraint { + if (_bottomConstraint == nil) { + _bottomConstraint = [self.view.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor]; + } + return _bottomConstraint; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self.view addSubview:self.scrollView]; + [self.scrollView addSubview:self.contentView]; + + [NSLayoutConstraint activateConstraints:@[ + [self.scrollView.topAnchor constraintEqualToAnchor:self.view.topAnchor], + [self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:self.scrollView.trailingAnchor], + self.bottomConstraint, + + [self.contentView.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor], + [self.contentView.leadingAnchor constraintEqualToAnchor:self.scrollView.leadingAnchor], + [self.scrollView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], + [self.scrollView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor], + [self.contentView.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor], + ]]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + if (self.keyboardNotificationsEnabled) { + [self ka_startObservingKeyboardNotifications]; + } +} + +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + + if (self.keyboardNotificationsEnabled) { + [self ka_stopObservingKeyboardNotifications]; + } +} + +- (UIViewController *)childViewControllerForStatusBarStyle { + return self.childViewControllers.firstObject; +} + +- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height + animationDuration:(NSTimeInterval)animationDuration + animationCurve:(UIViewAnimationCurve)animationCurve { + self.bottomConstraint.constant = height; + [self.view layoutIfNeeded]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.h b/DashPay/Presentation/Shared/UIImageView+DWDPAvatar.h similarity index 82% rename from DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.h rename to DashPay/Presentation/Shared/UIImageView+DWDPAvatar.h index 2c4e2b0af..9b92b9d5b 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.h +++ b/DashPay/Presentation/Shared/UIImageView+DWDPAvatar.h @@ -19,9 +19,9 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWContactsSearchInfoHeaderView : UICollectionReusableView +@interface UIImageView (DWDPAvatar) -@property (readonly, nonatomic, strong) UILabel *titleLabel; +- (void)dw_setAvatarWithURLString:(NSString *)urlString completion:(void (^)(UIImage *_Nullable image))completion; @end diff --git a/DashPay/Presentation/Shared/UIImageView+DWDPAvatar.m b/DashPay/Presentation/Shared/UIImageView+DWDPAvatar.m new file mode 100644 index 000000000..943f88165 --- /dev/null +++ b/DashPay/Presentation/Shared/UIImageView+DWDPAvatar.m @@ -0,0 +1,136 @@ +// +// 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 "UIImageView+DWDPAvatar.h" + +#import "DWDPAvatarView.h" +#import +#import + +@implementation UIImageView (DWDPAvatar) + +- (void)dw_setAvatarWithURLString:(NSString *)urlString completion:(void (^)(UIImage *_Nullable image))completion { + if (urlString.length == 0) { + if (completion) { + completion(nil); + } + return; + } + + + NSURL *url = [NSURL URLWithString:urlString]; + NSURL *originalURL = [url copy]; + if (url == nil) { + if (completion) { + completion(nil); + } + return; + } + + // has crop params + CGRect cropRectOfInterest = CGRectNull; + if (urlString.length > 0 && [urlString rangeOfString:DPCropParameterName].location != NSNotFound) { + NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + NSMutableArray *queryItems = [components.queryItems mutableCopy]; + NSURLQueryItem *cropItem = nil; + for (NSURLQueryItem *item in components.queryItems) { + if ([item.name isEqualToString:DPCropParameterName]) { + cropItem = item; + break; + } + } + + if (cropItem != nil) { + [queryItems removeObject:cropItem]; + + NSArray *params = [cropItem.value componentsSeparatedByString:@","]; + if (params.count != 4) { + params = [cropItem.value componentsSeparatedByString:@"%2C"]; + } + + if (params.count == 4) { + CGFloat x = [params[0] doubleValue]; + CGFloat y = [params[1] doubleValue]; + CGFloat w = [params[2] doubleValue] - x; + CGFloat h = [params[3] doubleValue] - y; + cropRectOfInterest = CGRectMake(x, y, w, h); + } + + components.queryItems = queryItems.count > 0 ? queryItems : nil; + url = components.URL; + } + } + + __weak typeof(self) weakSelf = self; + [self sd_setImageWithURL:url + completed:^(UIImage *_Nullable image, NSError *_Nullable error, SDImageCacheType cacheType, NSURL *_Nullable imageURL) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (image == nil || CGRectIsNull(cropRectOfInterest)) { + if (completion) { + completion(image); + } + + return; + } + + dispatch_async([strongSelf.class processingQueue], ^{ + NSString *croppedKey = originalURL.absoluteString; + + UIImage *croppedImage = [SDImageCache.sharedImageCache imageFromCacheForKey:croppedKey]; + if (croppedImage != nil) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) { + completion(croppedImage); + } + }); + + return; + } + + CGSize imageSize = image.size; + CGRect cropRect = CGRectMake(cropRectOfInterest.origin.x * imageSize.width, + cropRectOfInterest.origin.y * imageSize.height, + cropRectOfInterest.size.width * imageSize.width, + cropRectOfInterest.size.height * imageSize.height); + croppedImage = [image croppedImageWithFrame:cropRect angle:0 circularClip:NO]; + if (croppedImage) { + [SDImageCache.sharedImageCache storeImage:croppedImage forKey:croppedKey completion:nil]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) { + completion(croppedImage); + } + }); + }); + }]; +} + ++ (dispatch_queue_t)processingQueue { + static dispatch_queue_t _sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _sharedInstance = dispatch_queue_create("dw.dp.avatar-processing-queue", DISPATCH_QUEUE_SERIAL); + }); + return _sharedInstance; +} + +@end diff --git a/DashPay/Presentation/Tx Details/DWTxDetailPopupViewController.h b/DashPay/Presentation/Tx Details/DWTxDetailPopupViewController.h new file mode 100644 index 000000000..d1d56f54e --- /dev/null +++ b/DashPay/Presentation/Tx Details/DWTxDetailPopupViewController.h @@ -0,0 +1,46 @@ +// +// 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 DSTransaction; +@protocol DWTransactionListDataProviderProtocol; +@class DWTxDetailPopupViewController; +@protocol DWDPBasicUserItem; + +@protocol DWTxDetailPopupViewControllerDelegate + +- (void)txDetailPopupViewController:(DWTxDetailPopupViewController *)controller openUserItem:(id)userItem; + +@end + +@interface DWTxDetailPopupViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +- (instancetype)initWithTransaction:(DSTransaction *)transaction + dataProvider:(id)dataProvider; + +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil + bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; +- (instancetype)initWithCoder:(nullable NSCoder *)aDecoder NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Tx Details/DWTxDetailPopupViewController.m b/DashPay/Presentation/Tx Details/DWTxDetailPopupViewController.m new file mode 100644 index 000000000..c02e78c5b --- /dev/null +++ b/DashPay/Presentation/Tx Details/DWTxDetailPopupViewController.m @@ -0,0 +1,119 @@ +// +// 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 "DWTxDetailPopupViewController.h" + +#import "DWModalPopupTransition.h" +#import "DWUIKit.h" +#import "dashwallet-Swift.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const CORNER_RADIUS = 8.0; + +static CGFloat VerticalPadding(void) { + if (IS_IPAD) { + return 32.0; + } + else if (IS_IPHONE_6 || IS_IPHONE_5_OR_LESS) { + return 16.0; + } + else { + return 24.0; + } +} + +@interface DWTxDetailPopupViewController () + +@property (readonly, nonatomic, strong) DSTransaction *transaction; +@property (readonly, nonatomic, strong) id dataProvider; + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; + +@end + +@implementation DWTxDetailPopupViewController + +- (instancetype)initWithTransaction:(DSTransaction *)transaction + dataProvider:(id)dataProvider { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _transaction = transaction; + _dataProvider = dataProvider; + + _modalTransition = [[DWModalPopupTransition alloc] init]; + + self.transitioningDelegate = self.modalTransition; + self.modalPresentationStyle = UIModalPresentationCustom; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self setupView]; +} + +#pragma mark - DWTxDetailViewControllerDelegate + +- (void)txDetailViewController:(DWTxDetailViewController *)controller closeButtonAction:(UIButton *)sender { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)txDetailViewController:(DWTxDetailViewController *)controller openUserItem:(id)userItem { + [self.delegate txDetailPopupViewController:self openUserItem:userItem]; +} + +#pragma mark - Private + +- (void)setupView { + self.view.backgroundColor = [UIColor dw_backgroundColor]; + self.view.clipsToBounds = YES; + self.view.layer.cornerRadius = CORNER_RADIUS; + + DWTxDetailViewController *controller = + [[DWTxDetailViewController alloc] initWithModel:[[DWTxDetailModel alloc] initWithTransaction: _transaction]]; + + //controller.delegate = self; + + [self addChildViewController:controller]; + + UIView *childView = controller.view; + UIView *contentView = self.view; + + childView.translatesAutoresizingMaskIntoConstraints = NO; + childView.preservesSuperviewLayoutMargins = YES; + [contentView addSubview:childView]; + + const CGFloat padding = VerticalPadding(); + [NSLayoutConstraint activateConstraints:@[ + [childView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [childView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [childView.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor], + [childView.topAnchor constraintGreaterThanOrEqualToAnchor:contentView.topAnchor + constant:padding], + [childView.bottomAnchor constraintGreaterThanOrEqualToAnchor:contentView.bottomAnchor + constant:-padding], + ]]; + + [controller didMoveToParentViewController:self]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 4fe1069c6..2040e2f72 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -119,8 +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 */; }; - 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 */; }; 2A1AF6DF23C7681B00442AF5 /* DWShortcutCollectionViewCell~iphone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A1AF6DD23C7681B00442AF5 /* DWShortcutCollectionViewCell~iphone.xib */; }; 2A1B7D972322D0BC00BA8C6A /* DWTitleDetailCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7D962322D0BC00BA8C6A /* DWTitleDetailCellModel.m */; }; @@ -143,8 +141,6 @@ 2A307CBF22E8A44200A18347 /* DWButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A307CBE22E8A44200A18347 /* DWButton.m */; }; 2A36A88B2350A05B0014DC60 /* DWBackupSeedPhraseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A36A88A2350A05B0014DC60 /* DWBackupSeedPhraseViewController.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 */; }; 2A3DC86E239717C2004B3DBA /* DWRecoverWalletCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3DC86D239717C2004B3DBA /* DWRecoverWalletCommand.m */; }; 2A3DC87123972331004B3DBA /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3DC87023972331004B3DBA /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.m */; }; 2A4430E722CBB6EC009BAF7F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4430E622CBB6EC009BAF7F /* AppDelegate.m */; }; @@ -178,28 +174,19 @@ 2A4E533822F023AB00E5168A /* DWHomeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E533722F023AB00E5168A /* DWHomeModel.m */; }; 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 /* 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 */; }; - 2A56EF002419310C002C32F3 /* DWDashPayConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EEFF2419310C002C32F3 /* DWDashPayConstants.m */; }; 2A56EF0624193AEB002C32F3 /* DWDashPayModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EF0524193AEB002C32F3 /* DWDashPayModel.m */; }; 2A58815921A5906C00FD4D2C /* DWBaseLegacyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A58815821A5906C00FD4D2C /* DWBaseLegacyViewController.m */; }; - 2A5BD5922451D68300688A8D /* DWMinLengthUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD5912451D68300688A8D /* DWMinLengthUsernameValidationRule.m */; }; - 2A5BD5952451DCAF00688A8D /* DWAllowedCharactersUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD5942451DCAF00688A8D /* DWAllowedCharactersUsernameValidationRule.m */; }; - 2A5BD5982451DD0700688A8D /* DWMaxLengthUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD5972451DD0700688A8D /* DWMaxLengthUsernameValidationRule.m */; }; - 2A5BD59E2451F39500688A8D /* DWCheckExistenceUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD59D2451F39500688A8D /* DWCheckExistenceUsernameValidationRule.m */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; @@ -231,49 +218,17 @@ 2A7A7C16234B763600451078 /* DWLocalCurrencyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7C15234B763600451078 /* DWLocalCurrencyViewController.m */; }; 2A7A7C1D234B771400451078 /* DWLocalCurrencyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7C1C234B771400451078 /* DWLocalCurrencyModel.m */; }; 2A7A7C20234B79B700451078 /* DWLocalCurrencyTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7C1F234B79B700451078 /* DWLocalCurrencyTableViewCell.m */; }; - 2A7AF3152480DA51001D74F9 /* DWIncomingFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3142480DA51001D74F9 /* DWIncomingFetchedDataSource.m */; }; - 2A7AF3182480E35A001D74F9 /* DWContactsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3172480E35A001D74F9 /* DWContactsFetchedDataSource.m */; }; - 2A7AF31C2480E6AD001D74F9 /* DWNotificationsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF31B2480E6AD001D74F9 /* DWNotificationsModel.m */; }; - 2A7AF32824814A17001D74F9 /* DWNotificationsData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF32724814A17001D74F9 /* DWNotificationsData.m */; }; - 2A7AF3402481A1B2001D74F9 /* DWDPGenericItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF33F2481A1B2001D74F9 /* DWDPGenericItemView.m */; }; - 2A7AF34424822AE0001D74F9 /* DWDPGenericContactRequestItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34324822AE0001D74F9 /* DWDPGenericContactRequestItemView.m */; }; - 2A7AF34924823167001D74F9 /* DWDPGenericImageItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34824823167001D74F9 /* DWDPGenericImageItemView.m */; }; - 2A7AF34C24823315001D74F9 /* DWDPGenericStatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34B24823315001D74F9 /* DWDPGenericStatusItemView.m */; }; - 2A7AF3502482374D001D74F9 /* DWDPBasicCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34F2482374D001D74F9 /* DWDPBasicCell.m */; }; - 2A7AF35324823EC8001D74F9 /* DWDPIncomingRequestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF35224823EC8001D74F9 /* DWDPIncomingRequestCell.m */; }; - 2A7AF35624824F97001D74F9 /* DWDPImageStatusCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF35524824F97001D74F9 /* DWDPImageStatusCell.m */; }; - 2A7AF35924824FEB001D74F9 /* DWDPTextStatusCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF35824824FEB001D74F9 /* DWDPTextStatusCell.m */; }; - 2A7AF36324825A0C001D74F9 /* DWDPUserObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36224825A0C001D74F9 /* DWDPUserObject.m */; }; - 2A7AF3662482666C001D74F9 /* DWDPIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3652482666C001D74F9 /* DWDPIncomingRequestObject.m */; }; - 2A7AF36924826681001D74F9 /* DWDPRespondedIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36824826681001D74F9 /* DWDPRespondedIncomingRequestObject.m */; }; - 2A7AF36C248266FB001D74F9 /* DWDPEstablishedContactObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36B248266FB001D74F9 /* DWDPEstablishedContactObject.m */; }; - 2A7AF36F24826737001D74F9 /* DWDPContactObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36E24826737001D74F9 /* DWDPContactObject.m */; }; - 2A7AF37224826CDF001D74F9 /* DWDPAcceptedRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF37124826CDF001D74F9 /* DWDPAcceptedRequestNotificationObject.m */; }; - 2A7AF3752482703C001D74F9 /* DWDPNewIncomingRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3742482703C001D74F9 /* DWDPNewIncomingRequestNotificationObject.m */; }; - 2A7AF378248270A4001D74F9 /* DWDPEstablishedContactNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF377248270A4001D74F9 /* DWDPEstablishedContactNotificationObject.m */; }; - 2A7AF37B2482756D001D74F9 /* DWDPPendingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF37A2482756D001D74F9 /* DWDPPendingRequestObject.m */; }; - 2A7AF380248280CE001D74F9 /* DWDPSearchItemsFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF37F248280CE001D74F9 /* DWDPSearchItemsFactory.m */; }; - 2A7AF3832482954C001D74F9 /* DWDPContactsItemsFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3822482954C001D74F9 /* DWDPContactsItemsFactory.m */; }; - 2A7AF38924829AF2001D74F9 /* UICollectionView+DWDPItemDequeue.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF38824829AF2001D74F9 /* UICollectionView+DWDPItemDequeue.m */; }; - 2A7AF38F2482BDF1001D74F9 /* UIFont+DWDPItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF38E2482BDF1001D74F9 /* UIFont+DWDPItem.m */; }; - 2A7AF3992482E32E001D74F9 /* DWDashPayContactsActions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3982482E32E001D74F9 /* DWDashPayContactsActions.m */; }; 2A7AF39E2482FE46001D74F9 /* DWDateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF39D2482FE46001D74F9 /* DWDateFormatter.m */; }; 2A7F3B1B238C646600DEA3EF /* DWAdvancedSecurityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7F3B1A238C646600DEA3EF /* DWAdvancedSecurityViewController.m */; }; 2A7F3B1F238C651200DEA3EF /* DWSecurityStatusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7F3B1E238C651200DEA3EF /* DWSecurityStatusView.m */; }; 2A7F3B21238C653000DEA3EF /* DWSecurityStatusView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A7F3B20238C653000DEA3EF /* DWSecurityStatusView.xib */; }; - 2A80F39524D86201003E3B1E /* DWModalUserProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A80F39424D86201003E3B1E /* DWModalUserProfileViewController.m */; }; - 2A80F3D924DC55CD003E3B1E /* DWUserProfileSendRequestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A80F3D824DC55CC003E3B1E /* DWUserProfileSendRequestCell.m */; }; 2A811559269CE09300215F81 /* uphold-logout.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 2A811558269CE09300215F81 /* uphold-logout.jpg */; }; - 2A827B7224B5CA1800A42042 /* DWListCollectionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A827B7124B5CA1800A42042 /* DWListCollectionLayout.m */; }; 2A858A0F237EE89C0097A7B5 /* DSWatchTransactionDataObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A06237EE89B0097A7B5 /* DSWatchTransactionDataObject.m */; }; 2A858A10237EE89C0097A7B5 /* BRAppleWatchData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A08237EE89B0097A7B5 /* BRAppleWatchData.m */; }; 2A858A11237EE89C0097A7B5 /* DWPhoneWCSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A0C237EE89B0097A7B5 /* DWPhoneWCSessionManager.m */; }; 2A858A12237EE89C0097A7B5 /* BRAppleWatchTransactionData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A0E237EE89B0097A7B5 /* BRAppleWatchTransactionData.m */; }; 2A858A13237EE8A80097A7B5 /* BRAppleWatchData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A08237EE89B0097A7B5 /* BRAppleWatchData.m */; }; 2A858A14237EE8A80097A7B5 /* BRAppleWatchTransactionData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A0E237EE89B0097A7B5 /* BRAppleWatchTransactionData.m */; }; - 2A885FC82449B66500B9F679 /* DWSearchStateViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FC62449B66500B9F679 /* DWSearchStateViewController.m */; }; - 2A885FCC2449F08700B9F679 /* DWUserSearchResultViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FCB2449F08700B9F679 /* DWUserSearchResultViewController.m */; }; - 2A885FD02449F37A00B9F679 /* DWUserSearchModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FCF2449F37A00B9F679 /* DWUserSearchModel.m */; }; 2A885FD6244DFEF100B9F679 /* UIView+DWFindConstraints.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FD5244DFEF100B9F679 /* UIView+DWFindConstraints.m */; }; 2A8B9E2922FB1C5D00FF8653 /* DWSharedUIConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E2822FB1C5D00FF8653 /* DWSharedUIConstants.m */; }; 2A8B9E3D22FD71E100FF8653 /* Payments.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A8B9E3C22FD71E100FF8653 /* Payments.storyboard */; }; @@ -312,15 +267,9 @@ 2A913EB623A7E145006A2A59 /* DWTransactionStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913EB523A7E145006A2A59 /* DWTransactionStub.m */; }; 2A9172C425233DC50024B4C5 /* DWPhraseRepairViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9172C325233DC50024B4C5 /* DWPhraseRepairViewController.m */; }; 2A9172D325233F4F0024B4C5 /* DWPhraseRepairChildViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9172D225233F4F0024B4C5 /* DWPhraseRepairChildViewController.m */; }; - 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 */; }; 2A9CEBAD22E1DA4000A50237 /* DWAppRootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBAC22E1DA4000A50237 /* DWAppRootViewController.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 */; }; - 2A9E7DCA23F6BD5200CDA1EE /* DWUsernameValidationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DC923F6BD5200CDA1EE /* DWUsernameValidationView.m */; }; - 2A9E7DCE23F6C01A00CDA1EE /* DWUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DCD23F6C01A00CDA1EE /* DWUsernameValidationRule.m */; }; 2A9FFDF42230FF1A00956D5F /* UIView+DWAnimations.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDEF2230FF1A00956D5F /* UIView+DWAnimations.m */; }; 2A9FFDF52230FF1A00956D5F /* SFSafariViewController+DashWallet.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDF22230FF1A00956D5F /* SFSafariViewController+DashWallet.m */; }; 2A9FFE032230FF2B00956D5F /* DWUpholdTransactionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDFD2230FF2B00956D5F /* DWUpholdTransactionObject.m */; }; @@ -354,7 +303,6 @@ 2AA08534237D6CF500797F95 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AA08533237D6CF500797F95 /* CloudKit.framework */; }; 2AA87CFA26E5681100F0CEA6 /* DWCurrencyObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA87CF926E5681100F0CEA6 /* DWCurrencyObject.m */; }; 2AB231D42196E27300A6E7E6 /* StartStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2AB231D32196E27300A6E7E6 /* StartStoryboard.storyboard */; }; - 2AB2373824488DB80081B62C /* DWUserSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB2373724488DB80081B62C /* DWUserSearchViewController.m */; }; 2AB3415E23A8133A004E37A7 /* DWPayModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3415D23A8133A004E37A7 /* DWPayModelStub.m */; }; 2AB3416223A81E8B004E37A7 /* DWReceiveModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3416123A81E8B004E37A7 /* DWReceiveModelStub.m */; }; 2AB3416523A8213C004E37A7 /* DWBaseReceiveModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3416423A8213C004E37A7 /* DWBaseReceiveModel.m */; }; @@ -364,20 +312,14 @@ 2AB3417723A92978004E37A7 /* DWDemoAdvancedSecurityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3417623A92978004E37A7 /* DWDemoAdvancedSecurityViewController.m */; }; 2AB3417A23A929B6004E37A7 /* DWAdvancedSecurityModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3417923A929B6004E37A7 /* DWAdvancedSecurityModelStub.m */; }; 2AB3417F23A92A2A004E37A7 /* DWBaseAdvancedSecurityModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3417E23A92A2A004E37A7 /* DWBaseAdvancedSecurityModel.m */; }; - 2AB7303E24D0BC0400DCB420 /* UIColor+DWDashPay.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB7303D24D0BC0400DCB420 /* UIColor+DWDashPay.m */; }; 2AB7C907234DB82700A56795 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2AB7C906234DB82700A56795 /* About.storyboard */; }; 2AB7F7E72384676200C173AD /* DWWatchDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB7F7E62384676200C173AD /* DWWatchDataManager.swift */; }; 2AB7F7E923846F6000C173AD /* DWMainInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB7F7E823846F6000C173AD /* DWMainInterfaceController.swift */; }; 2AB7F7EB2384752C00C173AD /* DWTxInfoDisplayableInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB7F7EA2384752C00C173AD /* DWTxInfoDisplayableInterfaceController.swift */; }; 2ABCA9182357A61B00092C09 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2ABCA9172357A61B00092C09 /* Foundation.framework */; }; - 2AC52AD6241BB5FC00D9A829 /* DWDashPaySetupFlowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC52AD5241BB5FC00D9A829 /* DWDashPaySetupFlowController.m */; }; 2AC92C841FEB0A6D008CAEE0 /* DWQRScanViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC92C831FEB0A6D008CAEE0 /* DWQRScanViewController.m */; }; 2AC92C871FEB0AE8008CAEE0 /* DWQRScanView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC92C861FEB0AE8008CAEE0 /* DWQRScanView.m */; }; 2AC92C8A1FEB0B8B008CAEE0 /* DWQRScanModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC92C891FEB0B8B008CAEE0 /* DWQRScanModel.m */; }; - 2ACCA3A824BE0C7D00DB32DE /* DWDPTxListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3A724BE0C7D00DB32DE /* DWDPTxListCell.m */; }; - 2ACCA3AD24BE117300DB32DE /* DWProfileTxsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3AC24BE117300DB32DE /* DWProfileTxsFetchedDataSource.m */; }; - 2ACCA3B224BE3CDC00DB32DE /* DWUserProfileDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3B124BE3CDC00DB32DE /* DWUserProfileDataSourceObject.m */; }; - 2ACCA3B524BF280A00DB32DE /* DWDPTxObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3B424BF280A00DB32DE /* DWDPTxObject.m */; }; 2ACCD84D23180E7E00A96B62 /* DWPaymentOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD84C23180E7E00A96B62 /* DWPaymentOutput.m */; }; 2ACCD8592319399100A96B62 /* DWRequestAmountViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8582319399100A96B62 /* DWRequestAmountViewController.m */; }; 2ACCD85D231939FE00A96B62 /* DWRequestAmountContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD85C231939FE00A96B62 /* DWRequestAmountContentView.m */; }; @@ -407,37 +349,14 @@ 2AD1CEA822E0C8C900C99324 /* DWPreviewSeedPhraseModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CEA722E0C8C800C99324 /* DWPreviewSeedPhraseModel.m */; }; 2AD1CEAB22E18F1800C99324 /* DWGlobalOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CEAA22E18F1800C99324 /* DWGlobalOptions.m */; }; 2AD46232232A286000C71557 /* DWLockScreenModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD46231232A286000C71557 /* DWLockScreenModel.m */; }; - 2AD6E54D2487CE0100B52F14 /* DWContactsDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E54C2487CE0100B52F14 /* DWContactsDataSourceObject.m */; }; - 2AD6E5532487D50200B52F14 /* DWContactsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5522487D50200B52F14 /* DWContactsModel.m */; }; - 2AD6E5572487D8C000B52F14 /* DWContactsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5562487D8C000B52F14 /* DWContactsContentViewController.m */; }; - 2AD6E55A2487D9AF00B52F14 /* DWContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5592487D9AF00B52F14 /* DWContactsViewController.m */; }; - 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 */; }; 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 */; }; 2ADC722923B5547000D9DD37 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 2ADC722723B5547000D9DD37 /* Localizable.stringsdict */; }; 2ADC9D1C24603C4F001D7C0D /* UISearchBar+DWAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D1B24603C4F001D7C0D /* UISearchBar+DWAdditions.m */; }; - 2ADC9D712462D4AD001D7C0D /* DWUserProfileHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D6E2462D4AD001D7C0D /* DWUserProfileHeaderView.m */; }; - 2ADC9D722462D4AD001D7C0D /* DWStretchyHeaderListCollectionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D6F2462D4AD001D7C0D /* DWStretchyHeaderListCollectionLayout.m */; }; - 2ADC9D732462D4AD001D7C0D /* DWUserProfileNavigationTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D702462D4AD001D7C0D /* DWUserProfileNavigationTitleView.m */; }; - 2ADC9D7624640A2B001D7C0D /* DWUserProfileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D7524640A2B001D7C0D /* DWUserProfileModel.m */; }; - 2ADC9D7C24644E46001D7C0D /* DWUserProfileContactActionsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D7B24644E46001D7C0D /* DWUserProfileContactActionsCell.m */; }; 2ADF83FA23632D1C008459A7 /* BRBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 759816E619357D6F005060EA /* BRBubbleView.m */; }; 2ADF83FF23633116008459A7 /* SharedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2ADF83FE23633116008459A7 /* SharedAssets.xcassets */; }; 2ADF840023633121008459A7 /* SharedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2ADF83FE23633116008459A7 /* SharedAssets.xcassets */; }; - 2AE2F0F9245C16C8001DD722 /* DWUserProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE2F0F8245C16C8001DD722 /* DWUserProfileViewController.m */; }; 2AE4736B241BC5E300804DD4 /* UIViewController+DWDisplayError.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE4736A241BC5E300804DD4 /* UIViewController+DWDisplayError.m */; }; - 2AE8B64123CDB98A0016F221 /* DWCreateUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B64023CDB98A0016F221 /* DWCreateUsernameViewController.m */; }; - 2AE8B64423CDC0F50016F221 /* DWInputUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B64323CDC0F50016F221 /* DWInputUsernameViewController.m */; }; - 2AE8B66423CF09000016F221 /* DWConfirmUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B66323CF09000016F221 /* DWConfirmUsernameViewController.m */; }; - 2AE8B66B23CF0F390016F221 /* DWUsernamePendingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B66A23CF0F390016F221 /* DWUsernamePendingViewController.m */; }; - 2AE9549D23D0C4F4003612B3 /* DWBaseContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE9549C23D0C4F4003612B3 /* DWBaseContactsViewController.m */; }; - 2AEC5CB52493D87D00F4A689 /* DWNotificationsProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CB42493D87D00F4A689 /* DWNotificationsProvider.m */; }; - 2AEC5CBB2494045C00F4A689 /* DWNotificationsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CBA2494045C00F4A689 /* DWNotificationsFetchedDataSource.m */; }; - 2AEC5CBE24940EC200F4A689 /* DWDPOutgoingRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CBD24940EC200F4A689 /* DWDPOutgoingRequestNotificationObject.m */; }; - 2AEC5CC2249755BB00F4A689 /* DWDashPayContactsUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CC1249755BB00F4A689 /* DWDashPayContactsUpdater.m */; }; 2AF26F3B230C0E4C007F9228 /* DWBaseSeedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AF26F3A230C0E4C007F9228 /* DWBaseSeedViewController.m */; }; 2AFCB9BE23BE3C0800FF59A6 /* DWConfirmSendPaymentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFCB9BD23BE3C0800FF59A6 /* DWConfirmSendPaymentViewController.m */; }; 2AFCB9C123BE766D00FF59A6 /* DWUpholdConfirmViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFCB9C023BE766D00FF59A6 /* DWUpholdConfirmViewController.m */; }; @@ -665,17 +584,8 @@ BAA6E3F11BD5CA5900773205 /* BRAWReceiveMoneyInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA6E3F01BD5CA5900773205 /* BRAWReceiveMoneyInterfaceController.swift */; }; BAE12BF21B2DEE7F00895CC5 /* TodayExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = BAE12BE51B2DEE7F00895CC5 /* TodayExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; BAE12C061B2DEEF700895CC5 /* DWTodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BAE12C051B2DEEF700895CC5 /* DWTodayViewController.m */; }; - C3CA202C247E4AF300158074 /* DWNotificationsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3CA202B247E4AF300158074 /* DWNotificationsViewController.m */; }; - C3CA2030247E54C400158074 /* DWNoNotificationsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C3CA202F247E54C400158074 /* DWNoNotificationsCell.m */; }; C3DAD268246AA6F10001624F /* DWScreenshotWarningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD267246AA6F10001624F /* DWScreenshotWarningViewController.m */; }; - C3DAD26F246D46BF0001624F /* DWFetchedResultsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD26E246D46BF0001624F /* DWFetchedResultsDataSource.m */; }; - C3DAD2C024747F580001624F /* DWSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2BF24747F580001624F /* DWSearchViewController.m */; }; - C3DAD2C3247512BA0001624F /* DWBaseContactsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2C2247512BA0001624F /* DWBaseContactsContentViewController.m */; }; - C3DAD2C8247538AA0001624F /* DWTitleActionHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2C5247538A90001624F /* DWTitleActionHeaderView.m */; }; - C3DAD2C9247538AA0001624F /* DWTitleActionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C3DAD2C7247538AA0001624F /* DWTitleActionHeaderView.xib */; }; - C3DAD2CC24757A210001624F /* DWContactsSearchDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2CB24757A210001624F /* DWContactsSearchDataSourceObject.m */; }; C3DAD2CF247585C10001624F /* NSPredicate+DWFullTextSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2CE247585C10001624F /* NSPredicate+DWFullTextSearch.m */; }; - C3DAD2D52476886D0001624F /* DWContactsSearchInfoHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2D42476886D0001624F /* DWContactsSearchInfoHeaderView.m */; }; C909614D29EFF7D600002D82 /* WalletKeysOverviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909614C29EFF7D600002D82 /* WalletKeysOverviewModel.swift */; }; C909615129F158D700002D82 /* DerivationPathKeysViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909615029F158D700002D82 /* DerivationPathKeysViewController.swift */; }; C909615329F28E3700002D82 /* DerivationPathKeysModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909615229F28E3700002D82 /* DerivationPathKeysModel.swift */; }; @@ -686,6 +596,194 @@ 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 */; }; + C943B3172A408CED00AF23C5 /* DWErrorUpdatingUserProfileView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2BC2A408CEC00AF23C5 /* DWErrorUpdatingUserProfileView.m */; }; + C943B3182A408CED00AF23C5 /* DSBlockchainIdentity+DWDisplayTitleSubtitle.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2C22A408CEC00AF23C5 /* DSBlockchainIdentity+DWDisplayTitleSubtitle.m */; }; + C943B3192A408CED00AF23C5 /* DWDPUpdateProfileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2C32A408CEC00AF23C5 /* DWDPUpdateProfileModel.m */; }; + C943B31A2A408CED00AF23C5 /* DWCurrentUserProfileView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2C52A408CEC00AF23C5 /* DWCurrentUserProfileView.m */; }; + C943B31B2A408CED00AF23C5 /* DWUpdatingUserProfileView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2C62A408CEC00AF23C5 /* DWUpdatingUserProfileView.m */; }; + C943B31C2A408CED00AF23C5 /* DWDPWelcomeMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2C72A408CEC00AF23C5 /* DWDPWelcomeMenuView.m */; }; + C943B31D2A408CED00AF23C5 /* DWUserProfileContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2C82A408CEC00AF23C5 /* DWUserProfileContainerView.m */; }; + C943B31E2A408CED00AF23C5 /* DWUserProfileModalQRContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2CD2A408CEC00AF23C5 /* DWUserProfileModalQRContentView.m */; }; + C943B31F2A408CED00AF23C5 /* DWUserProfileModalQRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2CE2A408CEC00AF23C5 /* DWUserProfileModalQRViewController.m */; }; + C943B3202A408CED00AF23C5 /* DWImgurInfoChildView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2D42A408CEC00AF23C5 /* DWImgurInfoChildView.m */; }; + C943B3212A408CED00AF23C5 /* DWImgurItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2D62A408CEC00AF23C5 /* DWImgurItemView.m */; }; + C943B3222A408CED00AF23C5 /* DWImgurInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2D72A408CEC00AF23C5 /* DWImgurInfoViewController.m */; }; + C943B3232A408CED00AF23C5 /* DWFaceDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2DC2A408CEC00AF23C5 /* DWFaceDetector.m */; }; + C943B3242A408CED00AF23C5 /* DWRootEditProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2DD2A408CEC00AF23C5 /* DWRootEditProfileViewController.m */; }; + C943B3252A408CED00AF23C5 /* DWEditProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2DE2A408CEC00AF23C5 /* DWEditProfileViewController.m */; }; + C943B3262A408CED00AF23C5 /* DWCropAvatarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2DF2A408CEC00AF23C5 /* DWCropAvatarViewController.m */; }; + C943B3272A408CED00AF23C5 /* DWAvatarEditSelectorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2E12A408CEC00AF23C5 /* DWAvatarEditSelectorViewController.m */; }; + C943B3282A408CED00AF23C5 /* DWAvatarEditSelectorContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2E42A408CEC00AF23C5 /* DWAvatarEditSelectorContentView.m */; }; + C943B3292A408CED00AF23C5 /* DWExternalSourceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2E92A408CEC00AF23C5 /* DWExternalSourceViewController.m */; }; + C943B32A2A408CED00AF23C5 /* DWAvatarExternalSourceView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2EC2A408CEC00AF23C5 /* DWAvatarExternalSourceView.m */; }; + C943B32B2A408CED00AF23C5 /* DWAvatarExternalLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2EE2A408CEC00AF23C5 /* DWAvatarExternalLoadingView.m */; }; + C943B32C2A408CED00AF23C5 /* DWAvatarExternalSourceConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2EF2A408CEC00AF23C5 /* DWAvatarExternalSourceConfig.m */; }; + C943B32D2A408CED00AF23C5 /* DWAvatarPublicURLViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2F22A408CEC00AF23C5 /* DWAvatarPublicURLViewController.m */; }; + C943B32E2A408CED00AF23C5 /* DWAvatarGravatarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2F32A408CEC00AF23C5 /* DWAvatarGravatarViewController.m */; }; + C943B32F2A408CED00AF23C5 /* DWSaveAlertViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2F72A408CEC00AF23C5 /* DWSaveAlertViewController.m */; }; + C943B3302A408CED00AF23C5 /* DWSaveAlertChildView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2F82A408CEC00AF23C5 /* DWSaveAlertChildView.m */; }; + C943B3312A408CED00AF23C5 /* DWProfileAboutCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2FC2A408CEC00AF23C5 /* DWProfileAboutCellModel.m */; }; + C943B3322A408CED00AF23C5 /* DWProfileDisplayNameCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B2FD2A408CEC00AF23C5 /* DWProfileDisplayNameCellModel.m */; }; + C943B3332A408CED00AF23C5 /* DWTextFieldFormCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3002A408CEC00AF23C5 /* DWTextFieldFormCellModel.m */; }; + C943B3342A408CED00AF23C5 /* DWTextViewFormCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3022A408CEC00AF23C5 /* DWTextViewFormCellModel.m */; }; + C943B3352A408CED00AF23C5 /* DWEditProfileTextViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3072A408CEC00AF23C5 /* DWEditProfileTextViewCell.m */; }; + C943B3362A408CED00AF23C5 /* DWEditProfileBaseCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B30B2A408CEC00AF23C5 /* DWEditProfileBaseCell.m */; }; + C943B3372A408CED00AF23C5 /* DWEditProfileTextFieldCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B30C2A408CEC00AF23C5 /* DWEditProfileTextFieldCell.m */; }; + C943B3382A408CED00AF23C5 /* DWEditProfileAvatarView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B30D2A408CEC00AF23C5 /* DWEditProfileAvatarView.m */; }; + C943B3392A408CED00AF23C5 /* DWUploadAvatarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B30F2A408CEC00AF23C5 /* DWUploadAvatarViewController.m */; }; + C943B33A2A408CED00AF23C5 /* DWUploadAvatarModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3102A408CEC00AF23C5 /* DWUploadAvatarModel.m */; }; + C943B33B2A408CED00AF23C5 /* DWUploadAvatarChildView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3152A408CEC00AF23C5 /* DWUploadAvatarChildView.m */; }; + C943B33C2A408CED00AF23C5 /* DWHourGlassAnimationView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3162A408CEC00AF23C5 /* DWHourGlassAnimationView.m */; }; + C943B3402A408E5500AF23C5 /* DWMainMenuViewController+DashPay.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B33F2A408E5500AF23C5 /* DWMainMenuViewController+DashPay.m */; }; + C943B3432A409F9E00AF23C5 /* UIImageView+DWDPAvatar.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3422A409F9E00AF23C5 /* UIImageView+DWDPAvatar.m */; }; + C943B3462A409FFA00AF23C5 /* DWDPAvatarView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3442A409FFA00AF23C5 /* DWDPAvatarView.m */; }; + C943B34D2A40A4C500AF23C5 /* DWInfoPopupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B34A2A40A4C500AF23C5 /* DWInfoPopupViewController.m */; }; + C943B34E2A40A4C500AF23C5 /* DWInfoPopupContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B34B2A40A4C500AF23C5 /* DWInfoPopupContentView.m */; }; + C943B4AB2A40A54600AF23C5 /* DWContactsPlaceholderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3522A40A54500AF23C5 /* DWContactsPlaceholderViewController.m */; }; + C943B4AC2A40A54600AF23C5 /* DWContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3542A40A54500AF23C5 /* DWContactsViewController.m */; }; + C943B4AD2A40A54600AF23C5 /* DWBaseContactsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3552A40A54500AF23C5 /* DWBaseContactsContentViewController.m */; }; + C943B4AE2A40A54600AF23C5 /* DWNoContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3572A40A54500AF23C5 /* DWNoContactsViewController.m */; }; + C943B4AF2A40A54600AF23C5 /* DWInvitationSuggestionView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3582A40A54500AF23C5 /* DWInvitationSuggestionView.m */; }; + C943B4B02A40A54600AF23C5 /* DWContactsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B35B2A40A54500AF23C5 /* DWContactsContentViewController.m */; }; + C943B4B12A40A54600AF23C5 /* DWBaseContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B35C2A40A54500AF23C5 /* DWBaseContactsViewController.m */; }; + C943B4B22A40A54600AF23C5 /* DWContactsDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B35F2A40A54500AF23C5 /* DWContactsDataSourceObject.m */; }; + C943B4B32A40A54600AF23C5 /* DWContactsSearchDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3602A40A54500AF23C5 /* DWContactsSearchDataSourceObject.m */; }; + C943B4B42A40A54600AF23C5 /* DWIncomingFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3682A40A54500AF23C5 /* DWIncomingFetchedDataSource.m */; }; + C943B4B52A40A54600AF23C5 /* DWContactsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3692A40A54600AF23C5 /* DWContactsFetchedDataSource.m */; }; + C943B4B62A40A54600AF23C5 /* DWFetchedResultsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B36A2A40A54600AF23C5 /* DWFetchedResultsDataSource.m */; }; + C943B4B72A40A54600AF23C5 /* DWBaseContactsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B36F2A40A54600AF23C5 /* DWBaseContactsModel.m */; }; + C943B4B82A40A54600AF23C5 /* DWContactsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3702A40A54600AF23C5 /* DWContactsModel.m */; }; + C943B4B92A40A54600AF23C5 /* DWRequestsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3722A40A54600AF23C5 /* DWRequestsContentViewController.m */; }; + C943B4BA2A40A54600AF23C5 /* DWRequestsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3742A40A54600AF23C5 /* DWRequestsModel.m */; }; + C943B4BB2A40A54600AF23C5 /* DWRequestsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3782A40A54600AF23C5 /* DWRequestsViewController.m */; }; + C943B4BC2A40A54600AF23C5 /* DWRootContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3792A40A54600AF23C5 /* DWRootContactsViewController.m */; }; + C943B4BD2A40A54600AF23C5 /* DWGlobalMatchHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3802A40A54600AF23C5 /* DWGlobalMatchHeaderView.m */; }; + C943B4BE2A40A54600AF23C5 /* DWGlobalMatchFailedHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3812A40A54600AF23C5 /* DWGlobalMatchFailedHeaderView.m */; }; + C943B4BF2A40A54600AF23C5 /* DWContactsSearchPlaceholderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3822A40A54600AF23C5 /* DWContactsSearchPlaceholderView.m */; }; + C943B4C02A40A54600AF23C5 /* DWContactsSearchInfoHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3852A40A54600AF23C5 /* DWContactsSearchInfoHeaderView.m */; }; + C943B4C12A40A54600AF23C5 /* DWTitleActionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C943B3872A40A54600AF23C5 /* DWTitleActionHeaderView.xib */; }; + C943B4C22A40A54600AF23C5 /* BaseCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3892A40A54600AF23C5 /* BaseCollectionReusableView.m */; }; + C943B4C32A40A54600AF23C5 /* DWTitleActionHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B38A2A40A54600AF23C5 /* DWTitleActionHeaderView.m */; }; + C943B4C42A40A54600AF23C5 /* DWSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B38F2A40A54600AF23C5 /* DWSearchViewController.m */; }; + C943B4C52A40A54600AF23C5 /* DWUserSearchModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3942A40A54600AF23C5 /* DWUserSearchModel.m */; }; + C943B4C62A40A54600AF23C5 /* DWUserSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3962A40A54600AF23C5 /* DWUserSearchViewController.m */; }; + C943B4C72A40A54600AF23C5 /* DWUserSearchResultViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B39A2A40A54600AF23C5 /* DWUserSearchResultViewController.m */; }; + C943B4C82A40A54600AF23C5 /* DWSearchStateViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B39B2A40A54600AF23C5 /* DWSearchStateViewController.m */; }; + C943B4C92A40A54600AF23C5 /* DWUsernamePendingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B39F2A40A54600AF23C5 /* DWUsernamePendingViewController.m */; }; + C943B4CA2A40A54600AF23C5 /* DWUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3A42A40A54600AF23C5 /* DWUsernameValidationRule.m */; }; + C943B4CB2A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3A62A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m */; }; + C943B4CC2A40A54600AF23C5 /* DWLengthUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3A92A40A54600AF23C5 /* DWLengthUsernameValidationRule.m */; }; + C943B4CD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3AD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m */; }; + C943B4CE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3AE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m */; }; + C943B4CF2A40A54600AF23C5 /* DWInputUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3AF2A40A54600AF23C5 /* DWInputUsernameViewController.m */; }; + C943B4D02A40A54600AF23C5 /* DWCreateUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B02A40A54600AF23C5 /* DWCreateUsernameViewController.m */; }; + C943B4D12A40A54600AF23C5 /* DWUsernameHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B22A40A54600AF23C5 /* DWUsernameHeaderView.m */; }; + C943B4D22A40A54600AF23C5 /* DWPlanetarySystemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B32A40A54600AF23C5 /* DWPlanetarySystemView.m */; }; + C943B4D32A40A54600AF23C5 /* DWTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B42A40A54600AF23C5 /* DWTextField.m */; }; + C943B4D42A40A54600AF23C5 /* DWUsernameValidationView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B92A40A54600AF23C5 /* DWUsernameValidationView.m */; }; + C943B4D52A40A54600AF23C5 /* DWRegistrationCompletedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3BB2A40A54600AF23C5 /* DWRegistrationCompletedViewController.m */; }; + C943B4D62A40A54600AF23C5 /* DWDashPaySetupFlowController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3BE2A40A54600AF23C5 /* DWDashPaySetupFlowController.m */; }; + C943B4D82A40A54600AF23C5 /* DWConfirmUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3C22A40A54600AF23C5 /* DWConfirmUsernameViewController.m */; }; + C943B4D92A40A54600AF23C5 /* DWConfirmUsernameContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3C52A40A54600AF23C5 /* DWConfirmUsernameContentView.m */; }; + C943B4DA2A40A54600AF23C5 /* DWConfirmUsernameContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C943B3C62A40A54600AF23C5 /* DWConfirmUsernameContentView.xib */; }; + C943B4DB2A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3CA2A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.m */; }; + C943B4DC2A40A54600AF23C5 /* DWInvitationFlowViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3CB2A40A54600AF23C5 /* DWInvitationFlowViewController.m */; }; + C943B4DD2A40A54600AF23C5 /* DWDPWelcomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3CC2A40A54600AF23C5 /* DWDPWelcomeViewController.m */; }; + C943B4DE2A40A54600AF23C5 /* DWDPWelcomePageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3CD2A40A54600AF23C5 /* DWDPWelcomePageViewController.m */; }; + C943B4DF2A40A54600AF23C5 /* DWGetStartedContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3CF2A40A54600AF23C5 /* DWGetStartedContentViewController.m */; }; + C943B4E02A40A54600AF23C5 /* DWGetStartedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3D12A40A54600AF23C5 /* DWGetStartedViewController.m */; }; + C943B4E12A40A54600AF23C5 /* DWGetStartedItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3D62A40A54600AF23C5 /* DWGetStartedItemView.m */; }; + C943B4E22A40A54600AF23C5 /* DWPassthroughView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3DD2A40A54600AF23C5 /* DWPassthroughView.m */; }; + C943B4E32A40A54600AF23C5 /* DWPassthroughStackView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3DE2A40A54600AF23C5 /* DWPassthroughStackView.m */; }; + C943B4E72A40A54600AF23C5 /* DWUserProfileDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3EE2A40A54600AF23C5 /* DWUserProfileDataSourceObject.m */; }; + C943B4E82A40A54600AF23C5 /* DWProfileTxsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3EF2A40A54600AF23C5 /* DWProfileTxsFetchedDataSource.m */; }; + C943B4E92A40A54600AF23C5 /* DWUserProfileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3F32A40A54600AF23C5 /* DWUserProfileModel.m */; }; + C943B4EA2A40A54600AF23C5 /* DWUserProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3F42A40A54600AF23C5 /* DWUserProfileViewController.m */; }; + C943B4EB2A40A54600AF23C5 /* DWModalUserProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3F52A40A54600AF23C5 /* DWModalUserProfileViewController.m */; }; + C943B4EC2A40A54600AF23C5 /* DWUserProfileSendRequestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3F82A40A54600AF23C5 /* DWUserProfileSendRequestCell.m */; }; + C943B4ED2A40A54600AF23C5 /* DWPendingContactInfoView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3FA2A40A54600AF23C5 /* DWPendingContactInfoView.m */; }; + C943B4EE2A40A54600AF23C5 /* DWStretchyHeaderListCollectionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3FD2A40A54600AF23C5 /* DWStretchyHeaderListCollectionLayout.m */; }; + C943B4EF2A40A54600AF23C5 /* DWUserProfileHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3FE2A40A54600AF23C5 /* DWUserProfileHeaderView.m */; }; + C943B4F02A40A54600AF23C5 /* DWUserProfileNavigationTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4012A40A54600AF23C5 /* DWUserProfileNavigationTitleView.m */; }; + C943B4F12A40A54600AF23C5 /* DWUserProfileContactActionsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4022A40A54600AF23C5 /* DWUserProfileContactActionsCell.m */; }; + C943B4F22A40A54600AF23C5 /* DWDPNewIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4052A40A54600AF23C5 /* DWDPNewIncomingRequestObject.m */; }; + C943B4F32A40A54600AF23C5 /* DWDPEstablishedContactObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4072A40A54600AF23C5 /* DWDPEstablishedContactObject.m */; }; + C943B4F42A40A54600AF23C5 /* DWDPUserObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4092A40A54600AF23C5 /* DWDPUserObject.m */; }; + C943B4F52A40A54600AF23C5 /* DWDPIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B40C2A40A54600AF23C5 /* DWDPIncomingRequestObject.m */; }; + C943B4F62A40A54600AF23C5 /* DWDPContactObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B40D2A40A54600AF23C5 /* DWDPContactObject.m */; }; + C943B4F72A40A54600AF23C5 /* DWDPTxObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4102A40A54600AF23C5 /* DWDPTxObject.m */; }; + C943B4F82A40A54600AF23C5 /* DWDPPendingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4122A40A54600AF23C5 /* DWDPPendingRequestObject.m */; }; + C943B4F92A40A54600AF23C5 /* DWDPAcceptedRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4152A40A54600AF23C5 /* DWDPAcceptedRequestNotificationObject.m */; }; + C943B4FA2A40A54600AF23C5 /* DWDPOutgoingRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4162A40A54600AF23C5 /* DWDPOutgoingRequestNotificationObject.m */; }; + C943B4FB2A40A54600AF23C5 /* DWDPNewIncomingRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4172A40A54600AF23C5 /* DWDPNewIncomingRequestNotificationObject.m */; }; + C943B4FC2A40A54600AF23C5 /* DWDPEstablishedContactNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B41B2A40A54600AF23C5 /* DWDPEstablishedContactNotificationObject.m */; }; + C943B4FD2A40A54600AF23C5 /* DWDPRespondedIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B41D2A40A54600AF23C5 /* DWDPRespondedIncomingRequestObject.m */; }; + C943B4FE2A40A54600AF23C5 /* DWDPTextStatusCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B41F2A40A54600AF23C5 /* DWDPTextStatusCell.m */; }; + C943B4FF2A40A54600AF23C5 /* DWDPGenericContactRequestItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4222A40A54600AF23C5 /* DWDPGenericContactRequestItemView.m */; }; + C943B5002A40A54600AF23C5 /* DWDPTxItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4232A40A54600AF23C5 /* DWDPTxItemView.m */; }; + C943B5012A40A54600AF23C5 /* DWDPGenericItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4252A40A54600AF23C5 /* DWDPGenericItemView.m */; }; + C943B5022A40A54600AF23C5 /* DWDPGenericStatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4272A40A54600AF23C5 /* DWDPGenericStatusItemView.m */; }; + C943B5032A40A54600AF23C5 /* DWDPGenericImageItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4282A40A54600AF23C5 /* DWDPGenericImageItemView.m */; }; + C943B5042A40A54600AF23C5 /* DWDPImageStatusCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B42D2A40A54600AF23C5 /* DWDPImageStatusCell.m */; }; + C943B5052A40A54600AF23C5 /* DWDPIncomingRequestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B42E2A40A54600AF23C5 /* DWDPIncomingRequestCell.m */; }; + C943B5062A40A54600AF23C5 /* UICollectionView+DWDPItemDequeue.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4302A40A54600AF23C5 /* UICollectionView+DWDPItemDequeue.m */; }; + C943B5072A40A54600AF23C5 /* DWDPBasicCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4312A40A54600AF23C5 /* DWDPBasicCell.m */; }; + C943B5082A40A54600AF23C5 /* DWDPTxListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4332A40A54600AF23C5 /* DWDPTxListCell.m */; }; + C943B5092A40A54600AF23C5 /* UIFont+DWDPItem.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4352A40A54600AF23C5 /* UIFont+DWDPItem.m */; }; + C943B50A2A40A54600AF23C5 /* DWDPContactsItemsFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4482A40A54600AF23C5 /* DWDPContactsItemsFactory.m */; }; + C943B50B2A40A54600AF23C5 /* DWDPSearchItemsFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4492A40A54600AF23C5 /* DWDPSearchItemsFactory.m */; }; + C943B50C2A40A54600AF23C5 /* DPAlertViewController+DWInvite.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B44F2A40A54600AF23C5 /* DPAlertViewController+DWInvite.m */; }; + C943B50D2A40A54600AF23C5 /* DWConfirmInvitationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4522A40A54600AF23C5 /* DWConfirmInvitationViewController.m */; }; + C943B50E2A40A54600AF23C5 /* DWConfirmInvitationContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4532A40A54600AF23C5 /* DWConfirmInvitationContentView.m */; }; + C943B50F2A40A54600AF23C5 /* DWConfirmInvitationContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C943B4562A40A54600AF23C5 /* DWConfirmInvitationContentView.xib */; }; + C943B5102A40A54600AF23C5 /* DWSendInviteFirstStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4572A40A54600AF23C5 /* DWSendInviteFirstStepViewController.m */; }; + C943B5112A40A54600AF23C5 /* DWInvitationHistoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4592A40A54600AF23C5 /* DWInvitationHistoryViewController.m */; }; + C943B5122A40A54600AF23C5 /* DWInvitationHistoryModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B45C2A40A54600AF23C5 /* DWInvitationHistoryModel.m */; }; + C943B5132A40A54600AF23C5 /* DWHistoryFilterContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4602A40A54600AF23C5 /* DWHistoryFilterContentView.m */; }; + C943B5142A40A54600AF23C5 /* DWHistoryFilterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4612A40A54600AF23C5 /* DWHistoryFilterViewController.m */; }; + C943B5152A40A54600AF23C5 /* DWInvitationTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4662A40A54600AF23C5 /* DWInvitationTableViewCell.m */; }; + C943B5162A40A54600AF23C5 /* DWCreateInvitationButton.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4672A40A54600AF23C5 /* DWCreateInvitationButton.m */; }; + C943B5172A40A54600AF23C5 /* DWHistoryHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4692A40A54600AF23C5 /* DWHistoryHeaderView.m */; }; + C943B5182A40A54600AF23C5 /* DWInvitationPreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B46E2A40A54600AF23C5 /* DWInvitationPreviewViewController.m */; }; + C943B5192A40A54600AF23C5 /* DWSendInviteFlowController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B46F2A40A54600AF23C5 /* DWSendInviteFlowController.m */; }; + C943B51A2A40A54600AF23C5 /* DWInvitationLinkBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4712A40A54600AF23C5 /* DWInvitationLinkBuilder.m */; }; + C943B51B2A40A54600AF23C5 /* BaseInvitationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C943B4722A40A54600AF23C5 /* BaseInvitationViewController.swift */; }; + C943B51C2A40A54600AF23C5 /* SuccessInvitationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C943B4732A40A54600AF23C5 /* SuccessInvitationViewController.swift */; }; + C943B51D2A40A54600AF23C5 /* InvitationBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C943B4762A40A54600AF23C5 /* InvitationBottomView.swift */; }; + C943B51E2A40A54600AF23C5 /* InvitationTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C943B4772A40A54600AF23C5 /* InvitationTopView.swift */; }; + C943B51F2A40A54600AF23C5 /* DWSuccessInvitationView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4782A40A54600AF23C5 /* DWSuccessInvitationView.m */; }; + C943B5202A40A54600AF23C5 /* DWInvitationActionsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B47A2A40A54600AF23C5 /* DWInvitationActionsView.m */; }; + C943B5212A40A54600AF23C5 /* SuccessInvitationTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C943B47C2A40A54600AF23C5 /* SuccessInvitationTopView.swift */; }; + C943B5222A40A54600AF23C5 /* DWInvitationMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B47D2A40A54600AF23C5 /* DWInvitationMessageView.m */; }; + C943B5232A40A54600AF23C5 /* DWNetworkUnavailableView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4802A40A54600AF23C5 /* DWNetworkUnavailableView.m */; }; + C943B5242A40A54600AF23C5 /* UIColor+DWDashPay.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4812A40A54600AF23C5 /* UIColor+DWDashPay.m */; }; + C943B5252A40A54600AF23C5 /* DWDPSmallContactView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4872A40A54600AF23C5 /* DWDPSmallContactView.m */; }; + C943B5262A40A54600AF23C5 /* DWDashPayAnimationView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4882A40A54600AF23C5 /* DWDashPayAnimationView.m */; }; + C943B5282A40A54600AF23C5 /* DWNetworkErrorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B48B2A40A54600AF23C5 /* DWNetworkErrorViewController.m */; }; + C943B5292A40A54600AF23C5 /* DWDashPayConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B48D2A40A54600AF23C5 /* DWDashPayConstants.m */; }; + C943B52A2A40A54600AF23C5 /* UIImageView+DWDPAvatar.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B48F2A40A54600AF23C5 /* UIImageView+DWDPAvatar.m */; }; + C943B52B2A40A54600AF23C5 /* DWDashPayContactsActions.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4912A40A54600AF23C5 /* DWDashPayContactsActions.m */; }; + C943B52C2A40A54600AF23C5 /* DSBlockchainIdentity+DWDisplayName.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4932A40A54600AF23C5 /* DSBlockchainIdentity+DWDisplayName.m */; }; + C943B52D2A40A54600AF23C5 /* DWDashPayContactsUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4962A40A54600AF23C5 /* DWDashPayContactsUpdater.m */; }; + C943B52E2A40A54600AF23C5 /* DWNotificationsProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4982A40A54600AF23C5 /* DWNotificationsProvider.m */; }; + C943B52F2A40A54600AF23C5 /* DWNotificationsData.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4992A40A54600AF23C5 /* DWNotificationsData.m */; }; + C943B5302A40A54600AF23C5 /* DWNotificationsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B49C2A40A54600AF23C5 /* DWNotificationsFetchedDataSource.m */; }; + C943B5312A40A54600AF23C5 /* DWNoNotificationsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4A12A40A54600AF23C5 /* DWNoNotificationsCell.m */; }; + C943B5322A40A54600AF23C5 /* DWNotificationsInvitationCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4A22A40A54600AF23C5 /* DWNotificationsInvitationCell.m */; }; + C943B5332A40A54600AF23C5 /* DWNotificationsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4A72A40A54600AF23C5 /* DWNotificationsModel.m */; }; + C943B5342A40A54600AF23C5 /* DWListCollectionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4A92A40A54600AF23C5 /* DWListCollectionLayout.m */; }; + C943B5352A40A54600AF23C5 /* DWNotificationsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4AA2A40A54600AF23C5 /* DWNotificationsViewController.m */; }; + C943B5382A40A65B00AF23C5 /* DWScrollingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5362A40A65B00AF23C5 /* DWScrollingViewController.m */; }; + C943B53E2A40A6BE00AF23C5 /* DPAlertViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B53B2A40A6BE00AF23C5 /* DPAlertViewController.m */; }; + C943B53F2A40A6BE00AF23C5 /* DPAlertChildContentsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B53C2A40A6BE00AF23C5 /* DPAlertChildContentsView.m */; }; + C943B5432A40AFD100AF23C5 /* DWTxDetailPopupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5422A40AFD100AF23C5 /* DWTxDetailPopupViewController.m */; }; + C943B54A2A40B52F00AF23C5 /* NSLayoutConstraint+DWAutolayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5462A40B52F00AF23C5 /* NSLayoutConstraint+DWAutolayout.m */; }; + C943B54B2A40B52F00AF23C5 /* UIView+DWAutolayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5482A40B52F00AF23C5 /* UIView+DWAutolayout.m */; }; + C943B54E2A40B6B500AF23C5 /* DWColoredButton.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B54C2A40B6B500AF23C5 /* DWColoredButton.m */; }; + C943B5542A40C23500AF23C5 /* DWFilterHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C943B5522A40C23500AF23C5 /* DWFilterHeaderView.xib */; }; + C943B5582A40DA3700AF23C5 /* DWFullScreenModalControllerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5572A40DA3700AF23C5 /* DWFullScreenModalControllerViewController.m */; }; + C943B55B2A40DD4000AF23C5 /* NSArray+DWFlatten.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B55A2A40DD4000AF23C5 /* NSArray+DWFlatten.m */; }; + C943B55E2A40E6F200AF23C5 /* DWFilterHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B55C2A40E6F200AF23C5 /* DWFilterHeaderView.m */; }; 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 */; }; @@ -711,12 +809,10 @@ C9D2C6A32A320AA000D15901 /* ApiCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B8449328F27C460082770C /* ApiCode.swift */; }; C9D2C6A42A320AA000D15901 /* CoinbaseEntryPointModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B91290701470003E8AB /* CoinbaseEntryPointModel.swift */; }; C9D2C6A52A320AA000D15901 /* SyncingAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451EA2A0BF10B00825057 /* SyncingAlertViewController.swift */; }; - C9D2C6A62A320AA000D15901 /* DWDashPayConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EEFF2419310C002C32F3 /* DWDashPayConstants.m */; }; C9D2C6A72A320AA000D15901 /* CrowdNodeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B8449528F5B9F80082770C /* CrowdNodeResponse.swift */; }; C9D2C6A82A320AA000D15901 /* FileManager+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BB128BFF61A00490F5E /* FileManager+DashWallet.swift */; }; C9D2C6A92A320AA000D15901 /* PointOfUseItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BCB28C1305E00490F5E /* PointOfUseItemCell.swift */; }; C9D2C6AA2A320AA000D15901 /* DSTransaction+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47083B3129892D770010AF71 /* DSTransaction+DashWallet.swift */; }; - C9D2C6AB2A320AA000D15901 /* DWConfirmUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B66323CF09000016F221 /* DWConfirmUsernameViewController.m */; }; C9D2C6AC2A320AA000D15901 /* PayableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42F9E29DA82E5001BC549 /* PayableViewController.swift */; }; C9D2C6AD2A320AA000D15901 /* HairlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1428C6378E00490F5E /* HairlineView.swift */; }; C9D2C6AE2A320AA000D15901 /* DWInitialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E9423A3F75F006A2A59 /* DWInitialViewController.m */; }; @@ -724,13 +820,10 @@ C9D2C6B02A320AA000D15901 /* DWSettingsMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BD52348CB6600451078 /* DWSettingsMenuViewController.m */; }; C9D2C6B12A320AA000D15901 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1D28C7491C00490F5E /* AboutViewController.swift */; }; C9D2C6B22A320AA000D15901 /* UISpringTimingParameters+DWInit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69D92314727F001B8C90 /* UISpringTimingParameters+DWInit.m */; }; - C9D2C6B32A320AA000D15901 /* DWDPNewIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9D72AB249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.m */; }; C9D2C6B42A320AA000D15901 /* DWToolsMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BDC2348DC0A00451078 /* DWToolsMenuModel.m */; }; - C9D2C6B52A320AA000D15901 /* DWNotificationsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3CA202B247E4AF300158074 /* DWNotificationsViewController.m */; }; C9D2C6B62A320AA000D15901 /* NumberKeyboardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661AC28F972BD00028A8D /* NumberKeyboardButton.swift */; }; C9D2C6B72A320AA000D15901 /* IsDefaultEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11AE3DD72997C599000856EE /* IsDefaultEmail.swift */; }; C9D2C6B82A320AA000D15901 /* DWHomeViewController+DWSecureWalletDelegateImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7DC223266C8400BA8C6A /* DWHomeViewController+DWSecureWalletDelegateImpl.m */; }; - C9D2C6B92A320AA000D15901 /* DWNotificationsProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CB42493D87D00F4A689 /* DWNotificationsProvider.m */; }; C9D2C6BA2A320AA000D15901 /* DWUpholdAccountObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFF292233E60F00956D5F /* DWUpholdAccountObject.m */; }; C9D2C6BB2A320AA000D15901 /* DWFormTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE2E2230FF4600956D5F /* DWFormTableViewController.m */; }; C9D2C6BC2A320AA000D15901 /* WalletKeysOverviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909614C29EFF7D600002D82 /* WalletKeysOverviewModel.swift */; }; @@ -760,7 +853,6 @@ C9D2C6D42A320AA000D15901 /* DWRecoverTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFFA2305464C00C475EB /* DWRecoverTextView.m */; }; C9D2C6D52A320AA000D15901 /* TxUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709C314287EA11900B4BD48 /* TxUserInfo.swift */; }; C9D2C6D62A320AA000D15901 /* ExtendedPublicKeysViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91E91AD29FFC8A1003E7883 /* ExtendedPublicKeysViewController.swift */; }; - C9D2C6D72A320AA000D15901 /* DWContactsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3172480E35A001D74F9 /* DWContactsFetchedDataSource.m */; }; C9D2C6D82A320AA000D15901 /* AtmListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BDD28C1305E00490F5E /* AtmListViewController.swift */; }; C9D2C6D92A320AA000D15901 /* TxDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A5145F2848F75B005A8E3E /* TxDetailViewController.swift */; }; C9D2C6DA2A320AA000D15901 /* NSAttributedString+Builder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13E9299E5396006903F1 /* NSAttributedString+Builder.swift */; }; @@ -768,10 +860,8 @@ C9D2C6DC2A320AA000D15901 /* DWPhoneWCSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A0C237EE89B0097A7B5 /* DWPhoneWCSessionManager.m */; }; C9D2C6DD2A320AA000D15901 /* NumberKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661AA28F9707A00028A8D /* NumberKeyboard.swift */; }; C9D2C6DE2A320AA000D15901 /* DWSeedPhraseTitledView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431E522D73617009BAF7F /* DWSeedPhraseTitledView.m */; }; - C9D2C6DF2A320AA000D15901 /* UICollectionView+DWDPItemDequeue.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF38824829AF2001D74F9 /* UICollectionView+DWDPItemDequeue.m */; }; C9D2C6E02A320AA000D15901 /* CrowdNodeTopUpTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D0B2907C61400D406C1 /* CrowdNodeTopUpTx.swift */; }; C9D2C6E12A320AA000D15901 /* BackupInfoItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB329DD86FB001BC549 /* BackupInfoItemView.swift */; }; - C9D2C6E22A320AA000D15901 /* DWContactsSearchInfoHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2D42476886D0001624F /* DWContactsSearchInfoHeaderView.m */; }; C9D2C6E32A320AA000D15901 /* AccountCreatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118A1392291AA21B002641E4 /* AccountCreatingController.swift */; }; C9D2C6E42A320AA000D15901 /* UIView+DWReuseHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E534322F02BC300E5168A /* UIView+DWReuseHelper.m */; }; C9D2C6E52A320AA000D15901 /* DWExtendedContainerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E9B23A53B8E006A2A59 /* DWExtendedContainerViewController.m */; }; @@ -779,19 +869,15 @@ C9D2C6E72A320AA000D15901 /* ServiceEntryPointModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F71317428F4365C0072F454 /* ServiceEntryPointModel.swift */; }; C9D2C6E82A320AA000D15901 /* ExploreDash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8B9428BFACA100490F5E /* ExploreDash.swift */; }; C9D2C6E92A320AA000D15901 /* DWToolsMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BDF2348DC1900451078 /* DWToolsMenuViewController.m */; }; - C9D2C6EA2A320AA000D15901 /* DWUsernamePendingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B66A23CF0F390016F221 /* DWUsernamePendingViewController.m */; }; C9D2C6EB2A320AA000D15901 /* UpholdAmountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4789D22E2981067600BAFEFA /* UpholdAmountModel.swift */; }; C9D2C6EC2A320AA000D15901 /* DWModalInteractiveTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69BC23140779001B8C90 /* DWModalInteractiveTransition.m */; }; - C9D2C6ED2A320AA000D15901 /* DWRequestsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5622487E16400B52F14 /* DWRequestsViewController.m */; }; C9D2C6EE2A320AA000D15901 /* DWUpholdMainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE712230FF4600956D5F /* DWUpholdMainViewController.m */; }; - C9D2C6EF2A320AA000D15901 /* DWDPUserObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36224825A0C001D74F9 /* DWDPUserObject.m */; }; C9D2C6F02A320AA000D15901 /* UIImage+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = BAA4843B1B3EFFAF0075C749 /* UIImage+Utils.m */; }; C9D2C6F12A320AA000D15901 /* CNCreateAccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47083B3529893E700010AF71 /* CNCreateAccountCell.swift */; }; C9D2C6F22A320AA000D15901 /* DWSetupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4430ED22CBBAC9009BAF7F /* DWSetupViewController.m */; }; C9D2C6F32A320AA000D15901 /* ExploreDatabaseConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BA928BFAE5800490F5E /* ExploreDatabaseConnection.swift */; }; C9D2C6F42A320AA000D15901 /* WithdrawalLimitsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C4B296C51B500788E18 /* WithdrawalLimitsController.swift */; }; C9D2C6F52A320AA000D15901 /* CoinbaseTransactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA928C896BC000427E7 /* CoinbaseTransactionResponse.swift */; }; - C9D2C6F62A320AA000D15901 /* DWIncomingFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3142480DA51001D74F9 /* DWIncomingFetchedDataSource.m */; }; C9D2C6F72A320AA000D15901 /* TxDetailModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709C31A288020DE00B4BD48 /* TxDetailModel.swift */; }; C9D2C6F82A320AA000D15901 /* NewAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BD737D28E6BD7400A34022 /* NewAccountViewController.swift */; }; C9D2C6F92A320AA000D15901 /* PointOfUseListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD728C1305E00490F5E /* PointOfUseListModel.swift */; }; @@ -817,7 +903,6 @@ C9D2C70D2A320AA000D15901 /* CBAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF46A429654E190067B6EE /* CBAccount.swift */; }; C9D2C70E2A320AA000D15901 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AF180429070B720025803E /* Types.swift */; }; C9D2C70F2A320AA000D15901 /* DWConfirmPaymentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69EA2316B344001B8C90 /* DWConfirmPaymentViewController.m */; }; - C9D2C7102A320AA000D15901 /* DWDPGenericItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF33F2481A1B2001D74F9 /* DWDPGenericItemView.m */; }; C9D2C7112A320AA000D15901 /* Coinbase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4774DCDA28F3FA9C008CF87D /* Coinbase.swift */; }; C9D2C7122A320AA000D15901 /* SyncModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452072A11F28600825057 /* SyncModel.swift */; }; C9D2C7132A320AA000D15901 /* ReceiveContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FAA29DC1098001BC549 /* ReceiveContentView.swift */; }; @@ -826,8 +911,6 @@ C9D2C7162A320AA000D15901 /* CrowdNodeTransferModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193FF3529602835004EA8D7 /* CrowdNodeTransferModel.swift */; }; C9D2C7172A320AA000D15901 /* DWSetPinViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44312522CCC14F009BAF7F /* DWSetPinViewController.m */; }; C9D2C7182A320AA000D15901 /* ConfirmOrderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F4B6C6294842DF00AED4C9 /* ConfirmOrderController.swift */; }; - C9D2C7192A320AA000D15901 /* DWUsernameHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1AE78D23F4668B00179A6E /* DWUsernameHeaderView.m */; }; - C9D2C71A2A320AA000D15901 /* DWDashPayContactsActions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3982482E32E001D74F9 /* DWDashPayContactsActions.m */; }; C9D2C71B2A320AA000D15901 /* DWResetWalletInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A10EB432358D2CA00C38B61 /* DWResetWalletInfoViewController.m */; }; C9D2C71C2A320AA000D15901 /* DWLockScreenModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD46231232A286000C71557 /* DWLockScreenModel.m */; }; C9D2C71D2A320AA000D15901 /* DWVerifySeedPhraseContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE8922DC9C6F00C99324 /* DWVerifySeedPhraseContentView.m */; }; @@ -850,11 +933,8 @@ C9D2C72E2A320AA000D15901 /* DWRecoverModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFFD2305763F00C475EB /* DWRecoverModel.m */; }; C9D2C72F2A320AA000D15901 /* DWCenteredTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431C722D4D92A009BAF7F /* DWCenteredTableView.m */; }; C9D2C7302A320AA000D15901 /* DWOnboardingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E6E23A26663006A2A59 /* DWOnboardingViewController.m */; }; - C9D2C7312A320AA000D15901 /* DWUsernameValidationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DC923F6BD5200CDA1EE /* DWUsernameValidationView.m */; }; C9D2C7322A320AA000D15901 /* CrowdNode+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C51296D620D00788E18 /* CrowdNode+UserDefaults.swift */; }; - C9D2C7332A320AA000D15901 /* DWDPAcceptedRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF37124826CDF001D74F9 /* DWDPAcceptedRequestNotificationObject.m */; }; C9D2C7342A320AA000D15901 /* DWPinView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44313822CE2CB9009BAF7F /* DWPinView.m */; }; - C9D2C7352A320AA000D15901 /* DWUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DCD23F6C01A00CDA1EE /* DWUsernameValidationRule.m */; }; C9D2C7362A320AA000D15901 /* DWBaseReceiveModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3416423A8213C004E37A7 /* DWBaseReceiveModel.m */; }; C9D2C7372A320AA000D15901 /* DWSegmentSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F6411238D650200A9B505 /* DWSegmentSlider.m */; }; C9D2C7382A320AA000D15901 /* CoinbasePlaceBuyOrderRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA528C896BC000427E7 /* CoinbasePlaceBuyOrderRequest.swift */; }; @@ -869,7 +949,6 @@ C9D2C7412A320AA000D15901 /* DWUpholdCardObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE012230FF2B00956D5F /* DWUpholdCardObject.m */; }; C9D2C7422A320AA000D15901 /* DWIntrinsicCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD72122F9B571008C7BC9 /* DWIntrinsicCollectionView.m */; }; C9D2C7432A320AA000D15901 /* DWRootModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E7E23A30609006A2A59 /* DWRootModelStub.m */; }; - C9D2C7442A320AA000D15901 /* DWDPIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3652482666C001D74F9 /* DWDPIncomingRequestObject.m */; }; C9D2C7452A320AA000D15901 /* UIDevice+DashWallet.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD6ED22F48159008C7BC9 /* UIDevice+DashWallet.m */; }; C9D2C7462A320AA000D15901 /* CBAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF46AB2965B65B0067B6EE /* CBAccountManager.swift */; }; C9D2C7472A320AA000D15901 /* FailedOperationStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47522F512927CBEE00EE143E /* FailedOperationStatusViewController.swift */; }; @@ -883,11 +962,9 @@ C9D2C74F2A320AA000D15901 /* DWSeedPhraseRow.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE9322DD078600C99324 /* DWSeedPhraseRow.m */; }; C9D2C7502A320AA000D15901 /* ServiceOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F71317828F436ED0072F454 /* ServiceOverviewViewController.swift */; }; C9D2C7512A320AA000D15901 /* AccountListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CAC3296EFE9500F63AC4 /* AccountListModel.swift */; }; - C9D2C7522A320AA000D15901 /* DWDPSearchItemsFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF37F248280CE001D74F9 /* DWDPSearchItemsFactory.m */; }; C9D2C7532A320AA000D15901 /* DWRecoverWalletCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3DC86D239717C2004B3DBA /* DWRecoverWalletCommand.m */; }; C9D2C7542A320AA000D15901 /* DWSwitcherFormTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE322230FF4600956D5F /* DWSwitcherFormTableViewCell.m */; }; C9D2C7552A320AA000D15901 /* DWPaymentInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C699B23104588001B8C90 /* DWPaymentInput.m */; }; - C9D2C7562A320AA000D15901 /* DWBaseContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE9549C23D0C4F4003612B3 /* DWBaseContactsViewController.m */; }; C9D2C7572A320AA000D15901 /* OnlineAccountDetailsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114D16B529812730009A124C /* OnlineAccountDetailsController.swift */; }; C9D2C7582A320AA000D15901 /* UIApplication+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E82A0BDAE700825057 /* UIApplication+DashWallet.swift */; }; C9D2C7592A320AA000D15901 /* OnlineAccountInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110D1783298E68A8005BEB30 /* OnlineAccountInfoController.swift */; }; @@ -900,12 +977,9 @@ C9D2C7602A320AA000D15901 /* DWAppGroupOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E7C23034AC100FF8653 /* DWAppGroupOptions.m */; }; C9D2C7612A320AA000D15901 /* DWTransactionListDataItemObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913EA323A799F3006A2A59 /* DWTransactionListDataItemObject.m */; }; C9D2C7622A320AA000D15901 /* DWUpholdAPIProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE002230FF2B00956D5F /* DWUpholdAPIProvider.m */; }; - C9D2C7632A320AA000D15901 /* DWConfirmUsernameContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EEFA2417E30F002C32F3 /* DWConfirmUsernameContentView.m */; }; C9D2C7642A320AA000D15901 /* BasePageSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F2C67C28602D4F00C2B774 /* BasePageSheetViewController.swift */; }; - C9D2C7652A320AA000D15901 /* DWListCollectionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A827B7124B5CA1800A42042 /* DWListCollectionLayout.m */; }; C9D2C7662A320AA000D15901 /* CrowdNodeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D0329051F9900D406C1 /* CrowdNodeRequest.swift */; }; C9D2C7672A320AA000D15901 /* CoinbaseExchangeRateResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFAF28C896BC000427E7 /* CoinbaseExchangeRateResponse.swift */; }; - C9D2C7682A320AA000D15901 /* DWDPTxItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6688FC24BCB739008E10F0 /* DWDPTxItemView.m */; }; C9D2C7692A320AA000D15901 /* DWConfirmPaymentContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69EE2316B7F5001B8C90 /* DWConfirmPaymentContentView.m */; }; C9D2C76A2A320AA000D15901 /* CrowdNodePortalItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 117728A2297A7D24006F1553 /* CrowdNodePortalItem.swift */; }; C9D2C76B2A320AA000D15901 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661B528FE75A700028A8D /* BaseViewController.swift */; }; @@ -917,10 +991,7 @@ C9D2C7712A320AA000D15901 /* UIDevice+Compatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4730586D295AB3BD004641DA /* UIDevice+Compatibility.swift */; }; C9D2C7722A320AA000D15901 /* DWSeedWordView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431E022D7219E009BAF7F /* DWSeedWordView.m */; }; C9D2C7732A320AA000D15901 /* DWSecurityStatusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7F3B1E238C651200DEA3EF /* DWSecurityStatusView.m */; }; - C9D2C7742A320AA000D15901 /* DWRequestsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E55F2487E13F00B52F14 /* DWRequestsContentViewController.m */; }; C9D2C7752A320AA000D15901 /* DWAnimatedShapeLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8D0231D33EA00A96B62 /* DWAnimatedShapeLayer.m */; }; - C9D2C7762A320AA000D15901 /* DWUserProfileNavigationTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D702462D4AD001D7C0D /* DWUserProfileNavigationTitleView.m */; }; - C9D2C7772A320AA000D15901 /* DWTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9E7DC623F6928C00CDA1EE /* DWTextField.m */; }; C9D2C7782A320AA000D15901 /* DWScreenshotWarningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD267246AA6F10001624F /* DWScreenshotWarningViewController.m */; }; C9D2C7792A320AA000D15901 /* ConvertCryptoOrderPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CACC297021EE00F63AC4 /* ConvertCryptoOrderPreviewController.swift */; }; C9D2C77A2A320AA000D15901 /* TransactionDataItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4708119E2990F56F003FCA3D /* TransactionDataItem.swift */; }; @@ -928,8 +999,6 @@ C9D2C77C2A320AA000D15901 /* DWVerifySeedPhraseModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE8C22DCB3B600C99324 /* DWVerifySeedPhraseModel.m */; }; C9D2C77D2A320AA000D15901 /* ReceiveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FA529DC092B001BC549 /* ReceiveViewController.swift */; }; C9D2C77E2A320AA000D15901 /* ConfirmationTransactionQRController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118B7A3E29865A3A00FBB6CC /* ConfirmationTransactionQRController.swift */; }; - C9D2C77F2A320AA000D15901 /* DWDashPayAnimationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADB396824223D9B00A6F898 /* DWDashPayAnimationView.m */; }; - C9D2C7802A320AA000D15901 /* DWDPOutgoingRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CBD24940EC200F4A689 /* DWDPOutgoingRequestNotificationObject.m */; }; C9D2C7812A320AA000D15901 /* Numbers+Dash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E4B7B6292F85E800CE0EB6 /* Numbers+Dash.swift */; }; C9D2C7822A320AA000D15901 /* PaymentMethodsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478C983129433C5700FAA0F0 /* PaymentMethodsController.swift */; }; C9D2C7832A320AA000D15901 /* PointOfUseDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1228C5F0A600490F5E /* PointOfUseDetailsViewController.swift */; }; @@ -939,15 +1008,11 @@ C9D2C7872A320AA000D15901 /* DWTransactionListDataProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E535B22F335C200E5168A /* DWTransactionListDataProvider.m */; }; C9D2C7882A320AA000D15901 /* DWModalPopupTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7DA72323AF4300BA8C6A /* DWModalPopupTransition.m */; }; C9D2C7892A320AA000D15901 /* DWAdvancedSecurityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7F3B1A238C646600DEA3EF /* DWAdvancedSecurityViewController.m */; }; - C9D2C78A2A320AA000D15901 /* DWInputUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B64323CDC0F50016F221 /* DWInputUsernameViewController.m */; }; C9D2C78B2A320AA000D15901 /* OrderPreviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CAD2297022B300F63AC4 /* OrderPreviewModel.swift */; }; - C9D2C78C2A320AA000D15901 /* DWDPGenericImageItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34824823167001D74F9 /* DWDPGenericImageItemView.m */; }; C9D2C78D2A320AA000D15901 /* UIView+DWFindConstraints.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FD5244DFEF100B9F679 /* UIView+DWFindConstraints.m */; }; C9D2C78E2A320AA000D15901 /* DWModalPopupPresentationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7DA42323AC1F00BA8C6A /* DWModalPopupPresentationController.m */; }; C9D2C78F2A320AA000D15901 /* CrowdNodeBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11517C89294B11DD004FC7BF /* CrowdNodeBalance.swift */; }; C9D2C7902A320AA000D15901 /* String+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471DD1B9290A962B00E030C8 /* String+DashWallet.swift */; }; - C9D2C7912A320AA000D15901 /* DWBaseContactsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2C2247512BA0001624F /* DWBaseContactsContentViewController.m */; }; - C9D2C7922A320AA000D15901 /* DWUserProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE2F0F8245C16C8001DD722 /* DWUserProfileViewController.m */; }; C9D2C7932A320AA000D15901 /* DWBaseModalViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69D623143B2F001B8C90 /* DWBaseModalViewController.m */; }; C9D2C7942A320AA000D15901 /* DWSecurityMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BCF2348A34800451078 /* DWSecurityMenuModel.m */; }; C9D2C7952A320AA000D15901 /* KeysOverviewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909615829F29C9200002D82 /* KeysOverviewCell.swift */; }; @@ -956,7 +1021,6 @@ C9D2C7982A320AA000D15901 /* BaseResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B8B29068F110003E8AB /* BaseResponse.swift */; }; C9D2C7992A320AA000D15901 /* DWDemoAdvancedSecurityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB3417623A92978004E37A7 /* DWDemoAdvancedSecurityViewController.m */; }; C9D2C79A2A320AA000D15901 /* TxListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474C7219298A803200475CA6 /* TxListTableViewCell.swift */; }; - C9D2C79B2A320AA000D15901 /* DWStretchyHeaderListCollectionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D6F2462D4AD001D7C0D /* DWStretchyHeaderListCollectionLayout.m */; }; C9D2C79C2A320AA000D15901 /* TxWithinTimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C4F296D5A4700788E18 /* TxWithinTimePeriod.swift */; }; C9D2C79D2A320AA000D15901 /* CoinbaseSwapeTradeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDF9F28C896BC000427E7 /* CoinbaseSwapeTradeRequest.swift */; }; C9D2C79E2A320AA000D15901 /* UIFont+DWFont.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44311622CBF0B2009BAF7F /* UIFont+DWFont.m */; }; @@ -965,18 +1029,14 @@ C9D2C7A12A320AA000D15901 /* CoinbaseInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751136E28D9B50E00223B77 /* CoinbaseInfoViewController.swift */; }; C9D2C7A22A320AA000D15901 /* DWSelectorFormCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE2A2230FF4600956D5F /* DWSelectorFormCellModel.m */; }; C9D2C7A32A320AA000D15901 /* DWVersionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FB2E5536218BA161003A1B7C /* DWVersionManager.m */; }; - C9D2C7A42A320AA000D15901 /* DWDPEstablishedContactNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF377248270A4001D74F9 /* DWDPEstablishedContactNotificationObject.m */; }; C9D2C7A52A320AA000D15901 /* DWModalPresentationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69C523142AA4001B8C90 /* DWModalPresentationController.m */; }; C9D2C7A62A320AA000D15901 /* PointOfUseInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1028C5430300490F5E /* PointOfUseInfoViewController.swift */; }; C9D2C7A72A320AA000D15901 /* ActionButtonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661B328FDCF7800028A8D /* ActionButtonViewController.swift */; }; C9D2C7A82A320AA000D15901 /* PortalServiceItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4774DCE428F4668B008CF87D /* PortalServiceItemCell.swift */; }; - C9D2C7A92A320AA000D15901 /* DWDPImageStatusCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF35524824F97001D74F9 /* DWDPImageStatusCell.m */; }; C9D2C7AA2A320AA000D15901 /* WithdrawalConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111B8BFF299BD973004A4129 /* WithdrawalConfirmationController.swift */; }; C9D2C7AB2A320AA000D15901 /* DWBaseFormTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BC82347E0D700451078 /* DWBaseFormTableViewCell.m */; }; C9D2C7AC2A320AA000D15901 /* AccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CAC6296FAEBB00F63AC4 /* AccountCell.swift */; }; C9D2C7AD2A320AA000D15901 /* DWTransactionListDataProviderStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913EA723A79AD2006A2A59 /* DWTransactionListDataProviderStub.m */; }; - C9D2C7AE2A320AA000D15901 /* DWUserSearchModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FCF2449F37A00B9F679 /* DWUserSearchModel.m */; }; - C9D2C7AF2A320AA000D15901 /* DWDPBasicCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34F2482374D001D74F9 /* DWDPBasicCell.m */; }; C9D2C7B02A320AA000D15901 /* DWExploreHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BE128C1305E00490F5E /* DWExploreHeaderView.m */; }; C9D2C7B12A320AA000D15901 /* CoinbaseBaseIDForCurrencyResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA028C896BC000427E7 /* CoinbaseBaseIDForCurrencyResponse.swift */; }; C9D2C7B22A320AA000D15901 /* DWBaseActionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BE22348E85400451078 /* DWBaseActionButton.m */; }; @@ -985,7 +1045,6 @@ C9D2C7B52A320AA000D15901 /* DWBackupSeedPhraseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A36A88A2350A05B0014DC60 /* DWBackupSeedPhraseViewController.m */; }; C9D2C7B62A320AA000D15901 /* CrowdNodeDepositTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114CFECF296469D9005F421B /* CrowdNodeDepositTx.swift */; }; C9D2C7B72A320AA000D15901 /* DWUpholdViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE6A2230FF4600956D5F /* DWUpholdViewController.m */; }; - C9D2C7B82A320AA000D15901 /* DWNotificationsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF31B2480E6AD001D74F9 /* DWNotificationsModel.m */; }; C9D2C7B92A320AA000D15901 /* DWIntrinsicTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFF072233E56E00956D5F /* DWIntrinsicTableView.m */; }; C9D2C7BA2A320AA000D15901 /* DWURLActions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E6523A11DFE006A2A59 /* DWURLActions.m */; }; C9D2C7BB2A320AA000D15901 /* KeysOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9903A5329E6A5F600535A4E /* KeysOverviewViewController.swift */; }; @@ -1011,7 +1070,6 @@ C9D2C7CF2A320AA000D15901 /* MerchantListLocationOffCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD128C1305E00490F5E /* MerchantListLocationOffCell.swift */; }; C9D2C7D02A320AA000D15901 /* DWExploreTestnetContentsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BE228C1305E00490F5E /* DWExploreTestnetContentsView.m */; }; C9D2C7D12A320AA000D15901 /* DWMainMenuContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BB5234792A600451078 /* DWMainMenuContentView.m */; }; - C9D2C7D22A320AA000D15901 /* DWDPIncomingRequestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF35224823EC8001D74F9 /* DWDPIncomingRequestCell.m */; }; C9D2C7D32A320AA000D15901 /* CrowdNodeAPIConfirmationTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1186092429759C4B00279FCC /* CrowdNodeAPIConfirmationTx.swift */; }; C9D2C7D42A320AA000D15901 /* UIViewController+DWEmbedding.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8F23A31713006A2A59 /* UIViewController+DWEmbedding.m */; }; C9D2C7D52A320AA000D15901 /* DWShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A2CD71522F98FEA008C7BC9 /* DWShadowView.m */; }; @@ -1024,9 +1082,7 @@ C9D2C7DC2A320AA000D15901 /* DWHomeModelStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E8123A30623006A2A59 /* DWHomeModelStub.m */; }; C9D2C7DD2A320AA000D15901 /* UISearchBar+DWAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D1B24603C4F001D7C0D /* UISearchBar+DWAdditions.m */; }; C9D2C7DE2A320AA000D15901 /* UIViewController+DWTxFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74F0032305C59700C475EB /* UIViewController+DWTxFilter.m */; }; - C9D2C7DF2A320AA000D15901 /* DWProfileTxsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3AC24BE117300DB32DE /* DWProfileTxsFetchedDataSource.m */; }; C9D2C7E02A320AA000D15901 /* TransactionFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 117ED4A728ED66F9006E3EE4 /* TransactionFilter.swift */; }; - C9D2C7E12A320AA000D15901 /* DWSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2BF24747F580001624F /* DWSearchViewController.m */; }; C9D2C7E22A320AA000D15901 /* CrowdNodeWithdrawalReceivedTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C53296D6A2D00788E18 /* CrowdNodeWithdrawalReceivedTx.swift */; }; C9D2C7E32A320AA000D15901 /* PointOfUseListSegmentedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C0228C1F0C600490F5E /* PointOfUseListSegmentedCell.swift */; }; C9D2C7E42A320AA000D15901 /* DWURLRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913E6723A14739006A2A59 /* DWURLRequestHandler.m */; }; @@ -1037,18 +1093,14 @@ C9D2C7E92A320AA000D15901 /* AtmDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BA428BFADD900490F5E /* AtmDAO.swift */; }; C9D2C7EA2A320AA000D15901 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 475AE2B82974348F009A1055 /* App.swift */; }; C9D2C7EB2A320AA000D15901 /* DWPaymentProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69A2231048DA001B8C90 /* DWPaymentProcessor.m */; }; - C9D2C7EC2A320AA000D15901 /* DWNotificationsData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF32724814A17001D74F9 /* DWNotificationsData.m */; }; C9D2C7ED2A320AA000D15901 /* SyncingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451F22A0C933700825057 /* SyncingHeaderView.swift */; }; C9D2C7EE2A320AA000D15901 /* MinimumDepositBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114CFED1296489CD005F421B /* MinimumDepositBanner.swift */; }; C9D2C7EF2A320AA000D15901 /* BalanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B8E2906D7340003E8AB /* BalanceView.swift */; }; C9D2C7F02A320AA000D15901 /* DWQRScanStatusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD85A9E245881740045B480 /* DWQRScanStatusView.m */; }; C9D2C7F12A320AA000D15901 /* DWURLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A741DC52363A06000840ADF /* DWURLParser.m */; }; - C9D2C7F22A320AA000D15901 /* DWDPContactObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36E24826737001D74F9 /* DWDPContactObject.m */; }; C9D2C7F32A320AA000D15901 /* DWPaymentInputBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E6B23029D2B00FF8653 /* DWPaymentInputBuilder.m */; }; C9D2C7F42A320AA000D15901 /* DWQRScanViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC92C831FEB0A6D008CAEE0 /* DWQRScanViewController.m */; }; C9D2C7F52A320AA000D15901 /* DWSelectorFormTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE2C2230FF4600956D5F /* DWSelectorFormTableViewCell.m */; }; - C9D2C7F62A320AA000D15901 /* DWRegistrationCompletedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3CCEF8242BB1B900300AF8 /* DWRegistrationCompletedViewController.m */; }; - C9D2C7F72A320AA000D15901 /* DWUserProfileHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D6E2462D4AD001D7C0D /* DWUserProfileHeaderView.m */; }; C9D2C7F82A320AA000D15901 /* UIViewController+AlertPresenting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47305871295C971F004641DA /* UIViewController+AlertPresenting.swift */; }; C9D2C7F92A320AA000D15901 /* DSWatchTransactionDataObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A858A06237EE89B0097A7B5 /* DSWatchTransactionDataObject.m */; }; C9D2C7FA2A320AA000D15901 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA3B0129364991008D58DC /* HTTPClient.swift */; }; @@ -1058,43 +1110,33 @@ C9D2C7FE2A320AA000D15901 /* CBUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2A2EB293E618600938DB7 /* CBUser.swift */; }; C9D2C7FF2A320AA000D15901 /* CBSecureTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2A2ED293E622700938DB7 /* CBSecureTokenService.swift */; }; C9D2C8002A320AA000D15901 /* PayTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FA029DA95F5001BC549 /* PayTableViewCell.swift */; }; - C9D2C8012A320AA000D15901 /* DWTitleActionHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2C5247538A90001624F /* DWTitleActionHeaderView.m */; }; - C9D2C8022A320AA000D15901 /* DWDPTxObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3B424BF280A00DB32DE /* DWDPTxObject.m */; }; C9D2C8032A320AA000D15901 /* ServiceOverviewTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F71317628F436920072F454 /* ServiceOverviewTableCell.swift */; }; - C9D2C8042A320AA000D15901 /* DWContactsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5522487D50200B52F14 /* DWContactsModel.m */; }; C9D2C8052A320AA000D15901 /* CoinbaseEntryPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B86290670630003E8AB /* CoinbaseEntryPointViewController.swift */; }; C9D2C8062A320AA000D15901 /* NSString+DWTextSize.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE7F22DC92BF00C99324 /* NSString+DWTextSize.m */; }; C9D2C8072A320AA000D15901 /* PointOfUseListFiltersCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C0428C1F74A00490F5E /* PointOfUseListFiltersCell.swift */; }; C9D2C8082A320AA000D15901 /* TransactionListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47081198298CF57D003FCA3D /* TransactionListDataSource.swift */; }; - C9D2C8092A320AA000D15901 /* DWAllowedCharactersUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD5942451DCAF00688A8D /* DWAllowedCharactersUsernameValidationRule.m */; }; C9D2C80A2A320AA000D15901 /* DWMainMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BB12347927700451078 /* DWMainMenuViewController.m */; }; C9D2C80B2A320AA000D15901 /* PointOfUseListFiltersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B7C290133610003E8AB /* PointOfUseListFiltersViewController.swift */; }; C9D2C80C2A320AA000D15901 /* AllMerchantLocationsDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1928C6A21A00490F5E /* AllMerchantLocationsDataProvider.swift */; }; C9D2C80D2A320AA000D15901 /* MainTabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F451E42A0B986E00825057 /* MainTabbarController.swift */; }; C9D2C80E2A320AA000D15901 /* NumberFormatter+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B30D77290BFCA60080C326 /* NumberFormatter+DashWallet.swift */; }; - C9D2C80F2A320AA000D15901 /* DWNotificationsFetchedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CBA2494045C00F4A689 /* DWNotificationsFetchedDataSource.m */; }; C9D2C8102A320AA000D15901 /* BaseAmountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AF18072907B7880025803E /* BaseAmountModel.swift */; }; - C9D2C8112A320AA000D15901 /* DWDPNewIncomingRequestNotificationObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3742482703C001D74F9 /* DWDPNewIncomingRequestNotificationObject.m */; }; C9D2C8122A320AA000D15901 /* SeedDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709C311287E78BD00B4BD48 /* SeedDB.swift */; }; C9D2C8132A320AA000D15901 /* DWPinField.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8EF231ECDE000A96B62 /* DWPinField.m */; }; C9D2C8142A320AA000D15901 /* DWCenteredScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CEA122DFC80900C99324 /* DWCenteredScrollView.m */; }; - C9D2C8152A320AA000D15901 /* DWDPEstablishedContactObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36B248266FB001D74F9 /* DWDPEstablishedContactObject.m */; }; C9D2C8162A320AA000D15901 /* DWPhraseRepairViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9172C325233DC50024B4C5 /* DWPhraseRepairViewController.m */; }; C9D2C8172A320AA000D15901 /* DWBiometricAuthModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44314B22CF8801009BAF7F /* DWBiometricAuthModel.m */; }; C9D2C8182A320AA000D15901 /* DWNumberKeyboardInputViewAudioFeedback.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8EB231ECCF000A96B62 /* DWNumberKeyboardInputViewAudioFeedback.m */; }; C9D2C8192A320AA000D15901 /* CrowdNode+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E47BAC28EB3A7D0097CFA0 /* CrowdNode+Constants.swift */; }; C9D2C81A2A320AA000D15901 /* CurrencyExchanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F006012971B1FE0029EB10 /* CurrencyExchanger.swift */; }; - C9D2C81B2A320AA000D15901 /* DWMinLengthUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD5912451D68300688A8D /* DWMinLengthUsernameValidationRule.m */; }; C9D2C81C2A320AA000D15901 /* DemoMainTabbarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94946E02A25F037008A678D /* DemoMainTabbarViewController.swift */; }; C9D2C81D2A320AA000D15901 /* MessageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11AE3DD52997AA36000856EE /* MessageStatus.swift */; }; C9D2C81E2A320AA000D15901 /* DWGlobalOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CEAA22E18F1800C99324 /* DWGlobalOptions.m */; }; C9D2C81F2A320AA000D15901 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4430E622CBB6EC009BAF7F /* AppDelegate.m */; }; C9D2C8202A320AA000D15901 /* SendReceivePageController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C917023E29D44E0B008C034D /* SendReceivePageController.swift */; }; C9D2C8212A320AA000D15901 /* DWSettingsMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BD82348CB7300451078 /* DWSettingsMenuModel.m */; }; - C9D2C8222A320AA000D15901 /* DWUserProfileSendRequestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A80F3D824DC55CC003E3B1E /* DWUserProfileSendRequestCell.m */; }; C9D2C8232A320AA000D15901 /* BaseNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478A2C6E28DC457000AD1420 /* BaseNavigationController.swift */; }; C9D2C8242A320AA000D15901 /* UIViewController+Coinbase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CDEECB294A2BAD008AE06D /* UIViewController+Coinbase.swift */; }; - C9D2C8252A320AA000D15901 /* DWCheckExistenceUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD59D2451F39500688A8D /* DWCheckExistenceUsernameValidationRule.m */; }; C9D2C8262A320AA000D15901 /* CSVBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13E0299E1F2F006903F1 /* CSVBuilder.swift */; }; C9D2C8272A320AA000D15901 /* DWDPRegistrationStatusTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5E4546243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.m */; }; C9D2C8282A320AA000D15901 /* CoinbaseUserAuthInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDF9D28C896BC000427E7 /* CoinbaseUserAuthInformation.swift */; }; @@ -1119,13 +1161,11 @@ C9D2C83B2A320AA000D15901 /* TxUserInfoDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709C317287EA23000B4BD48 /* TxUserInfoDAO.swift */; }; C9D2C83C2A320AA000D15901 /* DWModalContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69E223155A0E001B8C90 /* DWModalContentView.m */; }; C9D2C83D2A320AA000D15901 /* DWUpholdConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDFF2230FF2B00956D5F /* DWUpholdConstants.m */; }; - C9D2C83E2A320AA000D15901 /* DWMaxLengthUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BD5972451DD0700688A8D /* DWMaxLengthUsernameValidationRule.m */; }; C9D2C83F2A320AA000D15901 /* CoinbaseSwapeTradeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA128C896BC000427E7 /* CoinbaseSwapeTradeResponse.swift */; }; C9D2C8402A320AA000D15901 /* DWQRScanView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC92C861FEB0AE8008CAEE0 /* DWQRScanView.m */; }; C9D2C8412A320AA000D15901 /* ServiceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4774DCDE28F43AB4008CF87D /* ServiceItem.swift */; }; C9D2C8422A320AA000D15901 /* ErrorPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EE171629560DC200BA1986 /* ErrorPresentable.swift */; }; C9D2C8432A320AA000D15901 /* DWPinInputStepView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCD8E1231E507900A96B62 /* DWPinInputStepView.m */; }; - C9D2C8442A320AA000D15901 /* DWDashPaySetupFlowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AC52AD5241BB5FC00D9A829 /* DWDashPaySetupFlowController.m */; }; C9D2C8452A320AA000D15901 /* DWRecoverViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFEC2305318000C475EB /* DWRecoverViewController.m */; }; C9D2C8462A320AA000D15901 /* DWDPAmountContactView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A919F9E24A65CE00018C9A3 /* DWDPAmountContactView.m */; }; C9D2C8472A320AA000D15901 /* DWBaseTransactionListDataProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913EAD23A7AC86006A2A59 /* DWBaseTransactionListDataProvider.m */; }; @@ -1135,14 +1175,12 @@ C9D2C84B2A320AA000D15901 /* UITableView+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F005FE297164600029EB10 /* UITableView+DashWallet.swift */; }; C9D2C84C2A320AA000D15901 /* TransferAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C661B128FDC72700028A8D /* TransferAmountViewController.swift */; }; C9D2C84D2A320AA000D15901 /* ShortcutCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8D29D404850034FD57 /* ShortcutCell.swift */; }; - C9D2C84E2A320AA000D15901 /* DWDPGenericContactRequestItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34324822AE0001D74F9 /* DWDPGenericContactRequestItemView.m */; }; C9D2C84F2A320AA000D15901 /* UIColor+DWStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A44312022CCA2A0009BAF7F /* UIColor+DWStyle.m */; }; C9D2C8502A320AA000D15901 /* DWEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = FBEF3AF021823CD800917AB6 /* DWEnvironment.m */; }; C9D2C8512A320AA000D15901 /* AtmItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BC928C1305E00490F5E /* AtmItemCell.swift */; }; C9D2C8522A320AA000D15901 /* CoinbaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFB628C896BC000427E7 /* CoinbaseService.swift */; }; C9D2C8532A320AA000D15901 /* DWControllerCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E5522FEDF2900FF8653 /* DWControllerCollectionView.m */; }; C9D2C8542A320AA000D15901 /* SFSafariViewController+DashWallet.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDF22230FF1A00956D5F /* SFSafariViewController+DashWallet.m */; }; - C9D2C8552A320AA000D15901 /* DWContactsDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E54C2487CE0100B52F14 /* DWContactsDataSourceObject.m */; }; C9D2C8562A320AA000D15901 /* TransactionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474C7217298A422400475CA6 /* TransactionItemView.swift */; }; C9D2C8572A320AA000D15901 /* UIView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474C7210298A1A9500475CA6 /* UIView+Reuse.swift */; }; C9D2C8582A320AA000D15901 /* DWUpholdClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE022230FF2B00956D5F /* DWUpholdClient.m */; }; @@ -1153,20 +1191,14 @@ C9D2C85D2A320AA000D15901 /* DWLockPinInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6300442328D07500827825 /* DWLockPinInputView.m */; }; C9D2C85E2A320AA000D15901 /* UIView+DWHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69AB23125074001B8C90 /* UIView+DWHUD.m */; }; C9D2C85F2A320AA000D15901 /* DWPayOptionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E6722FFE4CC00FF8653 /* DWPayOptionModel.m */; }; - C9D2C8602A320AA000D15901 /* DWDPRespondedIncomingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF36824826681001D74F9 /* DWDPRespondedIncomingRequestObject.m */; }; C9D2C8612A320AA000D15901 /* CoinbaseTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFA328C896BC000427E7 /* CoinbaseTokenResponse.swift */; }; C9D2C8622A320AA000D15901 /* BackupInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB129DD5141001BC549 /* BackupInfoViewController.swift */; }; - C9D2C8632A320AA000D15901 /* DWPlanetarySystemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1AE79123F468CD00179A6E /* DWPlanetarySystemView.m */; }; C9D2C8642A320AA000D15901 /* AppliedFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C6E6E4291A68B6003FEDF2 /* AppliedFiltersView.swift */; }; C9D2C8652A320AA000D15901 /* DWCaptureSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BAD234770C900451078 /* DWCaptureSessionManager.m */; }; C9D2C8662A320AA000D15901 /* DWDataMigrationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A11F59E2194BD6200E7B563 /* DWDataMigrationManager.m */; }; - C9D2C8672A320AA000D15901 /* DWRequestsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5692487E1DB00B52F14 /* DWRequestsModel.m */; }; - C9D2C8682A320AA000D15901 /* DWDPTextStatusCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF35824824FEB001D74F9 /* DWDPTextStatusCell.m */; }; - C9D2C8692A320AA000D15901 /* DWContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5592487D9AF00B52F14 /* DWContactsViewController.m */; }; C9D2C86A2A320AA000D15901 /* OutlinedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EA863929952E9800ABD7B4 /* OutlinedTextField.swift */; }; C9D2C86B2A320AA000D15901 /* AmountPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13E5299E3C81006903F1 /* AmountPreviewView.swift */; }; C9D2C86C2A320AA000D15901 /* ModalNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4759D513292FEFFB002F20DC /* ModalNavigationController.swift */; }; - C9D2C86D2A320AA000D15901 /* DWDPSmallContactView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A919F9B24A4DCAD0018C9A3 /* DWDPSmallContactView.m */; }; C9D2C86E2A320AA000D15901 /* TransferAmountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A50F3A2913BC0900C70123 /* TransferAmountModel.swift */; }; C9D2C86F2A320AA000D15901 /* NavigationBarAppearanceCustomizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4730586F295AD62B004641DA /* NavigationBarAppearanceCustomizable.swift */; }; C9D2C8702A320AA000D15901 /* AmountObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471DD1B5290A901200E030C8 /* AmountObject.swift */; }; @@ -1176,8 +1208,6 @@ C9D2C8742A320AA000D15901 /* DWLocalCurrencyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7C1C234B771400451078 /* DWLocalCurrencyModel.m */; }; C9D2C8752A320AA000D15901 /* DWMainMenuModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BC2234797FC00451078 /* DWMainMenuModel.m */; }; C9D2C8762A320AA000D15901 /* DWUpholdMainnetConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = FB8ACEB522E0502100EE5035 /* DWUpholdMainnetConstants.m */; }; - C9D2C8772A320AA000D15901 /* DWUserSearchResultViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FCB2449F08700B9F679 /* DWUserSearchResultViewController.m */; }; - C9D2C8782A320AA000D15901 /* DWDPGenericStatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF34B24823315001D74F9 /* DWDPGenericStatusItemView.m */; }; C9D2C8792A320AA000D15901 /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FA829DC09CF001BC549 /* Style.swift */; }; C9D2C87A2A320AA000D15901 /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C917024029D462C6008C034D /* PayViewController.swift */; }; C9D2C87B2A320AA000D15901 /* DatabaseConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 479E7923287C00EC00D0F7D7 /* DatabaseConnection.swift */; }; @@ -1185,7 +1215,6 @@ C9D2C87D2A320AA000D15901 /* DWIntrinsicTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A74EFF723053ECE00C475EB /* DWIntrinsicTextView.m */; }; C9D2C87E2A320AA000D15901 /* DWExploreTestnetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BE428C1305E00490F5E /* DWExploreTestnetViewController.m */; }; C9D2C87F2A320AA000D15901 /* AddressUserInfoDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471A2607289ACDA00056B7B2 /* AddressUserInfoDAO.swift */; }; - C9D2C8802A320AA000D15901 /* DWDashPayContactsUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AEC5CC1249755BB00F4A689 /* DWDashPayContactsUpdater.m */; }; C9D2C8812A320AA000D15901 /* DWModalTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69D223143568001B8C90 /* DWModalTransition.m */; }; C9D2C8822A320AA000D15901 /* DWUpholdMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE6D2230FF4600956D5F /* DWUpholdMainModel.m */; }; C9D2C8832A320AA000D15901 /* MerchantsDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD628C1305E00490F5E /* MerchantsDataProvider.swift */; }; @@ -1196,18 +1225,14 @@ C9D2C8882A320AA000D15901 /* ExploreMapAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BDA28C1305E00490F5E /* ExploreMapAnnotationView.swift */; }; C9D2C8892A320AA000D15901 /* DWHomeViewController+DWBackupReminder.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B7DBF232669FF00BA8C6A /* DWHomeViewController+DWBackupReminder.m */; }; C9D2C88A2A320AA000D15901 /* DWHomeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E533722F023AB00E5168A /* DWHomeModel.m */; }; - C9D2C88B2A320AA000D15901 /* DWUserProfileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D7524640A2B001D7C0D /* DWUserProfileModel.m */; }; C9D2C88C2A320AA000D15901 /* DWHomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBB822E1FA1000A50237 /* DWHomeViewController.m */; }; C9D2C88D2A320AA000D15901 /* IsAddressInUse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1186092029758B2F00279FCC /* IsAddressInUse.swift */; }; - C9D2C88E2A320AA000D15901 /* DWFetchedResultsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD26E246D46BF0001624F /* DWFetchedResultsDataSource.m */; }; C9D2C88F2A320AA000D15901 /* DWSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E3F22FD75C000FF8653 /* DWSegmentedControl.m */; }; C9D2C8902A320AA000D15901 /* MerchantItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BCC28C1305E00490F5E /* MerchantItemCell.swift */; }; C9D2C8912A320AA000D15901 /* PaymentMethodCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478C98362943A60000FAA0F0 /* PaymentMethodCell.swift */; }; - C9D2C8922A320AA000D15901 /* DWContactsContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6E5562487D8C000B52F14 /* DWContactsContentViewController.m */; }; C9D2C8932A320AA000D15901 /* CBUserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EEE23A293F041E00049E0B /* CBUserManager.swift */; }; C9D2C8942A320AA000D15901 /* DerivationPathKeysHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C909615A29F6535300002D82 /* DerivationPathKeysHeaderView.swift */; }; C9D2C8952A320AA000D15901 /* ConvertCryptoOrderPreviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CACF2970224D00F63AC4 /* ConvertCryptoOrderPreviewModel.swift */; }; - C9D2C8962A320AA000D15901 /* DWContactsSearchDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DAD2CB24757A210001624F /* DWContactsSearchDataSourceObject.m */; }; C9D2C8972A320AA000D15901 /* RatesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2E3A82972B15F0032A63B /* RatesProvider.swift */; }; C9D2C8982A320AA000D15901 /* CBAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2A2E8293E612900938DB7 /* CBAuth.swift */; }; C9D2C8992A320AA000D15901 /* DWButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A307CBE22E8A44200A18347 /* DWButton.m */; }; @@ -1221,15 +1246,12 @@ C9D2C8A12A320AA000D15901 /* ShortcutAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94F5E8929D3FCCF0034FD57 /* ShortcutAction.swift */; }; C9D2C8A22A320AA000D15901 /* DWModalChevronView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69DF231556D6001B8C90 /* DWModalChevronView.m */; }; C9D2C8A32A320AA000D15901 /* DWBaseLegacyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A58815821A5906C00FD4D2C /* DWBaseLegacyViewController.m */; }; - C9D2C8A42A320AA000D15901 /* DWNoNotificationsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C3CA202F247E54C400158074 /* DWNoNotificationsCell.m */; }; C9D2C8A52A320AA000D15901 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F452002A0CE6C900825057 /* HomeView.swift */; }; C9D2C8A62A320AA000D15901 /* CoinbaseRatesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A2E3AB2972B1A60032A63B /* CoinbaseRatesProvider.swift */; }; C9D2C8A72A320AA000D15901 /* TaxReportGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472D13DE299DF5C6006903F1 /* TaxReportGenerator.swift */; }; C9D2C8A82A320AA000D15901 /* CrowdNodeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119E8D112909513F00D406C1 /* CrowdNodeError.swift */; }; C9D2C8A92A320AA000D15901 /* WithdrawalLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C4D296C52F800788E18 /* WithdrawalLimit.swift */; }; C9D2C8AA2A320AA000D15901 /* DWPayModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B9E6422FFE43500FF8653 /* DWPayModel.m */; }; - C9D2C8AB2A320AA000D15901 /* DWSearchStateViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A885FC62449B66500B9F679 /* DWSearchStateViewController.m */; }; - C9D2C8AC2A320AA000D15901 /* DWDPTxListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3A724BE0C7D00DB32DE /* DWDPTxListCell.m */; }; C9D2C8AD2A320AA000D15901 /* PointOfUseDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8C1728C63F9C00490F5E /* PointOfUseDetailsView.swift */; }; C9D2C8AE2A320AA000D15901 /* DSChain+DashWallet.m in Sources */ = {isa = PBXBuildFile; fileRef = 47E4F7C6297596D8006BEA68 /* DSChain+DashWallet.m */; }; C9D2C8AF2A320AA000D15901 /* FromLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193FF3A2961960B004EA8D7 /* FromLabel.swift */; }; @@ -1238,32 +1260,23 @@ C9D2C8B22A320AA000D15901 /* CoinbaseAccountAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F6EDFAC28C896BC000427E7 /* CoinbaseAccountAddress.swift */; }; C9D2C8B32A320AA000D15901 /* DWMainMenuTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BBC2347950700451078 /* DWMainMenuTableViewCell.m */; }; C9D2C8B42A320AA000D15901 /* CrowdNodeTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1141E4C1291BB12200ACDA9E /* CrowdNodeTransferViewController.swift */; }; - C9D2C8B52A320AA000D15901 /* DWUserSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB2373724488DB80081B62C /* DWUserSearchViewController.m */; }; C9D2C8B62A320AA000D15901 /* CBAuthInterop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478483E729629C0700E05A5A /* CBAuthInterop.swift */; }; - C9D2C8B72A320AA000D15901 /* UIFont+DWDPItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF38E2482BDF1001D74F9 /* UIFont+DWDPItem.m */; }; C9D2C8B82A320AA000D15901 /* CoinbaseAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4789D26F29825F5400BAFEFA /* CoinbaseAmountViewController.swift */; }; C9D2C8B92A320AA000D15901 /* DWUpholdAuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFE482230FF4600956D5F /* DWUpholdAuthViewController.m */; }; C9D2C8BA2A320AA000D15901 /* DWSegmentSliderFormCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F640E238D5C0900A9B505 /* DWSegmentSliderFormCellModel.m */; }; C9D2C8BB2A320AA000D15901 /* DWSeedPhraseView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE5F22D8E9D900C99324 /* DWSeedPhraseView.m */; }; - C9D2C8BC2A320AA000D15901 /* DWDPAvatarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3CCEFB242BB1DD00300AF8 /* DWDPAvatarView.m */; }; C9D2C8BD2A320AA000D15901 /* UIStackView+DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B30D7B29100ABA0080C326 /* UIStackView+DashWallet.swift */; }; - C9D2C8BE2A320AA000D15901 /* DWUserProfileContactActionsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADC9D7B24644E46001D7C0D /* DWUserProfileContactActionsCell.m */; }; C9D2C8BF2A320AA000D15901 /* ExplorePointOfUse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8B9B28BFAD2800490F5E /* ExplorePointOfUse.swift */; }; - C9D2C8C02A320AA000D15901 /* DWUserProfileDataSourceObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ACCA3B124BE3CDC00DB32DE /* DWUserProfileDataSourceObject.m */; }; C9D2C8C12A320AA000D15901 /* DWDashPayModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EF0524193AEB002C32F3 /* DWDashPayModel.m */; }; C9D2C8C22A320AA000D15901 /* TransactionObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 117ED4A028EC86E0006E3EE4 /* TransactionObserver.swift */; }; C9D2C8C32A320AA000D15901 /* DWInfoTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4431D322D52F67009BAF7F /* DWInfoTextCell.m */; }; C9D2C8C42A320AA000D15901 /* ColorizedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EE17182959CDC200BA1986 /* ColorizedText.swift */; }; - C9D2C8C52A320AA000D15901 /* DWDPPendingRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF37A2482756D001D74F9 /* DWDPPendingRequestObject.m */; }; - C9D2C8C62A320AA000D15901 /* DWModalUserProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A80F39424D86201003E3B1E /* DWModalUserProfileViewController.m */; }; C9D2C8C72A320AA000D15901 /* PaymentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FA229DBC183001BC549 /* PaymentsViewController.swift */; }; - C9D2C8C82A320AA000D15901 /* DWDPContactsItemsFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AF3822482954C001D74F9 /* DWDPContactsItemsFactory.m */; }; C9D2C8C92A320AA000D15901 /* ServiceDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4774DCDC28F43A68008CF87D /* ServiceDataSource.swift */; }; C9D2C8CA2A320AA000D15901 /* ConverterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47838B792900196F0003E8AB /* ConverterView.swift */; }; C9D2C8CB2A320AA000D15901 /* DWModalBaseAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A0C69C923142E11001B8C90 /* DWModalBaseAnimation.m */; }; C9D2C8CC2A320AA000D15901 /* SpendableTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F42FB729DFC506001BC549 /* SpendableTransaction.swift */; }; C9D2C8CD2A320AA000D15901 /* StakingInfoDialogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11ED906A29681773003784F9 /* StakingInfoDialogController.swift */; }; - C9D2C8CE2A320AA000D15901 /* DWCreateUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE8B64023CDB98A0016F221 /* DWCreateUsernameViewController.m */; }; C9D2C8CF2A320AA000D15901 /* TwoFactorAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F36937D2919A5DB007F4E91 /* TwoFactorAuthViewController.swift */; }; C9D2C8D02A320AA000D15901 /* Taxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471A2609289ACDF70056B7B2 /* Taxes.swift */; }; C9D2C8D12A320AA000D15901 /* DWSeedWordModel+DWLayoutSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD1CE9622DD0E8E00C99324 /* DWSeedWordModel+DWLayoutSupport.m */; }; @@ -1275,8 +1288,6 @@ C9D2C8D72A320AA000D15901 /* DWSecurityMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7A7BCC2347F01B00451078 /* DWSecurityMenuViewController.m */; }; C9D2C8D82A320AA000D15901 /* CrowdNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BD738028E7356100A34022 /* CrowdNode.swift */; }; C9D2C8D92A320AA000D15901 /* SQLite+ExloreDash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8B9E28BFAD8200490F5E /* SQLite+ExloreDash.swift */; }; - C9D2C8DA2A320AA000D15901 /* DWFilterHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E534A22F03A9E00E5168A /* DWFilterHeaderView.m */; }; - C9D2C8DB2A320AA000D15901 /* DWBaseContactsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A951CE323D1B92C00602824 /* DWBaseContactsModel.m */; }; C9D2C8DC2A320AA000D15901 /* DWAdvancedSecurityModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F6414238FEEA900A9B505 /* DWAdvancedSecurityModel.m */; }; C9D2C8DD2A320AA000D15901 /* AtmDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AE8BD528C1305E00490F5E /* AtmDataProvider.swift */; }; C9D2C8DE2A320AA000D15901 /* GettingStartedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110C67942921147F006B580C /* GettingStartedViewController.swift */; }; @@ -1294,13 +1305,12 @@ C9D2C8EA2A320AA000D15901 /* ConvertCryptoOrderPreviewViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4751CAD82970509600F63AC4 /* ConvertCryptoOrderPreviewViews.swift */; }; C9D2C8EB2A320AA000D15901 /* DWAboutModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8F421F21BEFEEA00858B91 /* DWAboutModel.m */; }; C9D2C8EC2A320AA000D15901 /* CNCreateAccountTxDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47083B3A298948B70010AF71 /* CNCreateAccountTxDetailsViewController.swift */; }; - C9D2C8ED2A320AA000D15901 /* UIColor+DWDashPay.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AB7303D24D0BC0400DCB420 /* UIColor+DWDashPay.m */; }; C9D2C8EE2A320AA000D15901 /* WelcomeToCrowdNodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1141E4C4291FDC7A00ACDA9E /* WelcomeToCrowdNodeViewController.swift */; }; C9D2C8EF2A320AA000D15901 /* TerritoriesListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C6E6E229196D48003FEDF2 /* TerritoriesListCell.swift */; }; C9D2C8F02A320AA000D15901 /* DWProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4E531C22EA49FE00E5168A /* DWProgressView.m */; }; C9D2C8F12A320AA000D15901 /* TxDetailCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A5146328491C60005A8E3E /* TxDetailCells.swift */; }; C9D2C8F22A320AA000D15901 /* BaseViewController+NetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477F501629543834003C7508 /* BaseViewController+NetworkReachability.swift */; }; - C9D2C8F32A320AA000D15901 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 75D5F3CD191EC270004AB296 /* main.m */; }; + C9D2C8F32A320AA000D15901 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 75D5F3CD191EC270004AB296 /* main.m */; settings = {COMPILER_FLAGS = "-DASHPAY"; }; }; C9D2C8F42A320AA000D15901 /* DWQuickReceiveViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C24B323336FEA00000D43 /* DWQuickReceiveViewController.m */; }; C9D2C8F62A320AA000D15901 /* libDashSync.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FC75A8E28F03E22000E4858 /* libDashSync.a */; }; C9D2C8F72A320AA000D15901 /* CoreNFC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A5279BB23D994BC00F856D3 /* CoreNFC.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; @@ -1326,13 +1336,11 @@ C9D2C90D2A320AA000D15901 /* ExploreDash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 47838B7E290160860003E8AB /* ExploreDash.storyboard */; }; C9D2C90E2A320AA000D15901 /* TxDetailHeaderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 474C720C298A19D100475CA6 /* TxDetailHeaderCell.xib */; }; C9D2C90F2A320AA000D15901 /* StartStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2AB231D32196E27300A6E7E6 /* StartStoryboard.storyboard */; }; - C9D2C9102A320AA000D15901 /* DWConfirmUsernameContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A60C9442444BF3900AF72CF /* DWConfirmUsernameContentView.xib */; }; C9D2C9112A320AA000D15901 /* DWShortcutCollectionViewCell~iphone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A1AF6DD23C7681B00442AF5 /* DWShortcutCollectionViewCell~iphone.xib */; }; C9D2C9122A320AA000D15901 /* DWInfoTextCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4431D422D52F67009BAF7F /* DWInfoTextCell.xib */; }; C9D2C9132A320AA000D15901 /* BiometricAuth.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A44314822CF82EF009BAF7F /* BiometricAuth.storyboard */; }; C9D2C9142A320AA000D15901 /* TxListEmptyTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E533F22F025FE00E5168A /* TxListEmptyTableViewCell.xib */; }; C9D2C9152A320AA000D15901 /* Payments.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A8B9E3C22FD71E100FF8653 /* Payments.storyboard */; }; - C9D2C9162A320AA000D15901 /* DWFilterHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */; }; C9D2C9172A320AA000D15901 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 757E09971ADB8EEB006FD352 /* Localizable.strings */; }; C9D2C9182A320AA000D15901 /* LockScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A6300412328CCE900827825 /* LockScreen.storyboard */; }; C9D2C9192A320AA000D15901 /* UpholdMainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A9FFE6E2230FF4600956D5F /* UpholdMainStoryboard.storyboard */; }; @@ -1355,7 +1363,6 @@ C9D2C92A2A320AA000D15901 /* explore.db in Resources */ = {isa = PBXBuildFile; fileRef = 47AE8BAF28BFF28400490F5E /* explore.db */; }; C9D2C92B2A320AA000D15901 /* DWConfirmPaymentContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A0C69F323179C0F001B8C90 /* DWConfirmPaymentContentView.xib */; }; C9D2C92C2A320AA000D15901 /* Coinbase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0F3693812919A70B007F4E91 /* Coinbase.storyboard */; }; - C9D2C92D2A320AA000D15901 /* DWTitleActionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C3DAD2C7247538AA0001624F /* DWTitleActionHeaderView.xib */; }; C9D2C92E2A320AA000D15901 /* ImportWalletInfo.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A10EB3D2358BDA500C38B61 /* ImportWalletInfo.storyboard */; }; C9D2C92F2A320AA000D15901 /* ReceiveContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F42FAC29DC115A001BC549 /* ReceiveContentView.xib */; }; C9D2C9302A320AA000D15901 /* VerifySeedPhrase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2AD1CE8522DC9B7300C99324 /* VerifySeedPhrase.storyboard */; }; @@ -1386,7 +1393,6 @@ C9D2C9622A386DA200D15901 /* DWDPWelcomeView.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C9602A386DA200D15901 /* DWDPWelcomeView.m */; }; C9D2C9652A38733B00D15901 /* DPAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9D2C9642A38733B00D15901 /* DPAssets.xcassets */; }; C9D2C9692A3875BA00D15901 /* DWCurrentUserProfileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C9672A3875BA00D15901 /* DWCurrentUserProfileModel.m */; }; - C9D2C96C2A38762800D15901 /* DWDPUpdateProfileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C96B2A38762800D15901 /* DWDPUpdateProfileModel.m */; }; C9D2C9712A38778E00D15901 /* DWDashPaySetupModel.m in Sources */ = {isa = PBXBuildFile; fileRef = C9D2C9702A38778E00D15901 /* DWDashPaySetupModel.m */; }; C9F067F229E4576D0022D958 /* HomeBalanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F067F129E4576D0022D958 /* HomeBalanceView.swift */; }; C9F067F429E543630022D958 /* HomeBalanceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9F067F329E457790022D958 /* HomeBalanceView.xib */; }; @@ -1656,10 +1662,6 @@ 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 = ""; }; 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 = ""; }; - 2A1AE79023F468CD00179A6E /* DWPlanetarySystemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPlanetarySystemView.h; sourceTree = ""; }; - 2A1AE79123F468CD00179A6E /* DWPlanetarySystemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWPlanetarySystemView.m; sourceTree = ""; }; 2A1AF6DC23C7681B00442AF5 /* DWShortcutCollectionViewCell~ipad.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = "DWShortcutCollectionViewCell~ipad.xib"; sourceTree = ""; }; 2A1AF6DD23C7681B00442AF5 /* DWShortcutCollectionViewCell~iphone.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = "DWShortcutCollectionViewCell~iphone.xib"; sourceTree = ""; }; 2A1B7D902322AE2200BA8C6A /* DWTitleDetailItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTitleDetailItem.h; sourceTree = ""; }; @@ -1704,10 +1706,6 @@ 2A36A88C2350A18A0014DC60 /* DWPreviewSeedPhraseViewController+DWProtected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWPreviewSeedPhraseViewController+DWProtected.h"; 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 = ""; }; - 2A3CCEF8242BB1B900300AF8 /* DWRegistrationCompletedViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWRegistrationCompletedViewController.m; sourceTree = ""; }; - 2A3CCEFA242BB1DD00300AF8 /* DWDPAvatarView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPAvatarView.h; sourceTree = ""; }; - 2A3CCEFB242BB1DD00300AF8 /* DWDPAvatarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPAvatarView.m; sourceTree = ""; }; 2A3DC86C239717C2004B3DBA /* DWRecoverWalletCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRecoverWalletCommand.h; sourceTree = ""; }; 2A3DC86D239717C2004B3DBA /* DWRecoverWalletCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWRecoverWalletCommand.m; sourceTree = ""; }; 2A3DC86F23972331004B3DBA /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWHomeViewController+DWImportPrivateKeyDelegateImpl.h"; sourceTree = ""; }; @@ -1765,37 +1763,21 @@ 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 /* 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 = ""; }; 2A5279BB23D994BC00F856D3 /* CoreNFC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreNFC.framework; path = System/Library/Frameworks/CoreNFC.framework; sourceTree = SDKROOT; }; - 2A56EEF92417E30F002C32F3 /* DWConfirmUsernameContentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWConfirmUsernameContentView.h; sourceTree = ""; }; - 2A56EEFA2417E30F002C32F3 /* DWConfirmUsernameContentView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWConfirmUsernameContentView.m; sourceTree = ""; }; - 2A56EEFE2419310C002C32F3 /* DWDashPayConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPayConstants.h; sourceTree = ""; }; - 2A56EEFF2419310C002C32F3 /* DWDashPayConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDashPayConstants.m; sourceTree = ""; }; 2A56EF0424193AEB002C32F3 /* DWDashPayModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPayModel.h; sourceTree = ""; }; 2A56EF0524193AEB002C32F3 /* DWDashPayModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDashPayModel.m; sourceTree = ""; }; 2A56EF0724193BA9002C32F3 /* DWDashPayProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPayProtocol.h; sourceTree = ""; }; 2A58815721A5906C00FD4D2C /* DWBaseLegacyViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBaseLegacyViewController.h; sourceTree = ""; }; 2A58815821A5906C00FD4D2C /* DWBaseLegacyViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBaseLegacyViewController.m; sourceTree = ""; }; 2A5BB89B2350BA4F00C87CCE /* DWWipeDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWWipeDelegate.h; sourceTree = ""; }; - 2A5BD5902451D68200688A8D /* DWMinLengthUsernameValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWMinLengthUsernameValidationRule.h; sourceTree = ""; }; - 2A5BD5912451D68300688A8D /* DWMinLengthUsernameValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWMinLengthUsernameValidationRule.m; sourceTree = ""; }; - 2A5BD5932451DCAF00688A8D /* DWAllowedCharactersUsernameValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWAllowedCharactersUsernameValidationRule.h; sourceTree = ""; }; - 2A5BD5942451DCAF00688A8D /* DWAllowedCharactersUsernameValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWAllowedCharactersUsernameValidationRule.m; sourceTree = ""; }; - 2A5BD5962451DD0700688A8D /* DWMaxLengthUsernameValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWMaxLengthUsernameValidationRule.h; sourceTree = ""; }; - 2A5BD5972451DD0700688A8D /* DWMaxLengthUsernameValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWMaxLengthUsernameValidationRule.m; sourceTree = ""; }; - 2A5BD5992451F1A100688A8D /* DWUsernameValidationRule+Protected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWUsernameValidationRule+Protected.h"; sourceTree = ""; }; - 2A5BD59C2451F39500688A8D /* DWCheckExistenceUsernameValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWCheckExistenceUsernameValidationRule.h; sourceTree = ""; }; - 2A5BD59D2451F39500688A8D /* DWCheckExistenceUsernameValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWCheckExistenceUsernameValidationRule.m; sourceTree = ""; }; 2A5E4545243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPRegistrationStatusTableViewCell.h; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -1805,9 +1787,6 @@ 2A6300482328EA8900827825 /* DWLockActionButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWLockActionButton.m; sourceTree = ""; }; 2A63004C2328F37C00827825 /* DWLockScreenViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWLockScreenViewController.h; sourceTree = ""; }; 2A63004D2328F37C00827825 /* DWLockScreenViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWLockScreenViewController.m; sourceTree = ""; }; - 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 = ""; }; 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 = ""; }; @@ -1869,66 +1848,6 @@ 2A7A7C1C234B771400451078 /* DWLocalCurrencyModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWLocalCurrencyModel.m; sourceTree = ""; }; 2A7A7C1E234B79B700451078 /* DWLocalCurrencyTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWLocalCurrencyTableViewCell.h; sourceTree = ""; }; 2A7A7C1F234B79B700451078 /* DWLocalCurrencyTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWLocalCurrencyTableViewCell.m; sourceTree = ""; }; - 2A7AF3132480DA51001D74F9 /* DWIncomingFetchedDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWIncomingFetchedDataSource.h; sourceTree = ""; }; - 2A7AF3142480DA51001D74F9 /* DWIncomingFetchedDataSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWIncomingFetchedDataSource.m; sourceTree = ""; }; - 2A7AF3162480E35A001D74F9 /* DWContactsFetchedDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsFetchedDataSource.h; sourceTree = ""; }; - 2A7AF3172480E35A001D74F9 /* DWContactsFetchedDataSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWContactsFetchedDataSource.m; sourceTree = ""; }; - 2A7AF31A2480E6AD001D74F9 /* DWNotificationsModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWNotificationsModel.h; sourceTree = ""; }; - 2A7AF31B2480E6AD001D74F9 /* DWNotificationsModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsModel.m; sourceTree = ""; }; - 2A7AF32624814A17001D74F9 /* DWNotificationsData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWNotificationsData.h; sourceTree = ""; }; - 2A7AF32724814A17001D74F9 /* DWNotificationsData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsData.m; sourceTree = ""; }; - 2A7AF33E2481A1B2001D74F9 /* DWDPGenericItemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPGenericItemView.h; sourceTree = ""; }; - 2A7AF33F2481A1B2001D74F9 /* DWDPGenericItemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPGenericItemView.m; sourceTree = ""; }; - 2A7AF34224822AE0001D74F9 /* DWDPGenericContactRequestItemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPGenericContactRequestItemView.h; sourceTree = ""; }; - 2A7AF34324822AE0001D74F9 /* DWDPGenericContactRequestItemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPGenericContactRequestItemView.m; sourceTree = ""; }; - 2A7AF34724823167001D74F9 /* DWDPGenericImageItemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPGenericImageItemView.h; sourceTree = ""; }; - 2A7AF34824823167001D74F9 /* DWDPGenericImageItemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPGenericImageItemView.m; sourceTree = ""; }; - 2A7AF34A24823315001D74F9 /* DWDPGenericStatusItemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPGenericStatusItemView.h; sourceTree = ""; }; - 2A7AF34B24823315001D74F9 /* DWDPGenericStatusItemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPGenericStatusItemView.m; sourceTree = ""; }; - 2A7AF34E2482374D001D74F9 /* DWDPBasicCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPBasicCell.h; sourceTree = ""; }; - 2A7AF34F2482374D001D74F9 /* DWDPBasicCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPBasicCell.m; sourceTree = ""; }; - 2A7AF35124823EC8001D74F9 /* DWDPIncomingRequestCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPIncomingRequestCell.h; sourceTree = ""; }; - 2A7AF35224823EC8001D74F9 /* DWDPIncomingRequestCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPIncomingRequestCell.m; sourceTree = ""; }; - 2A7AF35424824F97001D74F9 /* DWDPImageStatusCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPImageStatusCell.h; sourceTree = ""; }; - 2A7AF35524824F97001D74F9 /* DWDPImageStatusCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPImageStatusCell.m; sourceTree = ""; }; - 2A7AF35724824FEB001D74F9 /* DWDPTextStatusCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPTextStatusCell.h; sourceTree = ""; }; - 2A7AF35824824FEB001D74F9 /* DWDPTextStatusCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPTextStatusCell.m; sourceTree = ""; }; - 2A7AF35B2482577B001D74F9 /* DWDPBasicUserItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPBasicUserItem.h; sourceTree = ""; }; - 2A7AF35C24825860001D74F9 /* DWDPIncomingRequestItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPIncomingRequestItem.h; sourceTree = ""; }; - 2A7AF35D248258C9001D74F9 /* DWDPRespondedRequestItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPRespondedRequestItem.h; sourceTree = ""; }; - 2A7AF35E2482592F001D74F9 /* DWDPPendingRequestItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPPendingRequestItem.h; sourceTree = ""; }; - 2A7AF35F24825991001D74F9 /* DWDPEstablishedContactItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPEstablishedContactItem.h; sourceTree = ""; }; - 2A7AF36124825A0C001D74F9 /* DWDPUserObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPUserObject.h; sourceTree = ""; }; - 2A7AF36224825A0C001D74F9 /* DWDPUserObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPUserObject.m; sourceTree = ""; }; - 2A7AF3642482666C001D74F9 /* DWDPIncomingRequestObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPIncomingRequestObject.h; sourceTree = ""; }; - 2A7AF3652482666C001D74F9 /* DWDPIncomingRequestObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPIncomingRequestObject.m; sourceTree = ""; }; - 2A7AF36724826681001D74F9 /* DWDPRespondedIncomingRequestObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPRespondedIncomingRequestObject.h; sourceTree = ""; }; - 2A7AF36824826681001D74F9 /* DWDPRespondedIncomingRequestObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPRespondedIncomingRequestObject.m; sourceTree = ""; }; - 2A7AF36A248266FB001D74F9 /* DWDPEstablishedContactObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPEstablishedContactObject.h; sourceTree = ""; }; - 2A7AF36B248266FB001D74F9 /* DWDPEstablishedContactObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPEstablishedContactObject.m; sourceTree = ""; }; - 2A7AF36D24826737001D74F9 /* DWDPContactObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPContactObject.h; sourceTree = ""; }; - 2A7AF36E24826737001D74F9 /* DWDPContactObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPContactObject.m; sourceTree = ""; }; - 2A7AF37024826CDF001D74F9 /* DWDPAcceptedRequestNotificationObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPAcceptedRequestNotificationObject.h; sourceTree = ""; }; - 2A7AF37124826CDF001D74F9 /* DWDPAcceptedRequestNotificationObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPAcceptedRequestNotificationObject.m; sourceTree = ""; }; - 2A7AF3732482703C001D74F9 /* DWDPNewIncomingRequestNotificationObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPNewIncomingRequestNotificationObject.h; sourceTree = ""; }; - 2A7AF3742482703C001D74F9 /* DWDPNewIncomingRequestNotificationObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPNewIncomingRequestNotificationObject.m; sourceTree = ""; }; - 2A7AF376248270A4001D74F9 /* DWDPEstablishedContactNotificationObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPEstablishedContactNotificationObject.h; sourceTree = ""; }; - 2A7AF377248270A4001D74F9 /* DWDPEstablishedContactNotificationObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPEstablishedContactNotificationObject.m; sourceTree = ""; }; - 2A7AF3792482756D001D74F9 /* DWDPPendingRequestObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPPendingRequestObject.h; sourceTree = ""; }; - 2A7AF37A2482756D001D74F9 /* DWDPPendingRequestObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPPendingRequestObject.m; sourceTree = ""; }; - 2A7AF37E248280CE001D74F9 /* DWDPSearchItemsFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPSearchItemsFactory.h; sourceTree = ""; }; - 2A7AF37F248280CE001D74F9 /* DWDPSearchItemsFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPSearchItemsFactory.m; sourceTree = ""; }; - 2A7AF3812482954C001D74F9 /* DWDPContactsItemsFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPContactsItemsFactory.h; sourceTree = ""; }; - 2A7AF3822482954C001D74F9 /* DWDPContactsItemsFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPContactsItemsFactory.m; sourceTree = ""; }; - 2A7AF38724829AF2001D74F9 /* UICollectionView+DWDPItemDequeue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+DWDPItemDequeue.h"; sourceTree = ""; }; - 2A7AF38824829AF2001D74F9 /* UICollectionView+DWDPItemDequeue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+DWDPItemDequeue.m"; sourceTree = ""; }; - 2A7AF38A2482A22D001D74F9 /* DWDPBlockchainIdentityBackedItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPBlockchainIdentityBackedItem.h; sourceTree = ""; }; - 2A7AF38D2482BDF1001D74F9 /* UIFont+DWDPItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+DWDPItem.h"; sourceTree = ""; }; - 2A7AF38E2482BDF1001D74F9 /* UIFont+DWDPItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+DWDPItem.m"; sourceTree = ""; }; - 2A7AF3912482D9BE001D74F9 /* DWDPDashpayUserBackedItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPDashpayUserBackedItem.h; sourceTree = ""; }; - 2A7AF3922482DA2B001D74F9 /* DWDPFriendRequestBackedItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPFriendRequestBackedItem.h; sourceTree = ""; }; - 2A7AF3972482E32E001D74F9 /* DWDashPayContactsActions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPayContactsActions.h; sourceTree = ""; }; - 2A7AF3982482E32E001D74F9 /* DWDashPayContactsActions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDashPayContactsActions.m; sourceTree = ""; }; 2A7AF39C2482FE46001D74F9 /* DWDateFormatter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDateFormatter.h; sourceTree = ""; }; 2A7AF39D2482FE46001D74F9 /* DWDateFormatter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDateFormatter.m; sourceTree = ""; }; 2A7F3B19238C646600DEA3EF /* DWAdvancedSecurityViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWAdvancedSecurityViewController.h; sourceTree = ""; }; @@ -1937,14 +1856,7 @@ 2A7F3B1E238C651200DEA3EF /* DWSecurityStatusView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSecurityStatusView.m; sourceTree = ""; }; 2A7F3B20238C653000DEA3EF /* DWSecurityStatusView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWSecurityStatusView.xib; sourceTree = ""; }; 2A7F3B22238C709100DEA3EF /* DWSecurityLevel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSecurityLevel.h; sourceTree = ""; }; - 2A80F39324D86201003E3B1E /* DWModalUserProfileViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWModalUserProfileViewController.h; sourceTree = ""; }; - 2A80F39424D86201003E3B1E /* DWModalUserProfileViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWModalUserProfileViewController.m; sourceTree = ""; }; - 2A80F3D724DC55CC003E3B1E /* DWUserProfileSendRequestCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUserProfileSendRequestCell.h; sourceTree = ""; }; - 2A80F3D824DC55CC003E3B1E /* DWUserProfileSendRequestCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileSendRequestCell.m; sourceTree = ""; }; 2A811558269CE09300215F81 /* uphold-logout.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "uphold-logout.jpg"; sourceTree = ""; }; - 2A827B7024B5CA1800A42042 /* DWListCollectionLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWListCollectionLayout.h; sourceTree = ""; }; - 2A827B7124B5CA1800A42042 /* DWListCollectionLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWListCollectionLayout.m; sourceTree = ""; }; - 2A827B7324B729C400A42042 /* DWBaseContactsContentViewController+DWProtected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWBaseContactsContentViewController+DWProtected.h"; sourceTree = ""; }; 2A858A05237EE89B0097A7B5 /* BRAppleWatchTransactionData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRAppleWatchTransactionData.h; sourceTree = ""; }; 2A858A06237EE89B0097A7B5 /* DSWatchTransactionDataObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DSWatchTransactionDataObject.m; sourceTree = ""; }; 2A858A07237EE89B0097A7B5 /* DWPhoneWCSessionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWPhoneWCSessionManager.h; sourceTree = ""; }; @@ -1955,12 +1867,6 @@ 2A858A0C237EE89B0097A7B5 /* DWPhoneWCSessionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWPhoneWCSessionManager.m; sourceTree = ""; }; 2A858A0D237EE89B0097A7B5 /* DSWatchTransactionDataObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DSWatchTransactionDataObject.h; sourceTree = ""; }; 2A858A0E237EE89B0097A7B5 /* BRAppleWatchTransactionData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRAppleWatchTransactionData.m; sourceTree = ""; }; - 2A885FC52449B66500B9F679 /* DWSearchStateViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSearchStateViewController.h; sourceTree = ""; }; - 2A885FC62449B66500B9F679 /* DWSearchStateViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSearchStateViewController.m; sourceTree = ""; }; - 2A885FCA2449F08700B9F679 /* DWUserSearchResultViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUserSearchResultViewController.h; sourceTree = ""; }; - 2A885FCB2449F08700B9F679 /* DWUserSearchResultViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUserSearchResultViewController.m; sourceTree = ""; }; - 2A885FCE2449F37A00B9F679 /* DWUserSearchModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUserSearchModel.h; sourceTree = ""; }; - 2A885FCF2449F37A00B9F679 /* DWUserSearchModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUserSearchModel.m; sourceTree = ""; }; 2A885FD4244DFEF100B9F679 /* UIView+DWFindConstraints.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+DWFindConstraints.h"; sourceTree = ""; }; 2A885FD5244DFEF100B9F679 /* UIView+DWFindConstraints.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+DWFindConstraints.m"; sourceTree = ""; }; 2A8B9E2722FB1C5D00FF8653 /* DWSharedUIConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSharedUIConstants.h; sourceTree = ""; }; @@ -2042,26 +1948,13 @@ 2A9172C325233DC50024B4C5 /* DWPhraseRepairViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWPhraseRepairViewController.m; sourceTree = ""; }; 2A9172D125233F4F0024B4C5 /* DWPhraseRepairChildViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPhraseRepairChildViewController.h; sourceTree = ""; }; 2A9172D225233F4F0024B4C5 /* DWPhraseRepairChildViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWPhraseRepairChildViewController.m; sourceTree = ""; }; - 2A919F9A24A4DCAD0018C9A3 /* DWDPSmallContactView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPSmallContactView.h; sourceTree = ""; }; - 2A919F9B24A4DCAD0018C9A3 /* DWDPSmallContactView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPSmallContactView.m; sourceTree = ""; }; 2A919F9D24A65CE00018C9A3 /* DWDPAmountContactView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPAmountContactView.h; sourceTree = ""; }; 2A919F9E24A65CE00018C9A3 /* DWDPAmountContactView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPAmountContactView.m; sourceTree = ""; }; - 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 = ""; }; 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 = ""; }; 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 = ""; }; - 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 = ""; }; - 2A9E7DC523F6928C00CDA1EE /* DWTextField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTextField.h; sourceTree = ""; }; - 2A9E7DC623F6928C00CDA1EE /* DWTextField.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWTextField.m; sourceTree = ""; }; - 2A9E7DC823F6BD5200CDA1EE /* DWUsernameValidationView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUsernameValidationView.h; sourceTree = ""; }; - 2A9E7DC923F6BD5200CDA1EE /* DWUsernameValidationView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUsernameValidationView.m; sourceTree = ""; }; - 2A9E7DCC23F6C01A00CDA1EE /* DWUsernameValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUsernameValidationRule.h; sourceTree = ""; }; - 2A9E7DCD23F6C01A00CDA1EE /* DWUsernameValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUsernameValidationRule.m; sourceTree = ""; }; 2A9FFDEE2230FF1A00956D5F /* UIView+DWAnimations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+DWAnimations.h"; sourceTree = ""; }; 2A9FFDEF2230FF1A00956D5F /* UIView+DWAnimations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+DWAnimations.m"; sourceTree = ""; }; 2A9FFDF02230FF1A00956D5F /* SFSafariViewController+DashWallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SFSafariViewController+DashWallet.h"; sourceTree = ""; }; @@ -2132,8 +2025,6 @@ 2AB231D32196E27300A6E7E6 /* StartStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = StartStoryboard.storyboard; sourceTree = ""; }; 2AB231D52196E5CF00A6E7E6 /* DWStartModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWStartModel.h; sourceTree = ""; }; 2AB231D62196E5CF00A6E7E6 /* DWStartModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWStartModel.m; sourceTree = ""; }; - 2AB2373624488DB80081B62C /* DWUserSearchViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUserSearchViewController.h; sourceTree = ""; }; - 2AB2373724488DB80081B62C /* DWUserSearchViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUserSearchViewController.m; sourceTree = ""; }; 2AB3415B23A81291004E37A7 /* DWPayModelProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPayModelProtocol.h; sourceTree = ""; }; 2AB3415C23A8133A004E37A7 /* DWPayModelStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPayModelStub.h; sourceTree = ""; }; 2AB3415D23A8133A004E37A7 /* DWPayModelStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWPayModelStub.m; sourceTree = ""; }; @@ -2156,31 +2047,17 @@ 2AB3417923A929B6004E37A7 /* DWAdvancedSecurityModelStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWAdvancedSecurityModelStub.m; sourceTree = ""; }; 2AB3417D23A92A2A004E37A7 /* DWBaseAdvancedSecurityModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBaseAdvancedSecurityModel.h; sourceTree = ""; }; 2AB3417E23A92A2A004E37A7 /* DWBaseAdvancedSecurityModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBaseAdvancedSecurityModel.m; sourceTree = ""; }; - 2AB7303C24D0BC0400DCB420 /* UIColor+DWDashPay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+DWDashPay.h"; sourceTree = ""; }; - 2AB7303D24D0BC0400DCB420 /* UIColor+DWDashPay.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+DWDashPay.m"; sourceTree = ""; }; 2AB7C906234DB82700A56795 /* About.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = About.storyboard; sourceTree = ""; }; 2AB7F7E62384676200C173AD /* DWWatchDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DWWatchDataManager.swift; sourceTree = ""; }; 2AB7F7E823846F6000C173AD /* DWMainInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DWMainInterfaceController.swift; sourceTree = ""; }; 2AB7F7EA2384752C00C173AD /* DWTxInfoDisplayableInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DWTxInfoDisplayableInterfaceController.swift; sourceTree = ""; }; 2ABCA9172357A61B00092C09 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 2AC52AD4241BB5FC00D9A829 /* DWDashPaySetupFlowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPaySetupFlowController.h; sourceTree = ""; }; - 2AC52AD5241BB5FC00D9A829 /* DWDashPaySetupFlowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDashPaySetupFlowController.m; sourceTree = ""; }; 2AC92C821FEB0A6D008CAEE0 /* DWQRScanViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWQRScanViewController.h; sourceTree = ""; }; 2AC92C831FEB0A6D008CAEE0 /* DWQRScanViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWQRScanViewController.m; sourceTree = ""; }; 2AC92C851FEB0AE8008CAEE0 /* DWQRScanView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWQRScanView.h; sourceTree = ""; }; 2AC92C861FEB0AE8008CAEE0 /* DWQRScanView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWQRScanView.m; sourceTree = ""; }; 2AC92C881FEB0B8B008CAEE0 /* DWQRScanModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWQRScanModel.h; sourceTree = ""; }; 2AC92C891FEB0B8B008CAEE0 /* DWQRScanModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWQRScanModel.m; sourceTree = ""; }; - 2ACCA3A624BE0C7D00DB32DE /* DWDPTxListCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPTxListCell.h; sourceTree = ""; }; - 2ACCA3A724BE0C7D00DB32DE /* DWDPTxListCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPTxListCell.m; sourceTree = ""; }; - 2ACCA3A924BE0D3600DB32DE /* DWDPTxItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPTxItem.h; sourceTree = ""; }; - 2ACCA3AB24BE117300DB32DE /* DWProfileTxsFetchedDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWProfileTxsFetchedDataSource.h; sourceTree = ""; }; - 2ACCA3AC24BE117300DB32DE /* DWProfileTxsFetchedDataSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWProfileTxsFetchedDataSource.m; sourceTree = ""; }; - 2ACCA3AE24BE3BF900DB32DE /* DWUserProfileDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUserProfileDataSource.h; sourceTree = ""; }; - 2ACCA3B024BE3CDC00DB32DE /* DWUserProfileDataSourceObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUserProfileDataSourceObject.h; sourceTree = ""; }; - 2ACCA3B124BE3CDC00DB32DE /* DWUserProfileDataSourceObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileDataSourceObject.m; sourceTree = ""; }; - 2ACCA3B324BF280A00DB32DE /* DWDPTxObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPTxObject.h; sourceTree = ""; }; - 2ACCA3B424BF280A00DB32DE /* DWDPTxObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPTxObject.m; sourceTree = ""; }; 2ACCD84B23180E7E00A96B62 /* DWPaymentOutput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPaymentOutput.h; sourceTree = ""; }; 2ACCD84C23180E7E00A96B62 /* DWPaymentOutput.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWPaymentOutput.m; sourceTree = ""; }; 2ACCD84E23181CA800A96B62 /* DWPaymentOutput+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWPaymentOutput+Private.h"; sourceTree = ""; }; @@ -2242,66 +2119,17 @@ 2AD1CEAA22E18F1800C99324 /* DWGlobalOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWGlobalOptions.m; sourceTree = ""; }; 2AD46230232A286000C71557 /* DWLockScreenModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWLockScreenModel.h; sourceTree = ""; }; 2AD46231232A286000C71557 /* DWLockScreenModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWLockScreenModel.m; sourceTree = ""; }; - 2AD6E54B2487CE0100B52F14 /* DWContactsDataSourceObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsDataSourceObject.h; sourceTree = ""; }; - 2AD6E54C2487CE0100B52F14 /* DWContactsDataSourceObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWContactsDataSourceObject.m; sourceTree = ""; }; - 2AD6E54E2487D45F00B52F14 /* DWBaseContactsModel+DWProtected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWBaseContactsModel+DWProtected.h"; sourceTree = ""; }; - 2AD6E5512487D50200B52F14 /* DWContactsModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsModel.h; sourceTree = ""; }; - 2AD6E5522487D50200B52F14 /* DWContactsModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWContactsModel.m; sourceTree = ""; }; - 2AD6E5552487D8C000B52F14 /* DWContactsContentViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsContentViewController.h; sourceTree = ""; }; - 2AD6E5562487D8C000B52F14 /* DWContactsContentViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWContactsContentViewController.m; sourceTree = ""; }; - 2AD6E5582487D9AF00B52F14 /* DWContactsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsViewController.h; sourceTree = ""; }; - 2AD6E5592487D9AF00B52F14 /* DWContactsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWContactsViewController.m; sourceTree = ""; }; - 2AD6E55B2487DACC00B52F14 /* DWBaseContactsViewController+DWProtected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWBaseContactsViewController+DWProtected.h"; sourceTree = ""; }; - 2AD6E55E2487E13F00B52F14 /* DWRequestsContentViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRequestsContentViewController.h; sourceTree = ""; }; - 2AD6E55F2487E13F00B52F14 /* DWRequestsContentViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWRequestsContentViewController.m; sourceTree = ""; }; - 2AD6E5612487E16400B52F14 /* DWRequestsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRequestsViewController.h; sourceTree = ""; }; - 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 = ""; }; 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 = ""; }; - 2ADB396724223D9B00A6F898 /* DWDashPayAnimationView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPayAnimationView.h; sourceTree = ""; }; - 2ADB396824223D9B00A6F898 /* DWDashPayAnimationView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDashPayAnimationView.m; sourceTree = ""; }; 2ADB396A242615C200A6F898 /* CALayer+MBAnimationPersistence.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CALayer+MBAnimationPersistence.h"; sourceTree = ""; }; 2ADB396B242615C200A6F898 /* CALayer+MBAnimationPersistence.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CALayer+MBAnimationPersistence.m"; sourceTree = ""; }; 2ADC722823B5547000D9DD37 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 2ADC9D1A24603C4F001D7C0D /* UISearchBar+DWAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UISearchBar+DWAdditions.h"; sourceTree = ""; }; 2ADC9D1B24603C4F001D7C0D /* UISearchBar+DWAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UISearchBar+DWAdditions.m"; sourceTree = ""; }; - 2ADC9D6B2462D4AD001D7C0D /* DWStretchyHeaderListCollectionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWStretchyHeaderListCollectionLayout.h; sourceTree = ""; }; - 2ADC9D6C2462D4AD001D7C0D /* DWUserProfileHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileHeaderView.h; sourceTree = ""; }; - 2ADC9D6D2462D4AD001D7C0D /* DWUserProfileNavigationTitleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileNavigationTitleView.h; sourceTree = ""; }; - 2ADC9D6E2462D4AD001D7C0D /* DWUserProfileHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileHeaderView.m; sourceTree = ""; }; - 2ADC9D6F2462D4AD001D7C0D /* DWStretchyHeaderListCollectionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWStretchyHeaderListCollectionLayout.m; sourceTree = ""; }; - 2ADC9D702462D4AD001D7C0D /* DWUserProfileNavigationTitleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileNavigationTitleView.m; sourceTree = ""; }; - 2ADC9D7424640A2B001D7C0D /* DWUserProfileModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUserProfileModel.h; sourceTree = ""; }; - 2ADC9D7524640A2B001D7C0D /* DWUserProfileModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileModel.m; sourceTree = ""; }; - 2ADC9D7A24644E46001D7C0D /* DWUserProfileContactActionsCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUserProfileContactActionsCell.h; sourceTree = ""; }; - 2ADC9D7B24644E46001D7C0D /* DWUserProfileContactActionsCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileContactActionsCell.m; sourceTree = ""; }; 2ADF83FE23633116008459A7 /* SharedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = SharedAssets.xcassets; sourceTree = ""; }; - 2AE2F0F7245C16C8001DD722 /* DWUserProfileViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUserProfileViewController.h; sourceTree = ""; }; - 2AE2F0F8245C16C8001DD722 /* DWUserProfileViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileViewController.m; sourceTree = ""; }; 2AE47369241BC5E300804DD4 /* UIViewController+DWDisplayError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+DWDisplayError.h"; sourceTree = ""; }; 2AE4736A241BC5E300804DD4 /* UIViewController+DWDisplayError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+DWDisplayError.m"; sourceTree = ""; }; - 2AE56473249B675500CC2E80 /* DWDPNotificationItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPNotificationItem.h; sourceTree = ""; }; - 2AE8B63F23CDB98A0016F221 /* DWCreateUsernameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWCreateUsernameViewController.h; sourceTree = ""; }; - 2AE8B64023CDB98A0016F221 /* DWCreateUsernameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWCreateUsernameViewController.m; sourceTree = ""; }; - 2AE8B64223CDC0F40016F221 /* DWInputUsernameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWInputUsernameViewController.h; sourceTree = ""; }; - 2AE8B64323CDC0F50016F221 /* DWInputUsernameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWInputUsernameViewController.m; sourceTree = ""; }; - 2AE8B66223CF09000016F221 /* DWConfirmUsernameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWConfirmUsernameViewController.h; sourceTree = ""; }; - 2AE8B66323CF09000016F221 /* DWConfirmUsernameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWConfirmUsernameViewController.m; sourceTree = ""; }; - 2AE8B66923CF0F390016F221 /* DWUsernamePendingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUsernamePendingViewController.h; sourceTree = ""; }; - 2AE8B66A23CF0F390016F221 /* DWUsernamePendingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUsernamePendingViewController.m; sourceTree = ""; }; - 2AE9549B23D0C4F4003612B3 /* DWBaseContactsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBaseContactsViewController.h; sourceTree = ""; }; - 2AE9549C23D0C4F4003612B3 /* DWBaseContactsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBaseContactsViewController.m; sourceTree = ""; }; - 2AEC5CB32493D87D00F4A689 /* DWNotificationsProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWNotificationsProvider.h; sourceTree = ""; }; - 2AEC5CB42493D87D00F4A689 /* DWNotificationsProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsProvider.m; sourceTree = ""; }; - 2AEC5CB92494045C00F4A689 /* DWNotificationsFetchedDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWNotificationsFetchedDataSource.h; sourceTree = ""; }; - 2AEC5CBA2494045C00F4A689 /* DWNotificationsFetchedDataSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsFetchedDataSource.m; sourceTree = ""; }; - 2AEC5CBC24940EC200F4A689 /* DWDPOutgoingRequestNotificationObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDPOutgoingRequestNotificationObject.h; sourceTree = ""; }; - 2AEC5CBD24940EC200F4A689 /* DWDPOutgoingRequestNotificationObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDPOutgoingRequestNotificationObject.m; sourceTree = ""; }; - 2AEC5CC0249755BB00F4A689 /* DWDashPayContactsUpdater.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPayContactsUpdater.h; sourceTree = ""; }; - 2AEC5CC1249755BB00F4A689 /* DWDashPayContactsUpdater.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDashPayContactsUpdater.m; sourceTree = ""; }; 2AF26F39230C0E4C007F9228 /* DWBaseSeedViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBaseSeedViewController.h; sourceTree = ""; }; 2AF26F3A230C0E4C007F9228 /* DWBaseSeedViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBaseSeedViewController.m; sourceTree = ""; }; 2AFCB9BB23BE38DC00FF59A6 /* DWConfirmPaymentViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWConfirmPaymentViewProtocol.h; sourceTree = ""; }; @@ -2595,29 +2423,10 @@ BAE12BEA1B2DEE7F00895CC5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BAE12C051B2DEEF700895CC5 /* DWTodayViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWTodayViewController.m; sourceTree = ""; }; BEAE29AA65F429DD9EF1A1A7 /* libPods-DashWalletTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-DashWalletTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - C3CA202A247E4AF300158074 /* DWNotificationsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWNotificationsViewController.h; sourceTree = ""; }; - C3CA202B247E4AF300158074 /* DWNotificationsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsViewController.m; sourceTree = ""; }; - C3CA202E247E54C400158074 /* DWNoNotificationsCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWNoNotificationsCell.h; sourceTree = ""; }; - C3CA202F247E54C400158074 /* DWNoNotificationsCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWNoNotificationsCell.m; sourceTree = ""; }; C3DAD266246AA6F10001624F /* DWScreenshotWarningViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWScreenshotWarningViewController.h; sourceTree = ""; }; C3DAD267246AA6F10001624F /* DWScreenshotWarningViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWScreenshotWarningViewController.m; sourceTree = ""; }; - C3DAD26D246D46BF0001624F /* DWFetchedResultsDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWFetchedResultsDataSource.h; sourceTree = ""; }; - C3DAD26E246D46BF0001624F /* DWFetchedResultsDataSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWFetchedResultsDataSource.m; sourceTree = ""; }; - C3DAD2BB247327D90001624F /* DWContactsDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsDataSource.h; sourceTree = ""; }; - C3DAD2BE24747F580001624F /* DWSearchViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWSearchViewController.h; sourceTree = ""; }; - C3DAD2BF24747F580001624F /* DWSearchViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWSearchViewController.m; sourceTree = ""; }; - C3DAD2C1247512BA0001624F /* DWBaseContactsContentViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWBaseContactsContentViewController.h; sourceTree = ""; }; - C3DAD2C2247512BA0001624F /* DWBaseContactsContentViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWBaseContactsContentViewController.m; sourceTree = ""; }; - C3DAD2C4247534820001624F /* DWContactsSortModeProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsSortModeProtocol.h; sourceTree = ""; }; - C3DAD2C5247538A90001624F /* DWTitleActionHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWTitleActionHeaderView.m; sourceTree = ""; }; - C3DAD2C6247538A90001624F /* DWTitleActionHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWTitleActionHeaderView.h; sourceTree = ""; }; - C3DAD2C7247538AA0001624F /* DWTitleActionHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DWTitleActionHeaderView.xib; sourceTree = ""; }; - C3DAD2CA24757A210001624F /* DWContactsSearchDataSourceObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsSearchDataSourceObject.h; sourceTree = ""; }; - C3DAD2CB24757A210001624F /* DWContactsSearchDataSourceObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWContactsSearchDataSourceObject.m; sourceTree = ""; }; C3DAD2CD247585C10001624F /* NSPredicate+DWFullTextSearch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSPredicate+DWFullTextSearch.h"; sourceTree = ""; }; C3DAD2CE247585C10001624F /* NSPredicate+DWFullTextSearch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSPredicate+DWFullTextSearch.m"; sourceTree = ""; }; - C3DAD2D32476886D0001624F /* DWContactsSearchInfoHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWContactsSearchInfoHeaderView.h; sourceTree = ""; }; - C3DAD2D42476886D0001624F /* DWContactsSearchInfoHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWContactsSearchInfoHeaderView.m; sourceTree = ""; }; C47D5A9D319D41B450A9B96B /* libPods-WatchApp Extension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WatchApp Extension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C4C041DE4CD73FF445090FA3 /* Pods-dashpay.testnet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dashpay.testnet.xcconfig"; path = "Pods/Target Support Files/Pods-dashpay/Pods-dashpay.testnet.xcconfig"; sourceTree = ""; }; C909614C29EFF7D600002D82 /* WalletKeysOverviewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletKeysOverviewModel.swift; sourceTree = ""; }; @@ -2630,6 +2439,398 @@ 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 = ""; }; + C943B2BA2A408CEC00AF23C5 /* DWUpdatingUserProfileView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUpdatingUserProfileView.h; sourceTree = ""; }; + C943B2BB2A408CEC00AF23C5 /* DWCurrentUserProfileView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWCurrentUserProfileView.h; sourceTree = ""; }; + C943B2BC2A408CEC00AF23C5 /* DWErrorUpdatingUserProfileView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWErrorUpdatingUserProfileView.m; sourceTree = ""; }; + C943B2BD2A408CEC00AF23C5 /* DWDPWelcomeMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPWelcomeMenuView.h; sourceTree = ""; }; + C943B2BE2A408CEC00AF23C5 /* DWUserProfileContainerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileContainerView.h; sourceTree = ""; }; + C943B2C02A408CEC00AF23C5 /* DSBlockchainIdentity+DWDisplayTitleSubtitle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DSBlockchainIdentity+DWDisplayTitleSubtitle.h"; sourceTree = ""; }; + C943B2C12A408CEC00AF23C5 /* DWDPUpdateProfileModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPUpdateProfileModel.h; sourceTree = ""; }; + C943B2C22A408CEC00AF23C5 /* DSBlockchainIdentity+DWDisplayTitleSubtitle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "DSBlockchainIdentity+DWDisplayTitleSubtitle.m"; sourceTree = ""; }; + C943B2C32A408CEC00AF23C5 /* DWDPUpdateProfileModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPUpdateProfileModel.m; sourceTree = ""; }; + C943B2C42A408CEC00AF23C5 /* DWErrorUpdatingUserProfileView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWErrorUpdatingUserProfileView.h; sourceTree = ""; }; + C943B2C52A408CEC00AF23C5 /* DWCurrentUserProfileView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWCurrentUserProfileView.m; sourceTree = ""; }; + C943B2C62A408CEC00AF23C5 /* DWUpdatingUserProfileView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUpdatingUserProfileView.m; sourceTree = ""; }; + C943B2C72A408CEC00AF23C5 /* DWDPWelcomeMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPWelcomeMenuView.m; sourceTree = ""; }; + C943B2C82A408CEC00AF23C5 /* DWUserProfileContainerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileContainerView.m; sourceTree = ""; }; + C943B2CA2A408CEC00AF23C5 /* DWUserProfileModalQRContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileModalQRContentView.h; sourceTree = ""; }; + C943B2CB2A408CEC00AF23C5 /* DWDWUserProfileModalQRContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDWUserProfileModalQRContentView.h; sourceTree = ""; }; + C943B2CC2A408CEC00AF23C5 /* DWUserProfileModalQRViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileModalQRViewController.h; sourceTree = ""; }; + C943B2CD2A408CEC00AF23C5 /* DWUserProfileModalQRContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileModalQRContentView.m; sourceTree = ""; }; + C943B2CE2A408CEC00AF23C5 /* DWUserProfileModalQRViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileModalQRViewController.m; sourceTree = ""; }; + C943B2D02A408CEC00AF23C5 /* DWCropAvatarViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWCropAvatarViewController.h; sourceTree = ""; }; + C943B2D12A408CEC00AF23C5 /* DWEditProfileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWEditProfileViewController.h; sourceTree = ""; }; + C943B2D22A408CEC00AF23C5 /* DWRootEditProfileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWRootEditProfileViewController.h; sourceTree = ""; }; + C943B2D42A408CEC00AF23C5 /* DWImgurInfoChildView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWImgurInfoChildView.m; sourceTree = ""; }; + C943B2D52A408CEC00AF23C5 /* DWImgurInfoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWImgurInfoViewController.h; sourceTree = ""; }; + C943B2D62A408CEC00AF23C5 /* DWImgurItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWImgurItemView.m; sourceTree = ""; }; + C943B2D72A408CEC00AF23C5 /* DWImgurInfoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWImgurInfoViewController.m; sourceTree = ""; }; + C943B2D82A408CEC00AF23C5 /* DWImgurInfoChildView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWImgurInfoChildView.h; sourceTree = ""; }; + C943B2D92A408CEC00AF23C5 /* DWImgurItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWImgurItemView.h; sourceTree = ""; }; + C943B2DB2A408CEC00AF23C5 /* DWFaceDetector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWFaceDetector.h; sourceTree = ""; }; + C943B2DC2A408CEC00AF23C5 /* DWFaceDetector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWFaceDetector.m; sourceTree = ""; }; + C943B2DD2A408CEC00AF23C5 /* DWRootEditProfileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWRootEditProfileViewController.m; sourceTree = ""; }; + C943B2DE2A408CEC00AF23C5 /* DWEditProfileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWEditProfileViewController.m; sourceTree = ""; }; + C943B2DF2A408CEC00AF23C5 /* DWCropAvatarViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWCropAvatarViewController.m; sourceTree = ""; }; + C943B2E12A408CEC00AF23C5 /* DWAvatarEditSelectorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAvatarEditSelectorViewController.m; sourceTree = ""; }; + C943B2E22A408CEC00AF23C5 /* DWAvatarEditSelectorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAvatarEditSelectorViewController.h; sourceTree = ""; }; + C943B2E42A408CEC00AF23C5 /* DWAvatarEditSelectorContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAvatarEditSelectorContentView.m; sourceTree = ""; }; + C943B2E52A408CEC00AF23C5 /* DWAvatarEditSelectorContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAvatarEditSelectorContentView.h; sourceTree = ""; }; + C943B2E82A408CEC00AF23C5 /* DWExternalSourceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWExternalSourceViewController.h; sourceTree = ""; }; + C943B2E92A408CEC00AF23C5 /* DWExternalSourceViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWExternalSourceViewController.m; sourceTree = ""; }; + C943B2EB2A408CEC00AF23C5 /* DWAvatarExternalLoadingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAvatarExternalLoadingView.h; sourceTree = ""; }; + C943B2EC2A408CEC00AF23C5 /* DWAvatarExternalSourceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAvatarExternalSourceView.m; sourceTree = ""; }; + C943B2ED2A408CEC00AF23C5 /* DWAvatarExternalSourceConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAvatarExternalSourceConfig.h; sourceTree = ""; }; + C943B2EE2A408CEC00AF23C5 /* DWAvatarExternalLoadingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAvatarExternalLoadingView.m; sourceTree = ""; }; + C943B2EF2A408CEC00AF23C5 /* DWAvatarExternalSourceConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAvatarExternalSourceConfig.m; sourceTree = ""; }; + C943B2F02A408CEC00AF23C5 /* DWAvatarExternalSourceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAvatarExternalSourceView.h; sourceTree = ""; }; + C943B2F12A408CEC00AF23C5 /* DWAvatarGravatarViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAvatarGravatarViewController.h; sourceTree = ""; }; + C943B2F22A408CEC00AF23C5 /* DWAvatarPublicURLViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAvatarPublicURLViewController.m; sourceTree = ""; }; + C943B2F32A408CEC00AF23C5 /* DWAvatarGravatarViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAvatarGravatarViewController.m; sourceTree = ""; }; + C943B2F42A408CEC00AF23C5 /* DWAvatarPublicURLViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAvatarPublicURLViewController.h; sourceTree = ""; }; + C943B2F62A408CEC00AF23C5 /* DWSaveAlertChildView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSaveAlertChildView.h; sourceTree = ""; }; + C943B2F72A408CEC00AF23C5 /* DWSaveAlertViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWSaveAlertViewController.m; sourceTree = ""; }; + C943B2F82A408CEC00AF23C5 /* DWSaveAlertChildView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWSaveAlertChildView.m; sourceTree = ""; }; + C943B2F92A408CEC00AF23C5 /* DWSaveAlertViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSaveAlertViewController.h; sourceTree = ""; }; + C943B2FC2A408CEC00AF23C5 /* DWProfileAboutCellModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWProfileAboutCellModel.m; sourceTree = ""; }; + C943B2FD2A408CEC00AF23C5 /* DWProfileDisplayNameCellModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWProfileDisplayNameCellModel.m; sourceTree = ""; }; + C943B2FF2A408CEC00AF23C5 /* DWTextViewFormCellModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWTextViewFormCellModel.h; sourceTree = ""; }; + C943B3002A408CEC00AF23C5 /* DWTextFieldFormCellModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWTextFieldFormCellModel.m; sourceTree = ""; }; + C943B3012A408CEC00AF23C5 /* DWTextFieldFormCellModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWTextFieldFormCellModel.h; sourceTree = ""; }; + C943B3022A408CEC00AF23C5 /* DWTextViewFormCellModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWTextViewFormCellModel.m; sourceTree = ""; }; + C943B3032A408CEC00AF23C5 /* DWTextInputFormTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWTextInputFormTableViewCell.h; sourceTree = ""; }; + C943B3042A408CEC00AF23C5 /* DWProfileDisplayNameCellModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWProfileDisplayNameCellModel.h; sourceTree = ""; }; + C943B3052A408CEC00AF23C5 /* DWProfileAboutCellModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWProfileAboutCellModel.h; sourceTree = ""; }; + C943B3062A408CEC00AF23C5 /* DWEditProfileBaseCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWEditProfileBaseCell.h; sourceTree = ""; }; + C943B3072A408CEC00AF23C5 /* DWEditProfileTextViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWEditProfileTextViewCell.m; sourceTree = ""; }; + C943B3082A408CEC00AF23C5 /* DWEditProfileAvatarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWEditProfileAvatarView.h; sourceTree = ""; }; + C943B3092A408CEC00AF23C5 /* DWEditProfileTextFieldCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWEditProfileTextFieldCell.h; sourceTree = ""; }; + C943B30A2A408CEC00AF23C5 /* DWEditProfileTextViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWEditProfileTextViewCell.h; sourceTree = ""; }; + C943B30B2A408CEC00AF23C5 /* DWEditProfileBaseCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWEditProfileBaseCell.m; sourceTree = ""; }; + C943B30C2A408CEC00AF23C5 /* DWEditProfileTextFieldCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWEditProfileTextFieldCell.m; sourceTree = ""; }; + C943B30D2A408CEC00AF23C5 /* DWEditProfileAvatarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWEditProfileAvatarView.m; sourceTree = ""; }; + C943B30F2A408CEC00AF23C5 /* DWUploadAvatarViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUploadAvatarViewController.m; sourceTree = ""; }; + C943B3102A408CEC00AF23C5 /* DWUploadAvatarModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUploadAvatarModel.m; sourceTree = ""; }; + C943B3112A408CEC00AF23C5 /* DWHourGlassAnimationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWHourGlassAnimationView.h; sourceTree = ""; }; + C943B3122A408CEC00AF23C5 /* DWUploadAvatarChildView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUploadAvatarChildView.h; sourceTree = ""; }; + C943B3132A408CEC00AF23C5 /* DWUploadAvatarViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUploadAvatarViewController.h; sourceTree = ""; }; + C943B3142A408CEC00AF23C5 /* DWUploadAvatarModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUploadAvatarModel.h; sourceTree = ""; }; + C943B3152A408CEC00AF23C5 /* DWUploadAvatarChildView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUploadAvatarChildView.m; sourceTree = ""; }; + C943B3162A408CEC00AF23C5 /* DWHourGlassAnimationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWHourGlassAnimationView.m; sourceTree = ""; }; + C943B33E2A408E0500AF23C5 /* DWMainMenuViewController+DashPay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWMainMenuViewController+DashPay.h"; sourceTree = ""; }; + C943B33F2A408E5500AF23C5 /* DWMainMenuViewController+DashPay.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DWMainMenuViewController+DashPay.m"; sourceTree = ""; }; + C943B3412A409F9E00AF23C5 /* UIImageView+DWDPAvatar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImageView+DWDPAvatar.h"; sourceTree = ""; }; + C943B3422A409F9E00AF23C5 /* UIImageView+DWDPAvatar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImageView+DWDPAvatar.m"; sourceTree = ""; }; + C943B3442A409FFA00AF23C5 /* DWDPAvatarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPAvatarView.m; sourceTree = ""; }; + C943B3452A409FFA00AF23C5 /* DWDPAvatarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPAvatarView.h; sourceTree = ""; }; + C943B3492A40A4C500AF23C5 /* DWInfoPopupContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInfoPopupContentView.h; sourceTree = ""; }; + C943B34A2A40A4C500AF23C5 /* DWInfoPopupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInfoPopupViewController.m; sourceTree = ""; }; + C943B34B2A40A4C500AF23C5 /* DWInfoPopupContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInfoPopupContentView.m; sourceTree = ""; }; + C943B34C2A40A4C500AF23C5 /* DWInfoPopupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInfoPopupViewController.h; sourceTree = ""; }; + C943B3512A40A54500AF23C5 /* DWBaseContactsViewController+DWProtected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DWBaseContactsViewController+DWProtected.h"; sourceTree = ""; }; + C943B3522A40A54500AF23C5 /* DWContactsPlaceholderViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWContactsPlaceholderViewController.m; sourceTree = ""; }; + C943B3532A40A54500AF23C5 /* DWRootContactsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWRootContactsViewController.h; sourceTree = ""; }; + C943B3542A40A54500AF23C5 /* DWContactsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWContactsViewController.m; sourceTree = ""; }; + C943B3552A40A54500AF23C5 /* DWBaseContactsContentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWBaseContactsContentViewController.m; sourceTree = ""; }; + C943B3572A40A54500AF23C5 /* DWNoContactsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNoContactsViewController.m; sourceTree = ""; }; + C943B3582A40A54500AF23C5 /* DWInvitationSuggestionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationSuggestionView.m; sourceTree = ""; }; + C943B3592A40A54500AF23C5 /* DWNoContactsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNoContactsViewController.h; sourceTree = ""; }; + C943B35A2A40A54500AF23C5 /* DWInvitationSuggestionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationSuggestionView.h; sourceTree = ""; }; + C943B35B2A40A54500AF23C5 /* DWContactsContentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWContactsContentViewController.m; sourceTree = ""; }; + C943B35C2A40A54500AF23C5 /* DWBaseContactsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWBaseContactsViewController.m; sourceTree = ""; }; + C943B35F2A40A54500AF23C5 /* DWContactsDataSourceObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWContactsDataSourceObject.m; sourceTree = ""; }; + C943B3602A40A54500AF23C5 /* DWContactsSearchDataSourceObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWContactsSearchDataSourceObject.m; sourceTree = ""; }; + C943B3612A40A54500AF23C5 /* DWContactsSearchDataSourceObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsSearchDataSourceObject.h; sourceTree = ""; }; + C943B3622A40A54500AF23C5 /* DWContactsDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsDataSource.h; sourceTree = ""; }; + C943B3632A40A54500AF23C5 /* DWContactsDataSourceObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsDataSourceObject.h; sourceTree = ""; }; + C943B3652A40A54500AF23C5 /* DWIncomingFetchedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWIncomingFetchedDataSource.h; sourceTree = ""; }; + C943B3662A40A54500AF23C5 /* DWContactsFetchedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsFetchedDataSource.h; sourceTree = ""; }; + C943B3672A40A54500AF23C5 /* DWFetchedResultsDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWFetchedResultsDataSource.h; sourceTree = ""; }; + C943B3682A40A54500AF23C5 /* DWIncomingFetchedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWIncomingFetchedDataSource.m; sourceTree = ""; }; + C943B3692A40A54600AF23C5 /* DWContactsFetchedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWContactsFetchedDataSource.m; sourceTree = ""; }; + C943B36A2A40A54600AF23C5 /* DWFetchedResultsDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWFetchedResultsDataSource.m; sourceTree = ""; }; + C943B36B2A40A54600AF23C5 /* DWBaseContactsModel+DWProtected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DWBaseContactsModel+DWProtected.h"; sourceTree = ""; }; + C943B36C2A40A54600AF23C5 /* DWBaseContactsModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWBaseContactsModel.h; sourceTree = ""; }; + C943B36D2A40A54600AF23C5 /* DWContactsModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsModel.h; sourceTree = ""; }; + C943B36E2A40A54600AF23C5 /* DWContactsSortModeProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsSortModeProtocol.h; sourceTree = ""; }; + C943B36F2A40A54600AF23C5 /* DWBaseContactsModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWBaseContactsModel.m; sourceTree = ""; }; + C943B3702A40A54600AF23C5 /* DWContactsModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWContactsModel.m; sourceTree = ""; }; + C943B3722A40A54600AF23C5 /* DWRequestsContentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWRequestsContentViewController.m; sourceTree = ""; }; + C943B3742A40A54600AF23C5 /* DWRequestsModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWRequestsModel.m; sourceTree = ""; }; + C943B3752A40A54600AF23C5 /* DWRequestsModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWRequestsModel.h; sourceTree = ""; }; + C943B3762A40A54600AF23C5 /* DWRequestsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWRequestsViewController.h; sourceTree = ""; }; + C943B3772A40A54600AF23C5 /* DWRequestsContentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWRequestsContentViewController.h; sourceTree = ""; }; + C943B3782A40A54600AF23C5 /* DWRequestsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWRequestsViewController.m; sourceTree = ""; }; + C943B3792A40A54600AF23C5 /* DWRootContactsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWRootContactsViewController.m; sourceTree = ""; }; + C943B37A2A40A54600AF23C5 /* DWBaseContactsContentViewController+DWProtected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DWBaseContactsContentViewController+DWProtected.h"; sourceTree = ""; }; + C943B37B2A40A54600AF23C5 /* DWContactsPlaceholderViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsPlaceholderViewController.h; sourceTree = ""; }; + C943B37C2A40A54600AF23C5 /* DWContactsContentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsContentViewController.h; sourceTree = ""; }; + C943B37D2A40A54600AF23C5 /* DWBaseContactsContentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWBaseContactsContentViewController.h; sourceTree = ""; }; + C943B37E2A40A54600AF23C5 /* DWContactsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsViewController.h; sourceTree = ""; }; + C943B3802A40A54600AF23C5 /* DWGlobalMatchHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWGlobalMatchHeaderView.m; sourceTree = ""; }; + C943B3812A40A54600AF23C5 /* DWGlobalMatchFailedHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWGlobalMatchFailedHeaderView.m; sourceTree = ""; }; + C943B3822A40A54600AF23C5 /* DWContactsSearchPlaceholderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWContactsSearchPlaceholderView.m; sourceTree = ""; }; + C943B3832A40A54600AF23C5 /* DWTitleActionHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWTitleActionHeaderView.h; sourceTree = ""; }; + C943B3842A40A54600AF23C5 /* BaseCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseCollectionReusableView.h; sourceTree = ""; }; + C943B3852A40A54600AF23C5 /* DWContactsSearchInfoHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWContactsSearchInfoHeaderView.m; sourceTree = ""; }; + C943B3862A40A54600AF23C5 /* DWGlobalMatchHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWGlobalMatchHeaderView.h; sourceTree = ""; }; + C943B3872A40A54600AF23C5 /* DWTitleActionHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DWTitleActionHeaderView.xib; sourceTree = ""; }; + C943B3882A40A54600AF23C5 /* DWGlobalMatchFailedHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWGlobalMatchFailedHeaderView.h; sourceTree = ""; }; + C943B3892A40A54600AF23C5 /* BaseCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseCollectionReusableView.m; sourceTree = ""; }; + C943B38A2A40A54600AF23C5 /* DWTitleActionHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWTitleActionHeaderView.m; sourceTree = ""; }; + C943B38B2A40A54600AF23C5 /* DWContactsSearchPlaceholderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsSearchPlaceholderView.h; sourceTree = ""; }; + C943B38C2A40A54600AF23C5 /* DWContactsSearchInfoHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWContactsSearchInfoHeaderView.h; sourceTree = ""; }; + C943B38E2A40A54600AF23C5 /* DWSearchViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSearchViewController.h; sourceTree = ""; }; + C943B38F2A40A54600AF23C5 /* DWSearchViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWSearchViewController.m; sourceTree = ""; }; + C943B3902A40A54600AF23C5 /* DWBaseContactsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWBaseContactsViewController.h; sourceTree = ""; }; + C943B3922A40A54600AF23C5 /* DWUserSearchViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserSearchViewController.h; sourceTree = ""; }; + C943B3942A40A54600AF23C5 /* DWUserSearchModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserSearchModel.m; sourceTree = ""; }; + C943B3952A40A54600AF23C5 /* DWUserSearchModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserSearchModel.h; sourceTree = ""; }; + C943B3962A40A54600AF23C5 /* DWUserSearchViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserSearchViewController.m; sourceTree = ""; }; + C943B3982A40A54600AF23C5 /* DWUserSearchResultViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserSearchResultViewController.h; sourceTree = ""; }; + C943B3992A40A54600AF23C5 /* DWSearchStateViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSearchStateViewController.h; sourceTree = ""; }; + C943B39A2A40A54600AF23C5 /* DWUserSearchResultViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserSearchResultViewController.m; sourceTree = ""; }; + C943B39B2A40A54600AF23C5 /* DWSearchStateViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWSearchStateViewController.m; sourceTree = ""; }; + C943B39E2A40A54600AF23C5 /* DWUsernamePendingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUsernamePendingViewController.h; sourceTree = ""; }; + C943B39F2A40A54600AF23C5 /* DWUsernamePendingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUsernamePendingViewController.m; sourceTree = ""; }; + C943B3A12A40A54600AF23C5 /* DWInputUsernameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInputUsernameViewController.h; sourceTree = ""; }; + C943B3A22A40A54600AF23C5 /* DWCreateUsernameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWCreateUsernameViewController.h; sourceTree = ""; }; + C943B3A42A40A54600AF23C5 /* DWUsernameValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUsernameValidationRule.m; sourceTree = ""; }; + C943B3A52A40A54600AF23C5 /* DWLengthUsernameValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWLengthUsernameValidationRule.h; sourceTree = ""; }; + C943B3A62A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAllowedCharactersUsernameValidationRule.m; sourceTree = ""; }; + C943B3A72A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWCheckExistenceUsernameValidationRule.h; sourceTree = ""; }; + C943B3A82A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWFirstUsernameSymbolValidationRule.h; sourceTree = ""; }; + C943B3A92A40A54600AF23C5 /* DWLengthUsernameValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWLengthUsernameValidationRule.m; sourceTree = ""; }; + C943B3AA2A40A54600AF23C5 /* DWUsernameValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUsernameValidationRule.h; sourceTree = ""; }; + C943B3AB2A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAllowedCharactersUsernameValidationRule.h; sourceTree = ""; }; + C943B3AC2A40A54600AF23C5 /* DWUsernameValidationRule+Protected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DWUsernameValidationRule+Protected.h"; sourceTree = ""; }; + C943B3AD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWFirstUsernameSymbolValidationRule.m; sourceTree = ""; }; + C943B3AE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWCheckExistenceUsernameValidationRule.m; sourceTree = ""; }; + C943B3AF2A40A54600AF23C5 /* DWInputUsernameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInputUsernameViewController.m; sourceTree = ""; }; + C943B3B02A40A54600AF23C5 /* DWCreateUsernameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWCreateUsernameViewController.m; sourceTree = ""; }; + C943B3B22A40A54600AF23C5 /* DWUsernameHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUsernameHeaderView.m; sourceTree = ""; }; + C943B3B32A40A54600AF23C5 /* DWPlanetarySystemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWPlanetarySystemView.m; sourceTree = ""; }; + C943B3B42A40A54600AF23C5 /* DWTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWTextField.m; sourceTree = ""; }; + C943B3B52A40A54600AF23C5 /* DWUsernameValidationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUsernameValidationView.h; sourceTree = ""; }; + C943B3B62A40A54600AF23C5 /* DWTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWTextField.h; sourceTree = ""; }; + C943B3B72A40A54600AF23C5 /* DWPlanetarySystemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWPlanetarySystemView.h; sourceTree = ""; }; + C943B3B82A40A54600AF23C5 /* DWUsernameHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUsernameHeaderView.h; sourceTree = ""; }; + C943B3B92A40A54600AF23C5 /* DWUsernameValidationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUsernameValidationView.m; sourceTree = ""; }; + C943B3BB2A40A54600AF23C5 /* DWRegistrationCompletedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWRegistrationCompletedViewController.m; sourceTree = ""; }; + C943B3BC2A40A54600AF23C5 /* DWRegistrationCompletedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWRegistrationCompletedViewController.h; sourceTree = ""; }; + C943B3BE2A40A54600AF23C5 /* DWDashPaySetupFlowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPaySetupFlowController.m; sourceTree = ""; }; + C943B3C02A40A54600AF23C5 /* DWDashPaySetupFlowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDashPaySetupFlowController.h; sourceTree = ""; }; + C943B3C22A40A54600AF23C5 /* DWConfirmUsernameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWConfirmUsernameViewController.m; sourceTree = ""; }; + C943B3C32A40A54600AF23C5 /* DWConfirmUsernameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWConfirmUsernameViewController.h; sourceTree = ""; }; + C943B3C52A40A54600AF23C5 /* DWConfirmUsernameContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWConfirmUsernameContentView.m; sourceTree = ""; }; + C943B3C62A40A54600AF23C5 /* DWConfirmUsernameContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DWConfirmUsernameContentView.xib; sourceTree = ""; }; + C943B3C72A40A54600AF23C5 /* DWConfirmUsernameContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWConfirmUsernameContentView.h; sourceTree = ""; }; + C943B3C82A40A54600AF23C5 /* DWDashPayConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDashPayConstants.h; sourceTree = ""; }; + C943B3CA2A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPWelcomeCollectionViewController.m; sourceTree = ""; }; + C943B3CB2A40A54600AF23C5 /* DWInvitationFlowViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationFlowViewController.m; sourceTree = ""; }; + C943B3CC2A40A54600AF23C5 /* DWDPWelcomeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPWelcomeViewController.m; sourceTree = ""; }; + C943B3CD2A40A54600AF23C5 /* DWDPWelcomePageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPWelcomePageViewController.m; sourceTree = ""; }; + C943B3CF2A40A54600AF23C5 /* DWGetStartedContentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWGetStartedContentViewController.m; sourceTree = ""; }; + C943B3D02A40A54600AF23C5 /* DWGetStarted.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWGetStarted.h; sourceTree = ""; }; + C943B3D12A40A54600AF23C5 /* DWGetStartedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWGetStartedViewController.m; sourceTree = ""; }; + C943B3D22A40A54600AF23C5 /* DWGetStartedContentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWGetStartedContentViewController.h; sourceTree = ""; }; + C943B3D32A40A54600AF23C5 /* DWGetStartedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWGetStartedViewController.h; sourceTree = ""; }; + C943B3D52A40A54600AF23C5 /* DWGetStartedItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWGetStartedItemView.h; sourceTree = ""; }; + C943B3D62A40A54600AF23C5 /* DWGetStartedItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWGetStartedItemView.m; sourceTree = ""; }; + C943B3D72A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPWelcomeCollectionViewController.h; sourceTree = ""; }; + C943B3D82A40A54600AF23C5 /* DWInvitationFlowViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationFlowViewController.h; sourceTree = ""; }; + C943B3D92A40A54600AF23C5 /* DWDPWelcomePageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPWelcomePageViewController.h; sourceTree = ""; }; + C943B3DA2A40A54600AF23C5 /* DWDPWelcomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPWelcomeViewController.h; sourceTree = ""; }; + C943B3DC2A40A54600AF23C5 /* DWPassthroughStackView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWPassthroughStackView.h; sourceTree = ""; }; + C943B3DD2A40A54600AF23C5 /* DWPassthroughView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWPassthroughView.m; sourceTree = ""; }; + C943B3DE2A40A54600AF23C5 /* DWPassthroughStackView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWPassthroughStackView.m; sourceTree = ""; }; + C943B3DF2A40A54600AF23C5 /* DWPassthroughView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWPassthroughView.h; sourceTree = ""; }; + C943B3E92A40A54600AF23C5 /* DWUserProfileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileViewController.h; sourceTree = ""; }; + C943B3EA2A40A54600AF23C5 /* DWModalUserProfileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWModalUserProfileViewController.h; sourceTree = ""; }; + C943B3ED2A40A54600AF23C5 /* DWUserProfileDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileDataSource.h; sourceTree = ""; }; + C943B3EE2A40A54600AF23C5 /* DWUserProfileDataSourceObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileDataSourceObject.m; sourceTree = ""; }; + C943B3EF2A40A54600AF23C5 /* DWProfileTxsFetchedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWProfileTxsFetchedDataSource.m; sourceTree = ""; }; + C943B3F02A40A54600AF23C5 /* DWUserProfileDataSourceObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileDataSourceObject.h; sourceTree = ""; }; + C943B3F12A40A54600AF23C5 /* DWProfileTxsFetchedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWProfileTxsFetchedDataSource.h; sourceTree = ""; }; + C943B3F22A40A54600AF23C5 /* DWUserProfileModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileModel.h; sourceTree = ""; }; + C943B3F32A40A54600AF23C5 /* DWUserProfileModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileModel.m; sourceTree = ""; }; + C943B3F42A40A54600AF23C5 /* DWUserProfileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileViewController.m; sourceTree = ""; }; + C943B3F52A40A54600AF23C5 /* DWModalUserProfileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWModalUserProfileViewController.m; sourceTree = ""; }; + C943B3F72A40A54600AF23C5 /* DWStretchyHeaderListCollectionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWStretchyHeaderListCollectionLayout.h; sourceTree = ""; }; + C943B3F82A40A54600AF23C5 /* DWUserProfileSendRequestCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileSendRequestCell.m; sourceTree = ""; }; + C943B3F92A40A54600AF23C5 /* DWUserProfileHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileHeaderView.h; sourceTree = ""; }; + C943B3FA2A40A54600AF23C5 /* DWPendingContactInfoView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWPendingContactInfoView.m; sourceTree = ""; }; + C943B3FB2A40A54600AF23C5 /* DWUserProfileContactActionsCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileContactActionsCell.h; sourceTree = ""; }; + C943B3FC2A40A54600AF23C5 /* DWUserProfileNavigationTitleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileNavigationTitleView.h; sourceTree = ""; }; + C943B3FD2A40A54600AF23C5 /* DWStretchyHeaderListCollectionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWStretchyHeaderListCollectionLayout.m; sourceTree = ""; }; + C943B3FE2A40A54600AF23C5 /* DWUserProfileHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileHeaderView.m; sourceTree = ""; }; + C943B3FF2A40A54600AF23C5 /* DWUserProfileSendRequestCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUserProfileSendRequestCell.h; sourceTree = ""; }; + C943B4002A40A54600AF23C5 /* DWPendingContactInfoView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWPendingContactInfoView.h; sourceTree = ""; }; + C943B4012A40A54600AF23C5 /* DWUserProfileNavigationTitleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileNavigationTitleView.m; sourceTree = ""; }; + C943B4022A40A54600AF23C5 /* DWUserProfileContactActionsCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserProfileContactActionsCell.m; sourceTree = ""; }; + C943B4052A40A54600AF23C5 /* DWDPNewIncomingRequestObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPNewIncomingRequestObject.m; sourceTree = ""; }; + C943B4062A40A54600AF23C5 /* DWDPContactObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPContactObject.h; sourceTree = ""; }; + C943B4072A40A54600AF23C5 /* DWDPEstablishedContactObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPEstablishedContactObject.m; sourceTree = ""; }; + C943B4082A40A54600AF23C5 /* DWDPPendingRequestObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPPendingRequestObject.h; sourceTree = ""; }; + C943B4092A40A54600AF23C5 /* DWDPUserObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPUserObject.m; sourceTree = ""; }; + C943B40A2A40A54600AF23C5 /* DWDPTxObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPTxObject.h; sourceTree = ""; }; + C943B40B2A40A54600AF23C5 /* DWDPRespondedIncomingRequestObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPRespondedIncomingRequestObject.h; sourceTree = ""; }; + C943B40C2A40A54600AF23C5 /* DWDPIncomingRequestObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPIncomingRequestObject.m; sourceTree = ""; }; + C943B40D2A40A54600AF23C5 /* DWDPContactObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPContactObject.m; sourceTree = ""; }; + C943B40E2A40A54600AF23C5 /* DWDPNewIncomingRequestObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPNewIncomingRequestObject.h; sourceTree = ""; }; + C943B40F2A40A54600AF23C5 /* DWDPEstablishedContactObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPEstablishedContactObject.h; sourceTree = ""; }; + C943B4102A40A54600AF23C5 /* DWDPTxObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPTxObject.m; sourceTree = ""; }; + C943B4112A40A54600AF23C5 /* DWDPUserObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPUserObject.h; sourceTree = ""; }; + C943B4122A40A54600AF23C5 /* DWDPPendingRequestObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPPendingRequestObject.m; sourceTree = ""; }; + C943B4132A40A54600AF23C5 /* DWDPIncomingRequestObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPIncomingRequestObject.h; sourceTree = ""; }; + C943B4152A40A54600AF23C5 /* DWDPAcceptedRequestNotificationObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPAcceptedRequestNotificationObject.m; sourceTree = ""; }; + C943B4162A40A54600AF23C5 /* DWDPOutgoingRequestNotificationObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPOutgoingRequestNotificationObject.m; sourceTree = ""; }; + C943B4172A40A54600AF23C5 /* DWDPNewIncomingRequestNotificationObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPNewIncomingRequestNotificationObject.m; sourceTree = ""; }; + C943B4182A40A54600AF23C5 /* DWDPEstablishedContactNotificationObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPEstablishedContactNotificationObject.h; sourceTree = ""; }; + C943B4192A40A54600AF23C5 /* DWDPAcceptedRequestNotificationObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPAcceptedRequestNotificationObject.h; sourceTree = ""; }; + C943B41A2A40A54600AF23C5 /* DWDPOutgoingRequestNotificationObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPOutgoingRequestNotificationObject.h; sourceTree = ""; }; + C943B41B2A40A54600AF23C5 /* DWDPEstablishedContactNotificationObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPEstablishedContactNotificationObject.m; sourceTree = ""; }; + C943B41C2A40A54600AF23C5 /* DWDPNewIncomingRequestNotificationObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPNewIncomingRequestNotificationObject.h; sourceTree = ""; }; + C943B41D2A40A54600AF23C5 /* DWDPRespondedIncomingRequestObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPRespondedIncomingRequestObject.m; sourceTree = ""; }; + C943B41F2A40A54600AF23C5 /* DWDPTextStatusCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPTextStatusCell.m; sourceTree = ""; }; + C943B4212A40A54600AF23C5 /* DWDPGenericStatusItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPGenericStatusItemView.h; sourceTree = ""; }; + C943B4222A40A54600AF23C5 /* DWDPGenericContactRequestItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPGenericContactRequestItemView.m; sourceTree = ""; }; + C943B4232A40A54600AF23C5 /* DWDPTxItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPTxItemView.m; sourceTree = ""; }; + C943B4242A40A54600AF23C5 /* DWDPGenericImageItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPGenericImageItemView.h; sourceTree = ""; }; + C943B4252A40A54600AF23C5 /* DWDPGenericItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPGenericItemView.m; sourceTree = ""; }; + C943B4262A40A54600AF23C5 /* DWDPGenericContactRequestItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPGenericContactRequestItemView.h; sourceTree = ""; }; + C943B4272A40A54600AF23C5 /* DWDPGenericStatusItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPGenericStatusItemView.m; sourceTree = ""; }; + C943B4282A40A54600AF23C5 /* DWDPGenericImageItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPGenericImageItemView.m; sourceTree = ""; }; + C943B4292A40A54600AF23C5 /* DWDPTxItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPTxItemView.h; sourceTree = ""; }; + C943B42A2A40A54600AF23C5 /* DWDPGenericItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPGenericItemView.h; sourceTree = ""; }; + C943B42B2A40A54600AF23C5 /* DWDPBasicCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPBasicCell.h; sourceTree = ""; }; + C943B42C2A40A54600AF23C5 /* DWDPTxListCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPTxListCell.h; sourceTree = ""; }; + C943B42D2A40A54600AF23C5 /* DWDPImageStatusCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPImageStatusCell.m; sourceTree = ""; }; + C943B42E2A40A54600AF23C5 /* DWDPIncomingRequestCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPIncomingRequestCell.m; sourceTree = ""; }; + C943B42F2A40A54600AF23C5 /* UIFont+DWDPItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+DWDPItem.h"; sourceTree = ""; }; + C943B4302A40A54600AF23C5 /* UICollectionView+DWDPItemDequeue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+DWDPItemDequeue.m"; sourceTree = ""; }; + C943B4312A40A54600AF23C5 /* DWDPBasicCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPBasicCell.m; sourceTree = ""; }; + C943B4322A40A54600AF23C5 /* DWDPTextStatusCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPTextStatusCell.h; sourceTree = ""; }; + C943B4332A40A54600AF23C5 /* DWDPTxListCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPTxListCell.m; sourceTree = ""; }; + C943B4342A40A54600AF23C5 /* UICollectionView+DWDPItemDequeue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+DWDPItemDequeue.h"; sourceTree = ""; }; + C943B4352A40A54600AF23C5 /* UIFont+DWDPItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+DWDPItem.m"; sourceTree = ""; }; + C943B4362A40A54600AF23C5 /* DWDPIncomingRequestCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPIncomingRequestCell.h; sourceTree = ""; }; + C943B4372A40A54600AF23C5 /* DWDPImageStatusCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPImageStatusCell.h; sourceTree = ""; }; + C943B4392A40A54600AF23C5 /* DWDPBasicItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPBasicItem.h; sourceTree = ""; }; + C943B43A2A40A54600AF23C5 /* DWDPNewIncomingRequestItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPNewIncomingRequestItem.h; sourceTree = ""; }; + C943B43B2A40A54600AF23C5 /* DWDPPendingRequestItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPPendingRequestItem.h; sourceTree = ""; }; + C943B43D2A40A54600AF23C5 /* DWDPFriendRequestBackedItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPFriendRequestBackedItem.h; sourceTree = ""; }; + C943B43E2A40A54600AF23C5 /* DWDPBlockchainIdentityBackedItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPBlockchainIdentityBackedItem.h; sourceTree = ""; }; + C943B43F2A40A54600AF23C5 /* DWDPDashpayUserBackedItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPDashpayUserBackedItem.h; sourceTree = ""; }; + C943B4402A40A54600AF23C5 /* DWDPNotificationItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPNotificationItem.h; sourceTree = ""; }; + C943B4412A40A54600AF23C5 /* DWDPEstablishedContactItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPEstablishedContactItem.h; sourceTree = ""; }; + C943B4422A40A54600AF23C5 /* DWDPTxItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPTxItem.h; sourceTree = ""; }; + C943B4432A40A54600AF23C5 /* DWDPRespondedRequestItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPRespondedRequestItem.h; sourceTree = ""; }; + C943B4442A40A54600AF23C5 /* DWDPBasicUserItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPBasicUserItem.h; sourceTree = ""; }; + C943B4452A40A54600AF23C5 /* DWDPIncomingRequestItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPIncomingRequestItem.h; sourceTree = ""; }; + C943B4472A40A54600AF23C5 /* DWDPSearchItemsFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPSearchItemsFactory.h; sourceTree = ""; }; + C943B4482A40A54600AF23C5 /* DWDPContactsItemsFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPContactsItemsFactory.m; sourceTree = ""; }; + C943B4492A40A54600AF23C5 /* DWDPSearchItemsFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPSearchItemsFactory.m; sourceTree = ""; }; + C943B44A2A40A54600AF23C5 /* DWDPContactsItemsFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPContactsItemsFactory.h; sourceTree = ""; }; + C943B44C2A40A54600AF23C5 /* DWSendInviteFirstStepViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSendInviteFirstStepViewController.h; sourceTree = ""; }; + C943B44E2A40A54600AF23C5 /* DPAlertViewController+DWInvite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DPAlertViewController+DWInvite.h"; sourceTree = ""; }; + C943B44F2A40A54600AF23C5 /* DPAlertViewController+DWInvite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "DPAlertViewController+DWInvite.m"; sourceTree = ""; }; + C943B4502A40A54600AF23C5 /* DWSendInviteFlowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSendInviteFlowController.h; sourceTree = ""; }; + C943B4522A40A54600AF23C5 /* DWConfirmInvitationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWConfirmInvitationViewController.m; sourceTree = ""; }; + C943B4532A40A54600AF23C5 /* DWConfirmInvitationContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWConfirmInvitationContentView.m; sourceTree = ""; }; + C943B4542A40A54600AF23C5 /* DWConfirmInvitationContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWConfirmInvitationContentView.h; sourceTree = ""; }; + C943B4552A40A54600AF23C5 /* DWConfirmInvitationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWConfirmInvitationViewController.h; sourceTree = ""; }; + C943B4562A40A54600AF23C5 /* DWConfirmInvitationContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DWConfirmInvitationContentView.xib; sourceTree = ""; }; + C943B4572A40A54600AF23C5 /* DWSendInviteFirstStepViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWSendInviteFirstStepViewController.m; sourceTree = ""; }; + C943B4592A40A54600AF23C5 /* DWInvitationHistoryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationHistoryViewController.m; sourceTree = ""; }; + C943B45B2A40A54600AF23C5 /* DWInvitationHistoryFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationHistoryFilter.h; sourceTree = ""; }; + C943B45C2A40A54600AF23C5 /* DWInvitationHistoryModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationHistoryModel.m; sourceTree = ""; }; + C943B45D2A40A54600AF23C5 /* DWInvitationHistoryModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationHistoryModel.h; sourceTree = ""; }; + C943B45E2A40A54600AF23C5 /* DWInvitationItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationItem.h; sourceTree = ""; }; + C943B4602A40A54600AF23C5 /* DWHistoryFilterContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWHistoryFilterContentView.m; sourceTree = ""; }; + C943B4612A40A54600AF23C5 /* DWHistoryFilterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWHistoryFilterViewController.m; sourceTree = ""; }; + C943B4622A40A54600AF23C5 /* DWHistoryFilterContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWHistoryFilterContentView.h; sourceTree = ""; }; + C943B4632A40A54600AF23C5 /* DWHistoryFilterViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWHistoryFilterViewController.h; sourceTree = ""; }; + C943B4652A40A54600AF23C5 /* DWHistoryHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWHistoryHeaderView.h; sourceTree = ""; }; + C943B4662A40A54600AF23C5 /* DWInvitationTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationTableViewCell.m; sourceTree = ""; }; + C943B4672A40A54600AF23C5 /* DWCreateInvitationButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWCreateInvitationButton.m; sourceTree = ""; }; + C943B4682A40A54600AF23C5 /* DWInvitationTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationTableViewCell.h; sourceTree = ""; }; + C943B4692A40A54600AF23C5 /* DWHistoryHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWHistoryHeaderView.m; sourceTree = ""; }; + C943B46A2A40A54600AF23C5 /* DWCreateInvitationButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWCreateInvitationButton.h; sourceTree = ""; }; + C943B46B2A40A54600AF23C5 /* DWInvitationHistoryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationHistoryViewController.h; sourceTree = ""; }; + C943B46D2A40A54600AF23C5 /* DWInvitationPreviewViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationPreviewViewController.h; sourceTree = ""; }; + C943B46E2A40A54600AF23C5 /* DWInvitationPreviewViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationPreviewViewController.m; sourceTree = ""; }; + C943B46F2A40A54600AF23C5 /* DWSendInviteFlowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWSendInviteFlowController.m; sourceTree = ""; }; + C943B4712A40A54600AF23C5 /* DWInvitationLinkBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationLinkBuilder.m; sourceTree = ""; }; + C943B4722A40A54600AF23C5 /* BaseInvitationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseInvitationViewController.swift; sourceTree = ""; }; + C943B4732A40A54600AF23C5 /* SuccessInvitationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuccessInvitationViewController.swift; sourceTree = ""; }; + C943B4742A40A54600AF23C5 /* DWInvitationLinkBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationLinkBuilder.h; sourceTree = ""; }; + C943B4762A40A54600AF23C5 /* InvitationBottomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvitationBottomView.swift; sourceTree = ""; }; + C943B4772A40A54600AF23C5 /* InvitationTopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvitationTopView.swift; sourceTree = ""; }; + C943B4782A40A54600AF23C5 /* DWSuccessInvitationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWSuccessInvitationView.m; sourceTree = ""; }; + C943B4792A40A54600AF23C5 /* DWInvitationMessageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationMessageView.h; sourceTree = ""; }; + C943B47A2A40A54600AF23C5 /* DWInvitationActionsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationActionsView.m; sourceTree = ""; }; + C943B47B2A40A54600AF23C5 /* DWSuccessInvitationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSuccessInvitationView.h; sourceTree = ""; }; + C943B47C2A40A54600AF23C5 /* SuccessInvitationTopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuccessInvitationTopView.swift; sourceTree = ""; }; + C943B47D2A40A54600AF23C5 /* DWInvitationMessageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationMessageView.m; sourceTree = ""; }; + C943B47E2A40A54600AF23C5 /* DWInvitationActionsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInvitationActionsView.h; sourceTree = ""; }; + C943B4802A40A54600AF23C5 /* DWNetworkUnavailableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNetworkUnavailableView.m; sourceTree = ""; }; + C943B4812A40A54600AF23C5 /* UIColor+DWDashPay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+DWDashPay.m"; sourceTree = ""; }; + C943B4822A40A54600AF23C5 /* DWDPSmallContactView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPSmallContactView.h; sourceTree = ""; }; + C943B4842A40A54600AF23C5 /* DWDashPayAnimationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDashPayAnimationView.h; sourceTree = ""; }; + C943B4852A40A54600AF23C5 /* UIColor+DWDashPay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+DWDashPay.h"; sourceTree = ""; }; + C943B4862A40A54600AF23C5 /* DWNetworkUnavailableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNetworkUnavailableView.h; sourceTree = ""; }; + C943B4872A40A54600AF23C5 /* DWDPSmallContactView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPSmallContactView.m; sourceTree = ""; }; + C943B4882A40A54600AF23C5 /* DWDashPayAnimationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPayAnimationView.m; sourceTree = ""; }; + C943B48B2A40A54600AF23C5 /* DWNetworkErrorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNetworkErrorViewController.m; sourceTree = ""; }; + C943B48C2A40A54600AF23C5 /* DWNetworkErrorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNetworkErrorViewController.h; sourceTree = ""; }; + C943B48D2A40A54600AF23C5 /* DWDashPayConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPayConstants.m; sourceTree = ""; }; + C943B48F2A40A54600AF23C5 /* UIImageView+DWDPAvatar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImageView+DWDPAvatar.m"; sourceTree = ""; }; + C943B4902A40A54600AF23C5 /* DSBlockchainIdentity+DWDisplayName.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DSBlockchainIdentity+DWDisplayName.h"; sourceTree = ""; }; + C943B4912A40A54600AF23C5 /* DWDashPayContactsActions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPayContactsActions.m; sourceTree = ""; }; + C943B4922A40A54600AF23C5 /* DWDashPayContactsUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDashPayContactsUpdater.h; sourceTree = ""; }; + C943B4932A40A54600AF23C5 /* DSBlockchainIdentity+DWDisplayName.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "DSBlockchainIdentity+DWDisplayName.m"; sourceTree = ""; }; + C943B4942A40A54600AF23C5 /* UIImageView+DWDPAvatar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImageView+DWDPAvatar.h"; sourceTree = ""; }; + C943B4952A40A54600AF23C5 /* DWDashPayContactsActions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDashPayContactsActions.h; sourceTree = ""; }; + C943B4962A40A54600AF23C5 /* DWDashPayContactsUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPayContactsUpdater.m; sourceTree = ""; }; + C943B4982A40A54600AF23C5 /* DWNotificationsProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsProvider.m; sourceTree = ""; }; + C943B4992A40A54600AF23C5 /* DWNotificationsData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsData.m; sourceTree = ""; }; + C943B49A2A40A54600AF23C5 /* DWNotificationsFetchedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNotificationsFetchedDataSource.h; sourceTree = ""; }; + C943B49B2A40A54600AF23C5 /* DWNotificationsProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNotificationsProvider.h; sourceTree = ""; }; + C943B49C2A40A54600AF23C5 /* DWNotificationsFetchedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsFetchedDataSource.m; sourceTree = ""; }; + C943B49D2A40A54600AF23C5 /* DWNotificationsData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNotificationsData.h; sourceTree = ""; }; + C943B4A02A40A54600AF23C5 /* DWNotificationsInvitationCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNotificationsInvitationCell.h; sourceTree = ""; }; + C943B4A12A40A54600AF23C5 /* DWNoNotificationsCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNoNotificationsCell.m; sourceTree = ""; }; + C943B4A22A40A54600AF23C5 /* DWNotificationsInvitationCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsInvitationCell.m; sourceTree = ""; }; + C943B4A32A40A54600AF23C5 /* DWNoNotificationsCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNoNotificationsCell.h; sourceTree = ""; }; + C943B4A42A40A54600AF23C5 /* DWListCollectionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWListCollectionLayout.h; sourceTree = ""; }; + C943B4A52A40A54600AF23C5 /* DWNotificationsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNotificationsViewController.h; sourceTree = ""; }; + C943B4A72A40A54600AF23C5 /* DWNotificationsModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsModel.m; sourceTree = ""; }; + C943B4A82A40A54600AF23C5 /* DWNotificationsModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNotificationsModel.h; sourceTree = ""; }; + C943B4A92A40A54600AF23C5 /* DWListCollectionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWListCollectionLayout.m; sourceTree = ""; }; + C943B4AA2A40A54600AF23C5 /* DWNotificationsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNotificationsViewController.m; sourceTree = ""; }; + C943B5362A40A65B00AF23C5 /* DWScrollingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWScrollingViewController.m; sourceTree = ""; }; + C943B5372A40A65B00AF23C5 /* DWScrollingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWScrollingViewController.h; sourceTree = ""; }; + C943B53A2A40A6BE00AF23C5 /* DPAlertChildContentsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DPAlertChildContentsView.h; sourceTree = ""; }; + C943B53B2A40A6BE00AF23C5 /* DPAlertViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DPAlertViewController.m; sourceTree = ""; }; + C943B53C2A40A6BE00AF23C5 /* DPAlertChildContentsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DPAlertChildContentsView.m; sourceTree = ""; }; + C943B53D2A40A6BE00AF23C5 /* DPAlertViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DPAlertViewController.h; sourceTree = ""; }; + C943B5412A40AFD000AF23C5 /* DWTxDetailPopupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWTxDetailPopupViewController.h; sourceTree = ""; }; + C943B5422A40AFD100AF23C5 /* DWTxDetailPopupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWTxDetailPopupViewController.m; sourceTree = ""; }; + C943B5442A40B4AF00AF23C5 /* DWDashPayReadyProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPayReadyProtocol.h; sourceTree = ""; }; + C943B5462A40B52F00AF23C5 /* NSLayoutConstraint+DWAutolayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSLayoutConstraint+DWAutolayout.m"; sourceTree = ""; }; + C943B5472A40B52F00AF23C5 /* NSLayoutConstraint+DWAutolayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSLayoutConstraint+DWAutolayout.h"; sourceTree = ""; }; + C943B5482A40B52F00AF23C5 /* UIView+DWAutolayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+DWAutolayout.m"; sourceTree = ""; }; + C943B5492A40B52F00AF23C5 /* UIView+DWAutolayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+DWAutolayout.h"; sourceTree = ""; }; + C943B54C2A40B6B500AF23C5 /* DWColoredButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWColoredButton.m; sourceTree = ""; }; + C943B54D2A40B6B500AF23C5 /* DWColoredButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWColoredButton.h; sourceTree = ""; }; + C943B5522A40C23500AF23C5 /* DWFilterHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DWFilterHeaderView.xib; sourceTree = ""; }; + C943B5562A40DA3600AF23C5 /* DWFullScreenModalControllerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWFullScreenModalControllerViewController.h; sourceTree = ""; }; + C943B5572A40DA3700AF23C5 /* DWFullScreenModalControllerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWFullScreenModalControllerViewController.m; sourceTree = ""; }; + C943B5592A40DD3F00AF23C5 /* NSArray+DWFlatten.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+DWFlatten.h"; sourceTree = ""; }; + C943B55A2A40DD4000AF23C5 /* NSArray+DWFlatten.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+DWFlatten.m"; sourceTree = ""; }; + C943B55C2A40E6F200AF23C5 /* DWFilterHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWFilterHeaderView.m; sourceTree = ""; }; + C943B55D2A40E6F200AF23C5 /* DWFilterHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWFilterHeaderView.h; 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 = ""; }; @@ -2649,8 +2850,6 @@ C9D2C9642A38733B00D15901 /* DPAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DPAssets.xcassets; sourceTree = ""; }; C9D2C9672A3875BA00D15901 /* DWCurrentUserProfileModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWCurrentUserProfileModel.m; sourceTree = ""; }; C9D2C9682A3875BA00D15901 /* DWCurrentUserProfileModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWCurrentUserProfileModel.h; sourceTree = ""; }; - C9D2C96A2A38762700D15901 /* DWDPUpdateProfileModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDPUpdateProfileModel.h; sourceTree = ""; }; - C9D2C96B2A38762800D15901 /* DWDPUpdateProfileModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPUpdateProfileModel.m; sourceTree = ""; }; C9D2C96F2A38778E00D15901 /* DWDashPaySetupModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDashPaySetupModel.h; sourceTree = ""; }; C9D2C9702A38778E00D15901 /* DWDashPaySetupModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPaySetupModel.m; sourceTree = ""; }; C9F067F129E4576D0022D958 /* HomeBalanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeBalanceView.swift; sourceTree = ""; }; @@ -3285,21 +3484,6 @@ path = "Syncing Views"; sourceTree = ""; }; - 2A1AE78B23F463FB00179A6E /* Views */ = { - isa = PBXGroup; - children = ( - 2A1AE79023F468CD00179A6E /* DWPlanetarySystemView.h */, - 2A1AE79123F468CD00179A6E /* DWPlanetarySystemView.m */, - 2A1AE78C23F4668B00179A6E /* DWUsernameHeaderView.h */, - 2A1AE78D23F4668B00179A6E /* DWUsernameHeaderView.m */, - 2A9E7DC523F6928C00CDA1EE /* DWTextField.h */, - 2A9E7DC623F6928C00CDA1EE /* DWTextField.m */, - 2A9E7DC823F6BD5200CDA1EE /* DWUsernameValidationView.h */, - 2A9E7DC923F6BD5200CDA1EE /* DWUsernameValidationView.m */, - ); - path = Views; - sourceTree = ""; - }; 2A1B7D7E232151B800BA8C6A /* Tx */ = { isa = PBXGroup; children = ( @@ -3414,15 +3598,6 @@ path = Views; sourceTree = ""; }; - 2A3CCEF6242BB0AF00300AF8 /* RegistrationCompleted */ = { - isa = PBXGroup; - children = ( - 2A3CCEF7242BB1B900300AF8 /* DWRegistrationCompletedViewController.h */, - 2A3CCEF8242BB1B900300AF8 /* DWRegistrationCompletedViewController.m */, - ); - path = RegistrationCompleted; - sourceTree = ""; - }; 2A4430E922CBB98D009BAF7F /* Setup */ = { isa = PBXGroup; children = ( @@ -3574,6 +3749,7 @@ 2A44313C22CF631E009BAF7F /* UI */ = { isa = PBXGroup; children = ( + C943B34F2A40A54500AF23C5 /* DashPay */, 4751137228DAF27300223B77 /* Assembly */, C9F42FA729DC09C6001BC549 /* Style */, 11C5F51128E5D0C500F6F135 /* CrowdNode */, @@ -3581,7 +3757,6 @@ 4751136A28D9A3BE00223B77 /* Portal */, 0F6EDFE028C8AE32000427E7 /* Coinbase */, 47AE8BB328C1305E00490F5E /* Explore Dash */, - 2AE8B63D23CDB94E0016F221 /* DashPay */, 2AB231CF2196E23200A6E7E6 /* Start */, 2A913E6C23A26469006A2A59 /* Onboarding */, 2A7A7BAF2347917800451078 /* Menu */, @@ -3703,15 +3878,15 @@ 2A4E533C22F025ED00E5168A /* Cells */ = { isa = PBXGroup; children = ( + C943B55D2A40E6F200AF23C5 /* DWFilterHeaderView.h */, + C943B55C2A40E6F200AF23C5 /* DWFilterHeaderView.m */, + 2A4E535322F1D0D900E5168A /* TxListTableViewCell.xib */, C9F451F22A0C933700825057 /* SyncingHeaderView.swift */, 2A5E4544243E0595006BA067 /* RegistrationStatus */, C9F452022A0CEB5800825057 /* TxListEmptyTableViewCell.swift */, 2A4E533F22F025FE00E5168A /* TxListEmptyTableViewCell.xib */, - 2A4E534922F03A9E00E5168A /* DWFilterHeaderView.h */, - 2A4E534A22F03A9E00E5168A /* DWFilterHeaderView.m */, 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */, 474C7219298A803200475CA6 /* TxListTableViewCell.swift */, - 2A4E535322F1D0D900E5168A /* TxListTableViewCell.xib */, ); path = Cells; sourceTree = ""; @@ -3932,155 +4107,6 @@ path = Cells; sourceTree = ""; }; - 2A7AF3192480E619001D74F9 /* Models */ = { - isa = PBXGroup; - children = ( - 2A7AF31A2480E6AD001D74F9 /* DWNotificationsModel.h */, - 2A7AF31B2480E6AD001D74F9 /* DWNotificationsModel.m */, - ); - path = Models; - sourceTree = ""; - }; - 2A7AF33D24815BCA001D74F9 /* Fetched DataSource */ = { - isa = PBXGroup; - children = ( - C3DAD26D246D46BF0001624F /* DWFetchedResultsDataSource.h */, - C3DAD26E246D46BF0001624F /* DWFetchedResultsDataSource.m */, - 2A7AF3132480DA51001D74F9 /* DWIncomingFetchedDataSource.h */, - 2A7AF3142480DA51001D74F9 /* DWIncomingFetchedDataSource.m */, - 2A7AF3162480E35A001D74F9 /* DWContactsFetchedDataSource.h */, - 2A7AF3172480E35A001D74F9 /* DWContactsFetchedDataSource.m */, - ); - path = "Fetched DataSource"; - sourceTree = ""; - }; - 2A7AF3412481A33C001D74F9 /* Views */ = { - isa = PBXGroup; - children = ( - 2A7AF34D2482369D001D74F9 /* ContentViews */, - 2A7AF34E2482374D001D74F9 /* DWDPBasicCell.h */, - 2A7AF34F2482374D001D74F9 /* DWDPBasicCell.m */, - 2A7AF35124823EC8001D74F9 /* DWDPIncomingRequestCell.h */, - 2A7AF35224823EC8001D74F9 /* DWDPIncomingRequestCell.m */, - 2A7AF35424824F97001D74F9 /* DWDPImageStatusCell.h */, - 2A7AF35524824F97001D74F9 /* DWDPImageStatusCell.m */, - 2A7AF35724824FEB001D74F9 /* DWDPTextStatusCell.h */, - 2A7AF35824824FEB001D74F9 /* DWDPTextStatusCell.m */, - 2ACCA3A624BE0C7D00DB32DE /* DWDPTxListCell.h */, - 2ACCA3A724BE0C7D00DB32DE /* DWDPTxListCell.m */, - 2A7AF38724829AF2001D74F9 /* UICollectionView+DWDPItemDequeue.h */, - 2A7AF38824829AF2001D74F9 /* UICollectionView+DWDPItemDequeue.m */, - 2A7AF38D2482BDF1001D74F9 /* UIFont+DWDPItem.h */, - 2A7AF38E2482BDF1001D74F9 /* UIFont+DWDPItem.m */, - ); - path = Views; - sourceTree = ""; - }; - 2A7AF34D2482369D001D74F9 /* ContentViews */ = { - isa = PBXGroup; - children = ( - 2A7AF33E2481A1B2001D74F9 /* DWDPGenericItemView.h */, - 2A7AF33F2481A1B2001D74F9 /* DWDPGenericItemView.m */, - 2A7AF34224822AE0001D74F9 /* DWDPGenericContactRequestItemView.h */, - 2A7AF34324822AE0001D74F9 /* DWDPGenericContactRequestItemView.m */, - 2A7AF34724823167001D74F9 /* DWDPGenericImageItemView.h */, - 2A7AF34824823167001D74F9 /* DWDPGenericImageItemView.m */, - 2A7AF34A24823315001D74F9 /* DWDPGenericStatusItemView.h */, - 2A7AF34B24823315001D74F9 /* DWDPGenericStatusItemView.m */, - 2A6688FB24BCB739008E10F0 /* DWDPTxItemView.h */, - 2A6688FC24BCB739008E10F0 /* DWDPTxItemView.m */, - ); - path = ContentViews; - sourceTree = ""; - }; - 2A7AF35A248256EB001D74F9 /* Protocols */ = { - isa = PBXGroup; - children = ( - 2A7AF3902482D98C001D74F9 /* Items Associated Data */, - 2A6688FA24BCB3AC008E10F0 /* DWDPBasicItem.h */, - 2A7AF35B2482577B001D74F9 /* DWDPBasicUserItem.h */, - 2A7AF35F24825991001D74F9 /* DWDPEstablishedContactItem.h */, - 2A7AF35C24825860001D74F9 /* DWDPIncomingRequestItem.h */, - 2A9D72A9249A0E7800F79CD8 /* DWDPNewIncomingRequestItem.h */, - 2AE56473249B675500CC2E80 /* DWDPNotificationItem.h */, - 2A7AF35E2482592F001D74F9 /* DWDPPendingRequestItem.h */, - 2A7AF35D248258C9001D74F9 /* DWDPRespondedRequestItem.h */, - 2ACCA3A924BE0D3600DB32DE /* DWDPTxItem.h */, - ); - path = Protocols; - sourceTree = ""; - }; - 2A7AF360248259C6001D74F9 /* Objects */ = { - isa = PBXGroup; - children = ( - 2A7AF37C2482759F001D74F9 /* Notifications */, - 2A7AF36D24826737001D74F9 /* DWDPContactObject.h */, - 2A7AF36E24826737001D74F9 /* DWDPContactObject.m */, - 2A7AF36A248266FB001D74F9 /* DWDPEstablishedContactObject.h */, - 2A7AF36B248266FB001D74F9 /* DWDPEstablishedContactObject.m */, - 2A7AF3642482666C001D74F9 /* DWDPIncomingRequestObject.h */, - 2A7AF3652482666C001D74F9 /* DWDPIncomingRequestObject.m */, - 2A9D72AA249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.h */, - 2A9D72AB249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.m */, - 2A7AF3792482756D001D74F9 /* DWDPPendingRequestObject.h */, - 2A7AF37A2482756D001D74F9 /* DWDPPendingRequestObject.m */, - 2A7AF36724826681001D74F9 /* DWDPRespondedIncomingRequestObject.h */, - 2A7AF36824826681001D74F9 /* DWDPRespondedIncomingRequestObject.m */, - 2A7AF36124825A0C001D74F9 /* DWDPUserObject.h */, - 2A7AF36224825A0C001D74F9 /* DWDPUserObject.m */, - 2ACCA3B324BF280A00DB32DE /* DWDPTxObject.h */, - 2ACCA3B424BF280A00DB32DE /* DWDPTxObject.m */, - ); - path = Objects; - sourceTree = ""; - }; - 2A7AF37C2482759F001D74F9 /* Notifications */ = { - isa = PBXGroup; - children = ( - 2A7AF37024826CDF001D74F9 /* DWDPAcceptedRequestNotificationObject.h */, - 2A7AF37124826CDF001D74F9 /* DWDPAcceptedRequestNotificationObject.m */, - 2A7AF376248270A4001D74F9 /* DWDPEstablishedContactNotificationObject.h */, - 2A7AF377248270A4001D74F9 /* DWDPEstablishedContactNotificationObject.m */, - 2A7AF3732482703C001D74F9 /* DWDPNewIncomingRequestNotificationObject.h */, - 2A7AF3742482703C001D74F9 /* DWDPNewIncomingRequestNotificationObject.m */, - 2AEC5CBC24940EC200F4A689 /* DWDPOutgoingRequestNotificationObject.h */, - 2AEC5CBD24940EC200F4A689 /* DWDPOutgoingRequestNotificationObject.m */, - ); - path = Notifications; - sourceTree = ""; - }; - 2A7AF37D248280A8001D74F9 /* Factory */ = { - isa = PBXGroup; - children = ( - 2A7AF3812482954C001D74F9 /* DWDPContactsItemsFactory.h */, - 2A7AF3822482954C001D74F9 /* DWDPContactsItemsFactory.m */, - 2A7AF37E248280CE001D74F9 /* DWDPSearchItemsFactory.h */, - 2A7AF37F248280CE001D74F9 /* DWDPSearchItemsFactory.m */, - ); - path = Factory; - sourceTree = ""; - }; - 2A7AF3902482D98C001D74F9 /* Items Associated Data */ = { - isa = PBXGroup; - children = ( - 2A7AF38A2482A22D001D74F9 /* DWDPBlockchainIdentityBackedItem.h */, - 2A7AF3912482D9BE001D74F9 /* DWDPDashpayUserBackedItem.h */, - 2A7AF3922482DA2B001D74F9 /* DWDPFriendRequestBackedItem.h */, - ); - path = "Items Associated Data"; - sourceTree = ""; - }; - 2A7AF39A2482FA7D001D74F9 /* Items */ = { - isa = PBXGroup; - children = ( - 2A7AF37D248280A8001D74F9 /* Factory */, - 2A7AF360248259C6001D74F9 /* Objects */, - 2A7AF35A248256EB001D74F9 /* Protocols */, - 2A7AF3412481A33C001D74F9 /* Views */, - ); - path = Items; - sourceTree = ""; - }; 2A7F3B18238C643D00DEA3EF /* Advanced Security */ = { isa = PBXGroup; children = ( @@ -4127,26 +4153,6 @@ path = AppleWatch; sourceTree = ""; }; - 2A885FC12449B56000B9F679 /* Children */ = { - isa = PBXGroup; - children = ( - 2A885FC52449B66500B9F679 /* DWSearchStateViewController.h */, - 2A885FC62449B66500B9F679 /* DWSearchStateViewController.m */, - 2A885FCA2449F08700B9F679 /* DWUserSearchResultViewController.h */, - 2A885FCB2449F08700B9F679 /* DWUserSearchResultViewController.m */, - ); - path = Children; - sourceTree = ""; - }; - 2A885FCD2449F34400B9F679 /* Model */ = { - isa = PBXGroup; - children = ( - 2A885FCE2449F37A00B9F679 /* DWUserSearchModel.h */, - 2A885FCF2449F37A00B9F679 /* DWUserSearchModel.m */, - ); - path = Model; - sourceTree = ""; - }; 2A8B9E3822FC7F3B00FF8653 /* Payments */ = { isa = PBXGroup; children = ( @@ -4390,19 +4396,6 @@ path = PhraseRepair; sourceTree = ""; }; - 2A951CE523D1CC6300602824 /* Profile */ = { - isa = PBXGroup; - children = ( - 2ACCA3AA24BE10C100DB32DE /* Model */, - 2ADC9D6A2462D4AD001D7C0D /* Views */, - 2AE2F0F7245C16C8001DD722 /* DWUserProfileViewController.h */, - 2AE2F0F8245C16C8001DD722 /* DWUserProfileViewController.m */, - 2A80F39324D86201003E3B1E /* DWModalUserProfileViewController.h */, - 2A80F39424D86201003E3B1E /* DWModalUserProfileViewController.m */, - ); - path = Profile; - sourceTree = ""; - }; 2A9CEBA322E1C25700A50237 /* SecureWallet */ = { isa = PBXGroup; children = ( @@ -4468,24 +4461,6 @@ path = Home; sourceTree = ""; }; - 2A9E7DCB23F6C00C00CDA1EE /* Models */ = { - isa = PBXGroup; - children = ( - 2A5BD5932451DCAF00688A8D /* DWAllowedCharactersUsernameValidationRule.h */, - 2A5BD5942451DCAF00688A8D /* DWAllowedCharactersUsernameValidationRule.m */, - 2A5BD59C2451F39500688A8D /* DWCheckExistenceUsernameValidationRule.h */, - 2A5BD59D2451F39500688A8D /* DWCheckExistenceUsernameValidationRule.m */, - 2A5BD5962451DD0700688A8D /* DWMaxLengthUsernameValidationRule.h */, - 2A5BD5972451DD0700688A8D /* DWMaxLengthUsernameValidationRule.m */, - 2A5BD5902451D68200688A8D /* DWMinLengthUsernameValidationRule.h */, - 2A5BD5912451D68300688A8D /* DWMinLengthUsernameValidationRule.m */, - 2A9E7DCC23F6C01A00CDA1EE /* DWUsernameValidationRule.h */, - 2A9E7DCD23F6C01A00CDA1EE /* DWUsernameValidationRule.m */, - 2A5BD5992451F1A100688A8D /* DWUsernameValidationRule+Protected.h */, - ); - path = Models; - sourceTree = ""; - }; 2A9FFDF62230FF2B00956D5F /* Uphold */ = { isa = PBXGroup; children = ( @@ -4697,17 +4672,6 @@ path = Start; sourceTree = ""; }; - 2AB2373524488D8E0081B62C /* GlobalSearch */ = { - isa = PBXGroup; - children = ( - 2A885FCD2449F34400B9F679 /* Model */, - 2A885FC12449B56000B9F679 /* Children */, - 2AB2373624488DB80081B62C /* DWUserSearchViewController.h */, - 2AB2373724488DB80081B62C /* DWUserSearchViewController.m */, - ); - path = GlobalSearch; - sourceTree = ""; - }; 2AB3416623A8C464004E37A7 /* Views */ = { isa = PBXGroup; children = ( @@ -4827,41 +4791,6 @@ path = Views; sourceTree = ""; }; - 2AC52AD3241BB5CC00D9A829 /* Setup */ = { - isa = PBXGroup; - children = ( - 2A3CCEF6242BB0AF00300AF8 /* RegistrationCompleted */, - 2AE8B66823CF0EEA0016F221 /* UsernamePending */, - 2AE8B66123CF08C50016F221 /* ConfirmUsername */, - 2AE8B63E23CDB9570016F221 /* CreateUsername */, - 2AC52AD4241BB5FC00D9A829 /* DWDashPaySetupFlowController.h */, - 2AC52AD5241BB5FC00D9A829 /* DWDashPaySetupFlowController.m */, - ); - path = Setup; - sourceTree = ""; - }; - 2ACCA3AA24BE10C100DB32DE /* Model */ = { - isa = PBXGroup; - children = ( - 2ACCA3AF24BE3CC200DB32DE /* DataSource */, - 2ADC9D7424640A2B001D7C0D /* DWUserProfileModel.h */, - 2ADC9D7524640A2B001D7C0D /* DWUserProfileModel.m */, - ); - path = Model; - sourceTree = ""; - }; - 2ACCA3AF24BE3CC200DB32DE /* DataSource */ = { - isa = PBXGroup; - children = ( - 2ACCA3AB24BE117300DB32DE /* DWProfileTxsFetchedDataSource.h */, - 2ACCA3AC24BE117300DB32DE /* DWProfileTxsFetchedDataSource.m */, - 2ACCA3AE24BE3BF900DB32DE /* DWUserProfileDataSource.h */, - 2ACCA3B024BE3CDC00DB32DE /* DWUserProfileDataSourceObject.h */, - 2ACCA3B124BE3CDC00DB32DE /* DWUserProfileDataSourceObject.m */, - ); - path = DataSource; - sourceTree = ""; - }; 2ACCD8562319395000A96B62 /* RequestAmount */ = { isa = PBXGroup; children = ( @@ -5024,71 +4953,6 @@ path = VerifiedSuccessfully; sourceTree = ""; }; - 2AD6E54A2487A20700B52F14 /* Requests */ = { - isa = PBXGroup; - children = ( - 2AD6E5642487E18200B52F14 /* Models */, - 2AD6E55E2487E13F00B52F14 /* DWRequestsContentViewController.h */, - 2AD6E55F2487E13F00B52F14 /* DWRequestsContentViewController.m */, - 2AD6E5612487E16400B52F14 /* DWRequestsViewController.h */, - 2AD6E5622487E16400B52F14 /* DWRequestsViewController.m */, - ); - path = Requests; - sourceTree = ""; - }; - 2AD6E5542487D65200B52F14 /* DataSource */ = { - isa = PBXGroup; - children = ( - C3DAD2BB247327D90001624F /* DWContactsDataSource.h */, - 2AD6E54B2487CE0100B52F14 /* DWContactsDataSourceObject.h */, - 2AD6E54C2487CE0100B52F14 /* DWContactsDataSourceObject.m */, - C3DAD2CA24757A210001624F /* DWContactsSearchDataSourceObject.h */, - C3DAD2CB24757A210001624F /* DWContactsSearchDataSourceObject.m */, - ); - path = DataSource; - sourceTree = ""; - }; - 2AD6E5642487E18200B52F14 /* Models */ = { - isa = PBXGroup; - children = ( - 2AD6E5682487E1DB00B52F14 /* DWRequestsModel.h */, - 2AD6E5692487E1DB00B52F14 /* DWRequestsModel.m */, - ); - path = Models; - sourceTree = ""; - }; - 2ADB396624223D4800A6F898 /* Views */ = { - isa = PBXGroup; - children = ( - 2ADB396724223D9B00A6F898 /* DWDashPayAnimationView.h */, - 2ADB396824223D9B00A6F898 /* DWDashPayAnimationView.m */, - 2A3CCEFA242BB1DD00300AF8 /* DWDPAvatarView.h */, - 2A3CCEFB242BB1DD00300AF8 /* DWDPAvatarView.m */, - 2A919F9A24A4DCAD0018C9A3 /* DWDPSmallContactView.h */, - 2A919F9B24A4DCAD0018C9A3 /* DWDPSmallContactView.m */, - 2AB7303C24D0BC0400DCB420 /* UIColor+DWDashPay.h */, - 2AB7303D24D0BC0400DCB420 /* UIColor+DWDashPay.m */, - ); - path = Views; - sourceTree = ""; - }; - 2ADC9D6A2462D4AD001D7C0D /* Views */ = { - isa = PBXGroup; - children = ( - 2ADC9D6B2462D4AD001D7C0D /* DWStretchyHeaderListCollectionLayout.h */, - 2ADC9D6F2462D4AD001D7C0D /* DWStretchyHeaderListCollectionLayout.m */, - 2ADC9D6C2462D4AD001D7C0D /* DWUserProfileHeaderView.h */, - 2ADC9D6E2462D4AD001D7C0D /* DWUserProfileHeaderView.m */, - 2ADC9D6D2462D4AD001D7C0D /* DWUserProfileNavigationTitleView.h */, - 2ADC9D702462D4AD001D7C0D /* DWUserProfileNavigationTitleView.m */, - 2ADC9D7A24644E46001D7C0D /* DWUserProfileContactActionsCell.h */, - 2ADC9D7B24644E46001D7C0D /* DWUserProfileContactActionsCell.m */, - 2A80F3D724DC55CC003E3B1E /* DWUserProfileSendRequestCell.h */, - 2A80F3D824DC55CC003E3B1E /* DWUserProfileSendRequestCell.m */, - ); - path = Views; - sourceTree = ""; - }; 2ADF83FB23632D55008459A7 /* Shared */ = { isa = PBXGroup; children = ( @@ -5115,138 +4979,6 @@ path = Resources; sourceTree = ""; }; - 2AE8B63D23CDB94E0016F221 /* DashPay */ = { - isa = PBXGroup; - children = ( - 2AEC5CBF2497558900F4A689 /* Global */, - 2A7AF39A2482FA7D001D74F9 /* Items */, - C3CA2029247E4AC300158074 /* Notifications */, - 2ADB396624223D4800A6F898 /* Views */, - 2AC52AD3241BB5CC00D9A829 /* Setup */, - 2A951CE523D1CC6300602824 /* Profile */, - 2AE9549A23D0C4DC003612B3 /* Contacts */, - 2A56EEFE2419310C002C32F3 /* DWDashPayConstants.h */, - 2A56EEFF2419310C002C32F3 /* DWDashPayConstants.m */, - ); - path = DashPay; - sourceTree = ""; - }; - 2AE8B63E23CDB9570016F221 /* CreateUsername */ = { - isa = PBXGroup; - children = ( - 2A9E7DCB23F6C00C00CDA1EE /* Models */, - 2A1AE78B23F463FB00179A6E /* Views */, - 2AE8B63F23CDB98A0016F221 /* DWCreateUsernameViewController.h */, - 2AE8B64023CDB98A0016F221 /* DWCreateUsernameViewController.m */, - 2AE8B64223CDC0F40016F221 /* DWInputUsernameViewController.h */, - 2AE8B64323CDC0F50016F221 /* DWInputUsernameViewController.m */, - ); - path = CreateUsername; - sourceTree = ""; - }; - 2AE8B66123CF08C50016F221 /* ConfirmUsername */ = { - isa = PBXGroup; - children = ( - 2AE8B66523CF09A00016F221 /* Views */, - 2AE8B66223CF09000016F221 /* DWConfirmUsernameViewController.h */, - 2AE8B66323CF09000016F221 /* DWConfirmUsernameViewController.m */, - ); - path = ConfirmUsername; - sourceTree = ""; - }; - 2AE8B66523CF09A00016F221 /* Views */ = { - isa = PBXGroup; - children = ( - 2A56EEF92417E30F002C32F3 /* DWConfirmUsernameContentView.h */, - 2A56EEFA2417E30F002C32F3 /* DWConfirmUsernameContentView.m */, - 2A60C9442444BF3900AF72CF /* DWConfirmUsernameContentView.xib */, - ); - path = Views; - sourceTree = ""; - }; - 2AE8B66823CF0EEA0016F221 /* UsernamePending */ = { - isa = PBXGroup; - children = ( - 2AE8B66923CF0F390016F221 /* DWUsernamePendingViewController.h */, - 2AE8B66A23CF0F390016F221 /* DWUsernamePendingViewController.m */, - ); - path = UsernamePending; - sourceTree = ""; - }; - 2AE9549A23D0C4DC003612B3 /* Contacts */ = { - isa = PBXGroup; - children = ( - C3DAD2BD24747F2D0001624F /* Base */, - 2AB2373524488D8E0081B62C /* GlobalSearch */, - 2AE954A223D0C621003612B3 /* Models */, - 2AD6E54A2487A20700B52F14 /* Requests */, - 2AE9549E23D0C519003612B3 /* Views */, - C3DAD2C1247512BA0001624F /* DWBaseContactsContentViewController.h */, - C3DAD2C2247512BA0001624F /* DWBaseContactsContentViewController.m */, - 2A827B7324B729C400A42042 /* DWBaseContactsContentViewController+DWProtected.h */, - 2AE9549B23D0C4F4003612B3 /* DWBaseContactsViewController.h */, - 2AE9549C23D0C4F4003612B3 /* DWBaseContactsViewController.m */, - 2AD6E55B2487DACC00B52F14 /* DWBaseContactsViewController+DWProtected.h */, - 2AD6E5552487D8C000B52F14 /* DWContactsContentViewController.h */, - 2AD6E5562487D8C000B52F14 /* DWContactsContentViewController.m */, - 2AD6E5582487D9AF00B52F14 /* DWContactsViewController.h */, - 2AD6E5592487D9AF00B52F14 /* DWContactsViewController.m */, - ); - path = Contacts; - sourceTree = ""; - }; - 2AE9549E23D0C519003612B3 /* Views */ = { - isa = PBXGroup; - children = ( - C3DAD2C6247538A90001624F /* DWTitleActionHeaderView.h */, - C3DAD2C5247538A90001624F /* DWTitleActionHeaderView.m */, - C3DAD2C7247538AA0001624F /* DWTitleActionHeaderView.xib */, - C3DAD2D32476886D0001624F /* DWContactsSearchInfoHeaderView.h */, - C3DAD2D42476886D0001624F /* DWContactsSearchInfoHeaderView.m */, - ); - path = Views; - sourceTree = ""; - }; - 2AE954A223D0C621003612B3 /* Models */ = { - isa = PBXGroup; - children = ( - 2AD6E5542487D65200B52F14 /* DataSource */, - 2A7AF33D24815BCA001D74F9 /* Fetched DataSource */, - 2A951CE223D1B92C00602824 /* DWBaseContactsModel.h */, - 2A951CE323D1B92C00602824 /* DWBaseContactsModel.m */, - 2AD6E54E2487D45F00B52F14 /* DWBaseContactsModel+DWProtected.h */, - 2AD6E5512487D50200B52F14 /* DWContactsModel.h */, - 2AD6E5522487D50200B52F14 /* DWContactsModel.m */, - C3DAD2C4247534820001624F /* DWContactsSortModeProtocol.h */, - ); - path = Models; - sourceTree = ""; - }; - 2AEC5CBF2497558900F4A689 /* Global */ = { - isa = PBXGroup; - children = ( - 2AEC5CC42497A90A00F4A689 /* Notifications */, - 2A7AF3972482E32E001D74F9 /* DWDashPayContactsActions.h */, - 2A7AF3982482E32E001D74F9 /* DWDashPayContactsActions.m */, - 2AEC5CC0249755BB00F4A689 /* DWDashPayContactsUpdater.h */, - 2AEC5CC1249755BB00F4A689 /* DWDashPayContactsUpdater.m */, - ); - path = Global; - sourceTree = ""; - }; - 2AEC5CC42497A90A00F4A689 /* Notifications */ = { - isa = PBXGroup; - children = ( - 2A7AF32624814A17001D74F9 /* DWNotificationsData.h */, - 2A7AF32724814A17001D74F9 /* DWNotificationsData.m */, - 2AEC5CB92494045C00F4A689 /* DWNotificationsFetchedDataSource.h */, - 2AEC5CBA2494045C00F4A689 /* DWNotificationsFetchedDataSource.m */, - 2AEC5CB32493D87D00F4A689 /* DWNotificationsProvider.h */, - 2AEC5CB42493D87D00F4A689 /* DWNotificationsProvider.m */, - ); - path = Notifications; - sourceTree = ""; - }; 2AFCB9B023BDEED900FF59A6 /* Send */ = { isa = PBXGroup; children = ( @@ -5970,436 +5702,1402 @@ path = Views; sourceTree = ""; }; - 47AE8BDF28C1305E00490F5E /* Views */ = { + 47AE8BDF28C1305E00490F5E /* Views */ = { + isa = PBXGroup; + children = ( + 47AE8BE028C1305E00490F5E /* DWExploreTestnetContentsView.h */, + 47AE8BE228C1305E00490F5E /* DWExploreTestnetContentsView.m */, + 47AE8BE328C1305E00490F5E /* DWExploreHeaderView.h */, + 47AE8BE128C1305E00490F5E /* DWExploreHeaderView.m */, + ); + path = Views; + sourceTree = ""; + }; + 47AE8C1628C63F9400490F5E /* Views */ = { + isa = PBXGroup; + children = ( + 47AE8C1728C63F9C00490F5E /* PointOfUseDetailsView.swift */, + 47AE8BBC28C1305E00490F5E /* AtmDetailsView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 47AF180329070B660025803E /* Application */ = { + isa = PBXGroup; + children = ( + 47FA3AFD293508E3008D58DC /* Syncyng Activity Monitor */, + 47AF180429070B720025803E /* Types.swift */, + 471DD1B7290A92CD00E030C8 /* Tools.swift */, + 478C983B2945801D00FAA0F0 /* Constants.swift */, + 475AE2B82974348F009A1055 /* App.swift */, + ); + path = Application; + sourceTree = ""; + }; + 47B30D76290BFC840080C326 /* Foundation */ = { + isa = PBXGroup; + children = ( + 47B30D79290D035B0080C326 /* DashTextAttachment.swift */, + ); + path = Foundation; + sourceTree = ""; + }; + 47C661B028FDC70F00028A8D /* Transfer Amount */ = { + isa = PBXGroup; + children = ( + 47A50F392913B96F00C70123 /* Model */, + 47838B78290019310003E8AB /* Views */, + 47C661B128FDC72700028A8D /* TransferAmountViewController.swift */, + ); + path = "Transfer Amount"; + sourceTree = ""; + }; + 47C6E6E129196B98003FEDF2 /* Views */ = { + isa = PBXGroup; + children = ( + 47C6E6E229196D48003FEDF2 /* TerritoriesListCell.swift */, + ); + path = Views; + sourceTree = ""; + }; + 47CDEECA294A2B9F008AE06D /* Base */ = { + isa = PBXGroup; + children = ( + 4789D26F29825F5400BAFEFA /* CoinbaseAmountViewController.swift */, + 47CDEECB294A2BAD008AE06D /* UIViewController+Coinbase.swift */, + 477F501429531C07003C7508 /* ViewModel+Coinbase.swift */, + ); + path = Base; + sourceTree = ""; + }; + 47CF469D296540D30067B6EE /* Accounts */ = { + isa = PBXGroup; + children = ( + 47CF46AA2965B6440067B6EE /* Service */, + 47CF46A2296541120067B6EE /* Account */, + 47CF469E296540E40067B6EE /* AccountRepository.swift */, + 47CF46A0296540EF0067B6EE /* AccountService.swift */, + ); + path = Accounts; + sourceTree = ""; + }; + 47CF46A2296541120067B6EE /* Account */ = { + isa = PBXGroup; + children = ( + 47CF46A429654E190067B6EE /* CBAccount.swift */, + ); + path = Account; + sourceTree = ""; + }; + 47CF46A629655E7C0067B6EE /* Payment Methods */ = { + isa = PBXGroup; + children = ( + 47CF46A729655E8E0067B6EE /* PaymentMethods.swift */, + ); + path = "Payment Methods"; + sourceTree = ""; + }; + 47CF46A9296584A40067B6EE /* Services */ = { + isa = PBXGroup; + children = ( + 47A2A2ED293E622700938DB7 /* CBSecureTokenService.swift */, + 47EEE23A293F041E00049E0B /* CBUserManager.swift */, + ); + path = Services; + sourceTree = ""; + }; + 47CF46AA2965B6440067B6EE /* Service */ = { + isa = PBXGroup; + children = ( + 47CF46AB2965B65B0067B6EE /* CBAccountManager.swift */, + ); + path = Service; + sourceTree = ""; + }; + 47E71F73286AED0E00CDF2BD /* Utils */ = { + isa = PBXGroup; + children = ( + 472D13E0299E1F2F006903F1 /* CSVBuilder.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 47E94B95296D7CF9000FE68E /* Custodial Swaps */ = { + isa = PBXGroup; + children = ( + 4751CABD296EFCB400F63AC4 /* Order Preview */, + 47E94B98296D7F92000FE68E /* Model */, + 47E94B96296D7D5B000FE68E /* CustodialSwapsViewController.swift */, + ); + path = "Custodial Swaps"; + sourceTree = ""; + }; + 47E94B98296D7F92000FE68E /* Model */ = { + isa = PBXGroup; + children = ( + 47E94B99296D7F99000FE68E /* CustodialSwapsModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + 47F006002971B1CE0029EB10 /* Currency Exchanger */ = { + isa = PBXGroup; + children = ( + 47A2E3A72972B14B0032A63B /* Data Provider */, + 47F006012971B1FE0029EB10 /* CurrencyExchanger.swift */, + 472D13EB299E6579006903F1 /* CurrencyExchanger_Objc.h */, + 472D13EC299E6579006903F1 /* CurrencyExchanger_Objc.m */, + ); + path = "Currency Exchanger"; + sourceTree = ""; + }; + 47F2C67B28602D3800C2B774 /* PageSheet */ = { + isa = PBXGroup; + children = ( + 47F2C67C28602D4F00C2B774 /* BasePageSheetViewController.swift */, + ); + path = PageSheet; + sourceTree = ""; + }; + 47F2C67E2860314A00C2B774 /* Details */ = { + isa = PBXGroup; + children = ( + 2A1B7D9E23239C7500BA8C6A /* Model */, + 2A1B7D82232156A600BA8C6A /* Views */, + 47A5145F2848F75B005A8E3E /* TxDetailViewController.swift */, + ); + path = Details; + sourceTree = ""; + }; + 47F2C6802860315D00C2B774 /* Reclassify Transactions */ = { + isa = PBXGroup; + children = ( + 47F2C6812860319400C2B774 /* TxReclassifyTransactionsInfoViewController.swift */, + 47F2C6832860513900C2B774 /* TxReclassifyTransactionsWhereToChangeViewController.swift */, + ); + path = "Reclassify Transactions"; + sourceTree = ""; + }; + 47F4B6C5294842A500AED4C9 /* Confirm Transaction Controller */ = { + isa = PBXGroup; + children = ( + 47F4B6CB29485A6700AED4C9 /* Views */, + 47F4B6C829484C8600AED4C9 /* Model */, + 47F4B6C6294842DF00AED4C9 /* ConfirmOrderController.swift */, + ); + path = "Confirm Transaction Controller"; + sourceTree = ""; + }; + 47F4B6C829484C8600AED4C9 /* Model */ = { + isa = PBXGroup; + children = ( + 47F4B6C929484C9800AED4C9 /* ConfirmOrderModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + 47F4B6CB29485A6700AED4C9 /* Views */ = { + isa = PBXGroup; + children = ( + 47F4B6CC29485A8B00AED4C9 /* ConfirmOrderCells.swift */, + ); + path = Views; + sourceTree = ""; + }; + 47FA3AFD293508E3008D58DC /* Syncyng Activity Monitor */ = { + isa = PBXGroup; + children = ( + 47FA3AFE29350929008D58DC /* SyncingActivityMonitor.swift */, + ); + path = "Syncyng Activity Monitor"; + sourceTree = ""; + }; + 47FA3B0029364743008D58DC /* Networking */ = { + isa = PBXGroup; + children = ( + 47FA3B0129364991008D58DC /* HTTPClient.swift */, + ); + path = Networking; + sourceTree = ""; + }; + 75D5F3B5191EC270004AB296 = { + isa = PBXGroup; + children = ( + C9D2C9552A320AC100D15901 /* DashPay */, + 2ADF83FB23632D55008459A7 /* Shared */, + 75D5F3C7191EC270004AB296 /* DashWallet */, + BAE12BE81B2DEE7F00895CC5 /* TodayExtension */, + 75D5F3EC191EC270004AB296 /* DashWalletTests */, + BA913BDF1BD57E4D005A7C0E /* WatchApp */, + BA913BEE1BD57E4D005A7C0E /* WatchApp Extension */, + 2A4663012279DC2F0027533B /* DashWalletScreenshotsUITests */, + 75D5F3C0191EC270004AB296 /* Frameworks */, + 75D5F3BF191EC270004AB296 /* Products */, + EBFC2EA47915CD4F5BA81564 /* Pods */, + ); + sourceTree = ""; + }; + 75D5F3BF191EC270004AB296 /* Products */ = { + isa = PBXGroup; + children = ( + 75D5F3BE191EC270004AB296 /* dashwallet.app */, + 75D5F3E5191EC270004AB296 /* DashWalletTests.xctest */, + BAE12BE51B2DEE7F00895CC5 /* TodayExtension.appex */, + BA913BDE1BD57E4D005A7C0E /* WatchApp.app */, + BA913BEA1BD57E4D005A7C0E /* WatchApp Extension.appex */, + 2A4663002279DC2F0027533B /* DashWalletScreenshotsUITests.xctest */, + C9D2C9532A320AA000D15901 /* dashpay.app */, + ); + name = Products; + sourceTree = ""; + }; + 75D5F3C0191EC270004AB296 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 472CEE022925259B00656B48 /* libPods-WatchApp Extension.a */, + 0FC75A8E28F03E22000E4858 /* libDashSync.a */, + 2A5279BB23D994BC00F856D3 /* CoreNFC.framework */, + 2AA08533237D6CF500797F95 /* CloudKit.framework */, + 2A0C6C852362EFAE00C86F37 /* libDashSync-Shared.a */, + FB3E9F5F236125F600C09C5C /* BackgroundTasks.framework */, + 2ABCA9172357A61B00092C09 /* Foundation.framework */, + 2AD1CE9822DE63FA00C99324 /* GameplayKit.framework */, + FB4FA9C222505DD60060B017 /* AudioToolbox.framework */, + FBEF3AED2182395800917AB6 /* DashSync.framework */, + FB248B621F79BB7C00405AE0 /* SafariServices.framework */, + FB248B5C1F73803100405AE0 /* UserNotifications.framework */, + FBF3F4301E42B02800C7248E /* ImageIO.framework */, + FBF3F42E1E42B01E00C7248E /* CoreGraphics.framework */, + FBF3F42C1E42B00C00C7248E /* UIKit.framework */, + FBF3F42A1E42AF8F00C7248E /* QuartzCore.framework */, + 225383001C694D7400968BEE /* CoreLocation.framework */, + 22D3613C1C56F2CD0057CF76 /* libsqlite3.tbd */, + 222E7F571C46E9BE009AB45D /* SystemConfiguration.framework */, + 222E7F551C46E9B8009AB45D /* Security.framework */, + 222040C51C1A1940005CE1C3 /* WebKit.framework */, + 22B6A4471C0E963900673913 /* libbz2.tbd */, + 75F2E0B61BE2D5F000EAE861 /* Accelerate.framework */, + BAE12BE61B2DEE7F00895CC5 /* NotificationCenter.framework */, + A169BE797EC811F83373CCB9 /* Pods_dashwallet_no_watch.framework */, + BEAE29AA65F429DD9EF1A1A7 /* libPods-DashWalletTests.a */, + 02771AC5DDCA0A1749C6A05B /* libPods-TodayExtension.a */, + 07283055DE20FE578E399BE7 /* libPods-WatchApp.a */, + C47D5A9D319D41B450A9B96B /* libPods-WatchApp Extension.a */, + 0CDD4C961516ED20BC9F01FA /* libPods-DashWalletScreenshotsUITests.a */, + CE89DF632BC53160BB8FBED1 /* libPods-dashpay.a */, + 17427514C25A58AB4AEDF999 /* libPods-dashwallet.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 75D5F3C7191EC270004AB296 /* DashWallet */ = { + isa = PBXGroup; + children = ( + 2A44313B22CF62FC009BAF7F /* Sources */, + 2A4430F022CBD566009BAF7F /* Resources */, + FB6DD3811F7FA48500BC1E4D /* dashwallet.entitlements */, + 75D5F3C8191EC270004AB296 /* Supporting Files */, + FB66977E212C0B940034BE4F /* LaunchScreen.storyboard */, + 2A4430E522CBB6EC009BAF7F /* AppDelegate.h */, + 2A4430E622CBB6EC009BAF7F /* AppDelegate.m */, + ); + path = DashWallet; + sourceTree = ""; + }; + 75D5F3C8191EC270004AB296 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C94F5E8729D3E7E30034FD57 /* GoogleService-Info.plist */, + 47AE8BAF28BFF28400490F5E /* explore.db */, + 47A5145E2848F75A005A8E3E /* dashwallet-Bridging-Header.h */, + 2A8F420821BED16300858B91 /* DashSyncCurrentCommit */, + 75D5F3C9191EC270004AB296 /* Info.plist */, + 757E09971ADB8EEB006FD352 /* Localizable.strings */, + 2ADC722723B5547000D9DD37 /* Localizable.stringsdict */, + 75D5F3CD191EC270004AB296 /* main.m */, + 75D5F3CF191EC270004AB296 /* DashWallet-Prefix.pch */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 75D5F3EC191EC270004AB296 /* DashWalletTests */ = { + isa = PBXGroup; + children = ( + 47A184E4299CC19A0017E32C /* rates.json */, + 75CE50B51B5216F100DBC18C /* Info.plist */, + 471DD1BE290AE9F000E030C8 /* DashWalletTests-Bridging-Header.h */, + 2A2120EB22145CAE009906DC /* DWAmountInputValidatorTests.m */, + 471DD1BB290AE30B00E030C8 /* String+DashWalletTests.swift */, + 47976E492987772700612988 /* AmountObjectTests.swift */, + ); + path = DashWalletTests; + sourceTree = ""; + }; + BA913BDF1BD57E4D005A7C0E /* WatchApp */ = { + isa = PBXGroup; + children = ( + BA913BE01BD57E4D005A7C0E /* Interface.storyboard */, + BA913BE31BD57E4D005A7C0E /* Assets.xcassets */, + BA7B537A1BDD1DCF00355E8D /* LoadingIndicator.xcassets */, + BA913BE51BD57E4D005A7C0E /* Info.plist */, + ); + path = WatchApp; + sourceTree = ""; + }; + BA913BEE1BD57E4D005A7C0E /* WatchApp Extension */ = { + isa = PBXGroup; + children = ( + BA913BF11BD57E4D005A7C0E /* ExtensionDelegate.swift */, + BAA6E3EA1BD5C78500773205 /* DWSetupInfoInterfaceController.swift */, + BAA6E3EE1BD5CA0E00773205 /* BRAWBalanceInterfaceController.swift */, + BAA6E3F01BD5CA5900773205 /* BRAWReceiveMoneyInterfaceController.swift */, + BA6645951BD924EB007A6BB1 /* BRAWTransactionRowControl.swift */, + BA4A72661BE11AFA00E39C01 /* BRAWWeakTimerTarget.swift */, + 22F45A0A1C30EAB700B07A15 /* BRAWKeypad.swift */, + 2AB7F7E823846F6000C173AD /* DWMainInterfaceController.swift */, + 2AB7F7E62384676200C173AD /* DWWatchDataManager.swift */, + 2AB7F7EA2384752C00C173AD /* DWTxInfoDisplayableInterfaceController.swift */, + BA913BF71BD57E4D005A7C0E /* Info.plist */, + BAC7B6C41BD9C9B600165B84 /* WatchApp Extension-Bridging-Header.h */, + ); + path = "WatchApp Extension"; + sourceTree = ""; + }; + BAE12BE81B2DEE7F00895CC5 /* TodayExtension */ = { + isa = PBXGroup; + children = ( + 2A741DC02363993600840ADF /* DWTodayViewController.h */, + BAE12C051B2DEEF700895CC5 /* DWTodayViewController.m */, + 759816E519357D6F005060EA /* BRBubbleView.h */, + 759816E619357D6F005060EA /* BRBubbleView.m */, + 2A741DC123639A9700840ADF /* TodayExtension.storyboard */, + BA54D3CD1B2EA74000C9CB28 /* TodayExtensionAssets.xcassets */, + BAE12BEA1B2DEE7F00895CC5 /* Info.plist */, + FB1212A91FFFB0F3000E407E /* dashwalletTodayExtension.entitlements */, + ); + path = TodayExtension; + sourceTree = ""; + }; + C3DAD265246AA6CF0001624F /* ScreenshotWarning */ = { + isa = PBXGroup; + children = ( + C3DAD266246AA6F10001624F /* DWScreenshotWarningViewController.h */, + C3DAD267246AA6F10001624F /* DWScreenshotWarningViewController.m */, + ); + path = ScreenshotWarning; + sourceTree = ""; + }; + C909615629F29C7500002D82 /* Cell */ = { + isa = PBXGroup; + children = ( + C909615829F29C9200002D82 /* KeysOverviewCell.swift */, + ); + path = Cell; + sourceTree = ""; + }; + C909615729F29C7E00002D82 /* Model */ = { + isa = PBXGroup; + children = ( + C909614C29EFF7D600002D82 /* WalletKeysOverviewModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + C943B2B22A408CAF00AF23C5 /* Profile */ = { + isa = PBXGroup; + children = ( + C943B2CF2A408CEC00AF23C5 /* EditProfile */, + C943B2B92A408CEC00AF23C5 /* UserProfile */, + C943B2C92A408CEC00AF23C5 /* UserProfileModalQR */, + ); + path = Profile; + sourceTree = ""; + }; + C943B2B92A408CEC00AF23C5 /* UserProfile */ = { + isa = PBXGroup; + children = ( + C943B2BA2A408CEC00AF23C5 /* DWUpdatingUserProfileView.h */, + C943B2BB2A408CEC00AF23C5 /* DWCurrentUserProfileView.h */, + C943B2BC2A408CEC00AF23C5 /* DWErrorUpdatingUserProfileView.m */, + C943B2BD2A408CEC00AF23C5 /* DWDPWelcomeMenuView.h */, + C943B2BE2A408CEC00AF23C5 /* DWUserProfileContainerView.h */, + C943B2BF2A408CEC00AF23C5 /* Models */, + C943B2C42A408CEC00AF23C5 /* DWErrorUpdatingUserProfileView.h */, + C943B2C52A408CEC00AF23C5 /* DWCurrentUserProfileView.m */, + C943B2C62A408CEC00AF23C5 /* DWUpdatingUserProfileView.m */, + C943B2C72A408CEC00AF23C5 /* DWDPWelcomeMenuView.m */, + C943B2C82A408CEC00AF23C5 /* DWUserProfileContainerView.m */, + ); + path = UserProfile; + sourceTree = ""; + }; + C943B2BF2A408CEC00AF23C5 /* Models */ = { + isa = PBXGroup; + children = ( + C943B2C02A408CEC00AF23C5 /* DSBlockchainIdentity+DWDisplayTitleSubtitle.h */, + C943B2C12A408CEC00AF23C5 /* DWDPUpdateProfileModel.h */, + C943B2C22A408CEC00AF23C5 /* DSBlockchainIdentity+DWDisplayTitleSubtitle.m */, + C943B2C32A408CEC00AF23C5 /* DWDPUpdateProfileModel.m */, + ); + path = Models; + sourceTree = ""; + }; + C943B2C92A408CEC00AF23C5 /* UserProfileModalQR */ = { + isa = PBXGroup; + children = ( + C943B2CA2A408CEC00AF23C5 /* DWUserProfileModalQRContentView.h */, + C943B2CB2A408CEC00AF23C5 /* DWDWUserProfileModalQRContentView.h */, + C943B2CC2A408CEC00AF23C5 /* DWUserProfileModalQRViewController.h */, + C943B2CD2A408CEC00AF23C5 /* DWUserProfileModalQRContentView.m */, + C943B2CE2A408CEC00AF23C5 /* DWUserProfileModalQRViewController.m */, + ); + path = UserProfileModalQR; + sourceTree = ""; + }; + C943B2CF2A408CEC00AF23C5 /* EditProfile */ = { + isa = PBXGroup; + children = ( + C943B2D02A408CEC00AF23C5 /* DWCropAvatarViewController.h */, + C943B2D12A408CEC00AF23C5 /* DWEditProfileViewController.h */, + C943B2D22A408CEC00AF23C5 /* DWRootEditProfileViewController.h */, + C943B2D32A408CEC00AF23C5 /* Imgur */, + C943B2DA2A408CEC00AF23C5 /* Utils */, + C943B2DD2A408CEC00AF23C5 /* DWRootEditProfileViewController.m */, + C943B2DE2A408CEC00AF23C5 /* DWEditProfileViewController.m */, + C943B2DF2A408CEC00AF23C5 /* DWCropAvatarViewController.m */, + C943B2E02A408CEC00AF23C5 /* Avatar */, + C943B2E62A408CEC00AF23C5 /* External Sources */, + C943B2F52A408CEC00AF23C5 /* SaveAlert */, + C943B2FA2A408CEC00AF23C5 /* Views */, + C943B30E2A408CEC00AF23C5 /* Upload */, + ); + path = EditProfile; + sourceTree = ""; + }; + C943B2D32A408CEC00AF23C5 /* Imgur */ = { + isa = PBXGroup; + children = ( + C943B2D42A408CEC00AF23C5 /* DWImgurInfoChildView.m */, + C943B2D52A408CEC00AF23C5 /* DWImgurInfoViewController.h */, + C943B2D62A408CEC00AF23C5 /* DWImgurItemView.m */, + C943B2D72A408CEC00AF23C5 /* DWImgurInfoViewController.m */, + C943B2D82A408CEC00AF23C5 /* DWImgurInfoChildView.h */, + C943B2D92A408CEC00AF23C5 /* DWImgurItemView.h */, + ); + path = Imgur; + sourceTree = ""; + }; + C943B2DA2A408CEC00AF23C5 /* Utils */ = { + isa = PBXGroup; + children = ( + C943B2DB2A408CEC00AF23C5 /* DWFaceDetector.h */, + C943B2DC2A408CEC00AF23C5 /* DWFaceDetector.m */, + ); + path = Utils; + sourceTree = ""; + }; + C943B2E02A408CEC00AF23C5 /* Avatar */ = { + isa = PBXGroup; + children = ( + C943B2E12A408CEC00AF23C5 /* DWAvatarEditSelectorViewController.m */, + C943B2E22A408CEC00AF23C5 /* DWAvatarEditSelectorViewController.h */, + C943B2E32A408CEC00AF23C5 /* Views */, + ); + path = Avatar; + sourceTree = ""; + }; + C943B2E32A408CEC00AF23C5 /* Views */ = { + isa = PBXGroup; + children = ( + C943B2E42A408CEC00AF23C5 /* DWAvatarEditSelectorContentView.m */, + C943B2E52A408CEC00AF23C5 /* DWAvatarEditSelectorContentView.h */, + ); + path = Views; + sourceTree = ""; + }; + C943B2E62A408CEC00AF23C5 /* External Sources */ = { + isa = PBXGroup; + children = ( + C943B2E72A408CEC00AF23C5 /* Skeleton */, + C943B2F12A408CEC00AF23C5 /* DWAvatarGravatarViewController.h */, + C943B2F22A408CEC00AF23C5 /* DWAvatarPublicURLViewController.m */, + C943B2F32A408CEC00AF23C5 /* DWAvatarGravatarViewController.m */, + C943B2F42A408CEC00AF23C5 /* DWAvatarPublicURLViewController.h */, + ); + path = "External Sources"; + sourceTree = ""; + }; + C943B2E72A408CEC00AF23C5 /* Skeleton */ = { + isa = PBXGroup; + children = ( + C943B2E82A408CEC00AF23C5 /* DWExternalSourceViewController.h */, + C943B2E92A408CEC00AF23C5 /* DWExternalSourceViewController.m */, + C943B2EA2A408CEC00AF23C5 /* Views */, + ); + path = Skeleton; + sourceTree = ""; + }; + C943B2EA2A408CEC00AF23C5 /* Views */ = { + isa = PBXGroup; + children = ( + C943B2EB2A408CEC00AF23C5 /* DWAvatarExternalLoadingView.h */, + C943B2EC2A408CEC00AF23C5 /* DWAvatarExternalSourceView.m */, + C943B2ED2A408CEC00AF23C5 /* DWAvatarExternalSourceConfig.h */, + C943B2EE2A408CEC00AF23C5 /* DWAvatarExternalLoadingView.m */, + C943B2EF2A408CEC00AF23C5 /* DWAvatarExternalSourceConfig.m */, + C943B2F02A408CEC00AF23C5 /* DWAvatarExternalSourceView.h */, + ); + path = Views; + sourceTree = ""; + }; + C943B2F52A408CEC00AF23C5 /* SaveAlert */ = { + isa = PBXGroup; + children = ( + C943B2F62A408CEC00AF23C5 /* DWSaveAlertChildView.h */, + C943B2F72A408CEC00AF23C5 /* DWSaveAlertViewController.m */, + C943B2F82A408CEC00AF23C5 /* DWSaveAlertChildView.m */, + C943B2F92A408CEC00AF23C5 /* DWSaveAlertViewController.h */, + ); + path = SaveAlert; + sourceTree = ""; + }; + C943B2FA2A408CEC00AF23C5 /* Views */ = { + isa = PBXGroup; + children = ( + C943B2FB2A408CEC00AF23C5 /* CellModels */, + C943B3062A408CEC00AF23C5 /* DWEditProfileBaseCell.h */, + C943B3072A408CEC00AF23C5 /* DWEditProfileTextViewCell.m */, + C943B3082A408CEC00AF23C5 /* DWEditProfileAvatarView.h */, + C943B3092A408CEC00AF23C5 /* DWEditProfileTextFieldCell.h */, + C943B30A2A408CEC00AF23C5 /* DWEditProfileTextViewCell.h */, + C943B30B2A408CEC00AF23C5 /* DWEditProfileBaseCell.m */, + C943B30C2A408CEC00AF23C5 /* DWEditProfileTextFieldCell.m */, + C943B30D2A408CEC00AF23C5 /* DWEditProfileAvatarView.m */, + ); + path = Views; + sourceTree = ""; + }; + C943B2FB2A408CEC00AF23C5 /* CellModels */ = { + isa = PBXGroup; + children = ( + C943B2FC2A408CEC00AF23C5 /* DWProfileAboutCellModel.m */, + C943B2FD2A408CEC00AF23C5 /* DWProfileDisplayNameCellModel.m */, + C943B2FE2A408CEC00AF23C5 /* Base */, + C943B3042A408CEC00AF23C5 /* DWProfileDisplayNameCellModel.h */, + C943B3052A408CEC00AF23C5 /* DWProfileAboutCellModel.h */, + ); + path = CellModels; + sourceTree = ""; + }; + C943B2FE2A408CEC00AF23C5 /* Base */ = { + isa = PBXGroup; + children = ( + C943B2FF2A408CEC00AF23C5 /* DWTextViewFormCellModel.h */, + C943B3002A408CEC00AF23C5 /* DWTextFieldFormCellModel.m */, + C943B3012A408CEC00AF23C5 /* DWTextFieldFormCellModel.h */, + C943B3022A408CEC00AF23C5 /* DWTextViewFormCellModel.m */, + C943B3032A408CEC00AF23C5 /* DWTextInputFormTableViewCell.h */, + ); + path = Base; + sourceTree = ""; + }; + C943B30E2A408CEC00AF23C5 /* Upload */ = { + isa = PBXGroup; + children = ( + C943B30F2A408CEC00AF23C5 /* DWUploadAvatarViewController.m */, + C943B3102A408CEC00AF23C5 /* DWUploadAvatarModel.m */, + C943B3112A408CEC00AF23C5 /* DWHourGlassAnimationView.h */, + C943B3122A408CEC00AF23C5 /* DWUploadAvatarChildView.h */, + C943B3132A408CEC00AF23C5 /* DWUploadAvatarViewController.h */, + C943B3142A408CEC00AF23C5 /* DWUploadAvatarModel.h */, + C943B3152A408CEC00AF23C5 /* DWUploadAvatarChildView.m */, + C943B3162A408CEC00AF23C5 /* DWHourGlassAnimationView.m */, + ); + path = Upload; + sourceTree = ""; + }; + C943B33D2A408DB900AF23C5 /* Menu */ = { + isa = PBXGroup; + children = ( + C943B33E2A408E0500AF23C5 /* DWMainMenuViewController+DashPay.h */, + C943B33F2A408E5500AF23C5 /* DWMainMenuViewController+DashPay.m */, + ); + path = Menu; + sourceTree = ""; + }; + C943B3482A40A4C500AF23C5 /* InfoPopup */ = { + isa = PBXGroup; + children = ( + C943B3492A40A4C500AF23C5 /* DWInfoPopupContentView.h */, + C943B34A2A40A4C500AF23C5 /* DWInfoPopupViewController.m */, + C943B34B2A40A4C500AF23C5 /* DWInfoPopupContentView.m */, + C943B34C2A40A4C500AF23C5 /* DWInfoPopupViewController.h */, + ); + path = InfoPopup; + sourceTree = ""; + }; + C943B34F2A40A54500AF23C5 /* DashPay */ = { + isa = PBXGroup; + children = ( + C943B3502A40A54500AF23C5 /* Contacts */, + C943B39C2A40A54600AF23C5 /* Setup */, + C943B3C82A40A54600AF23C5 /* DWDashPayConstants.h */, + C943B3C92A40A54600AF23C5 /* Welcome */, + C943B3E82A40A54600AF23C5 /* Profile */, + C943B4032A40A54600AF23C5 /* Items */, + C943B44B2A40A54600AF23C5 /* Invites */, + C943B47F2A40A54600AF23C5 /* Views */, + C943B48A2A40A54600AF23C5 /* Error */, + C943B48D2A40A54600AF23C5 /* DWDashPayConstants.m */, + C943B48E2A40A54600AF23C5 /* Global */, + C943B49E2A40A54600AF23C5 /* Notifications */, + ); + name = DashPay; + path = "../../../../DashWallet-Platform/DashWallet/Sources/UI/DashPay"; + sourceTree = ""; + }; + C943B3502A40A54500AF23C5 /* Contacts */ = { + isa = PBXGroup; + children = ( + C943B3512A40A54500AF23C5 /* DWBaseContactsViewController+DWProtected.h */, + C943B3522A40A54500AF23C5 /* DWContactsPlaceholderViewController.m */, + C943B3532A40A54500AF23C5 /* DWRootContactsViewController.h */, + C943B3542A40A54500AF23C5 /* DWContactsViewController.m */, + C943B3552A40A54500AF23C5 /* DWBaseContactsContentViewController.m */, + C943B3562A40A54500AF23C5 /* Placeholders */, + C943B35B2A40A54500AF23C5 /* DWContactsContentViewController.m */, + C943B35C2A40A54500AF23C5 /* DWBaseContactsViewController.m */, + C943B35D2A40A54500AF23C5 /* Models */, + C943B3712A40A54600AF23C5 /* Requests */, + C943B3792A40A54600AF23C5 /* DWRootContactsViewController.m */, + C943B37A2A40A54600AF23C5 /* DWBaseContactsContentViewController+DWProtected.h */, + C943B37B2A40A54600AF23C5 /* DWContactsPlaceholderViewController.h */, + C943B37C2A40A54600AF23C5 /* DWContactsContentViewController.h */, + C943B37D2A40A54600AF23C5 /* DWBaseContactsContentViewController.h */, + C943B37E2A40A54600AF23C5 /* DWContactsViewController.h */, + C943B37F2A40A54600AF23C5 /* Views */, + C943B38D2A40A54600AF23C5 /* Base */, + C943B3902A40A54600AF23C5 /* DWBaseContactsViewController.h */, + C943B3912A40A54600AF23C5 /* GlobalSearch */, + ); + path = Contacts; + sourceTree = ""; + }; + C943B3562A40A54500AF23C5 /* Placeholders */ = { + isa = PBXGroup; + children = ( + C943B3572A40A54500AF23C5 /* DWNoContactsViewController.m */, + C943B3582A40A54500AF23C5 /* DWInvitationSuggestionView.m */, + C943B3592A40A54500AF23C5 /* DWNoContactsViewController.h */, + C943B35A2A40A54500AF23C5 /* DWInvitationSuggestionView.h */, + ); + path = Placeholders; + sourceTree = ""; + }; + C943B35D2A40A54500AF23C5 /* Models */ = { + isa = PBXGroup; + children = ( + C943B35E2A40A54500AF23C5 /* DataSource */, + C943B3642A40A54500AF23C5 /* Fetched DataSource */, + C943B36B2A40A54600AF23C5 /* DWBaseContactsModel+DWProtected.h */, + C943B36C2A40A54600AF23C5 /* DWBaseContactsModel.h */, + C943B36D2A40A54600AF23C5 /* DWContactsModel.h */, + C943B36E2A40A54600AF23C5 /* DWContactsSortModeProtocol.h */, + C943B36F2A40A54600AF23C5 /* DWBaseContactsModel.m */, + C943B3702A40A54600AF23C5 /* DWContactsModel.m */, + ); + path = Models; + sourceTree = ""; + }; + C943B35E2A40A54500AF23C5 /* DataSource */ = { + isa = PBXGroup; + children = ( + C943B35F2A40A54500AF23C5 /* DWContactsDataSourceObject.m */, + C943B3602A40A54500AF23C5 /* DWContactsSearchDataSourceObject.m */, + C943B3612A40A54500AF23C5 /* DWContactsSearchDataSourceObject.h */, + C943B3622A40A54500AF23C5 /* DWContactsDataSource.h */, + C943B3632A40A54500AF23C5 /* DWContactsDataSourceObject.h */, + ); + path = DataSource; + sourceTree = ""; + }; + C943B3642A40A54500AF23C5 /* Fetched DataSource */ = { + isa = PBXGroup; + children = ( + C943B3652A40A54500AF23C5 /* DWIncomingFetchedDataSource.h */, + C943B3662A40A54500AF23C5 /* DWContactsFetchedDataSource.h */, + C943B3672A40A54500AF23C5 /* DWFetchedResultsDataSource.h */, + C943B3682A40A54500AF23C5 /* DWIncomingFetchedDataSource.m */, + C943B3692A40A54600AF23C5 /* DWContactsFetchedDataSource.m */, + C943B36A2A40A54600AF23C5 /* DWFetchedResultsDataSource.m */, + ); + path = "Fetched DataSource"; + sourceTree = ""; + }; + C943B3712A40A54600AF23C5 /* Requests */ = { + isa = PBXGroup; + children = ( + C943B3722A40A54600AF23C5 /* DWRequestsContentViewController.m */, + C943B3732A40A54600AF23C5 /* Models */, + C943B3762A40A54600AF23C5 /* DWRequestsViewController.h */, + C943B3772A40A54600AF23C5 /* DWRequestsContentViewController.h */, + C943B3782A40A54600AF23C5 /* DWRequestsViewController.m */, + ); + path = Requests; + sourceTree = ""; + }; + C943B3732A40A54600AF23C5 /* Models */ = { + isa = PBXGroup; + children = ( + C943B3742A40A54600AF23C5 /* DWRequestsModel.m */, + C943B3752A40A54600AF23C5 /* DWRequestsModel.h */, + ); + path = Models; + sourceTree = ""; + }; + C943B37F2A40A54600AF23C5 /* Views */ = { + isa = PBXGroup; + children = ( + C943B3802A40A54600AF23C5 /* DWGlobalMatchHeaderView.m */, + C943B3812A40A54600AF23C5 /* DWGlobalMatchFailedHeaderView.m */, + C943B3822A40A54600AF23C5 /* DWContactsSearchPlaceholderView.m */, + C943B3832A40A54600AF23C5 /* DWTitleActionHeaderView.h */, + C943B3842A40A54600AF23C5 /* BaseCollectionReusableView.h */, + C943B3852A40A54600AF23C5 /* DWContactsSearchInfoHeaderView.m */, + C943B3862A40A54600AF23C5 /* DWGlobalMatchHeaderView.h */, + C943B3872A40A54600AF23C5 /* DWTitleActionHeaderView.xib */, + C943B3882A40A54600AF23C5 /* DWGlobalMatchFailedHeaderView.h */, + C943B3892A40A54600AF23C5 /* BaseCollectionReusableView.m */, + C943B38A2A40A54600AF23C5 /* DWTitleActionHeaderView.m */, + C943B38B2A40A54600AF23C5 /* DWContactsSearchPlaceholderView.h */, + C943B38C2A40A54600AF23C5 /* DWContactsSearchInfoHeaderView.h */, + ); + path = Views; + sourceTree = ""; + }; + C943B38D2A40A54600AF23C5 /* Base */ = { + isa = PBXGroup; + children = ( + C943B38E2A40A54600AF23C5 /* DWSearchViewController.h */, + C943B38F2A40A54600AF23C5 /* DWSearchViewController.m */, + ); + path = Base; + sourceTree = ""; + }; + C943B3912A40A54600AF23C5 /* GlobalSearch */ = { + isa = PBXGroup; + children = ( + C943B3922A40A54600AF23C5 /* DWUserSearchViewController.h */, + C943B3932A40A54600AF23C5 /* Model */, + C943B3962A40A54600AF23C5 /* DWUserSearchViewController.m */, + C943B3972A40A54600AF23C5 /* Children */, + ); + path = GlobalSearch; + sourceTree = ""; + }; + C943B3932A40A54600AF23C5 /* Model */ = { + isa = PBXGroup; + children = ( + C943B3942A40A54600AF23C5 /* DWUserSearchModel.m */, + C943B3952A40A54600AF23C5 /* DWUserSearchModel.h */, + ); + path = Model; + sourceTree = ""; + }; + C943B3972A40A54600AF23C5 /* Children */ = { + isa = PBXGroup; + children = ( + C943B3982A40A54600AF23C5 /* DWUserSearchResultViewController.h */, + C943B3992A40A54600AF23C5 /* DWSearchStateViewController.h */, + C943B39A2A40A54600AF23C5 /* DWUserSearchResultViewController.m */, + C943B39B2A40A54600AF23C5 /* DWSearchStateViewController.m */, + ); + path = Children; + sourceTree = ""; + }; + C943B39C2A40A54600AF23C5 /* Setup */ = { + isa = PBXGroup; + children = ( + C943B39D2A40A54600AF23C5 /* UsernamePending */, + C943B3A02A40A54600AF23C5 /* CreateUsername */, + C943B3BA2A40A54600AF23C5 /* RegistrationCompleted */, + C943B3BE2A40A54600AF23C5 /* DWDashPaySetupFlowController.m */, + C943B3C02A40A54600AF23C5 /* DWDashPaySetupFlowController.h */, + C943B3C12A40A54600AF23C5 /* ConfirmUsername */, + ); + path = Setup; + sourceTree = ""; + }; + C943B39D2A40A54600AF23C5 /* UsernamePending */ = { + isa = PBXGroup; + children = ( + C943B39E2A40A54600AF23C5 /* DWUsernamePendingViewController.h */, + C943B39F2A40A54600AF23C5 /* DWUsernamePendingViewController.m */, + ); + path = UsernamePending; + sourceTree = ""; + }; + C943B3A02A40A54600AF23C5 /* CreateUsername */ = { + isa = PBXGroup; + children = ( + C943B3A12A40A54600AF23C5 /* DWInputUsernameViewController.h */, + C943B3A22A40A54600AF23C5 /* DWCreateUsernameViewController.h */, + C943B3A32A40A54600AF23C5 /* Models */, + C943B3AF2A40A54600AF23C5 /* DWInputUsernameViewController.m */, + C943B3B02A40A54600AF23C5 /* DWCreateUsernameViewController.m */, + C943B3B12A40A54600AF23C5 /* Views */, + ); + path = CreateUsername; + sourceTree = ""; + }; + C943B3A32A40A54600AF23C5 /* Models */ = { + isa = PBXGroup; + children = ( + C943B3A42A40A54600AF23C5 /* DWUsernameValidationRule.m */, + C943B3A52A40A54600AF23C5 /* DWLengthUsernameValidationRule.h */, + C943B3A62A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m */, + C943B3A72A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.h */, + C943B3A82A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.h */, + C943B3A92A40A54600AF23C5 /* DWLengthUsernameValidationRule.m */, + C943B3AA2A40A54600AF23C5 /* DWUsernameValidationRule.h */, + C943B3AB2A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.h */, + C943B3AC2A40A54600AF23C5 /* DWUsernameValidationRule+Protected.h */, + C943B3AD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m */, + C943B3AE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m */, + ); + path = Models; + sourceTree = ""; + }; + C943B3B12A40A54600AF23C5 /* Views */ = { + isa = PBXGroup; + children = ( + C943B3B22A40A54600AF23C5 /* DWUsernameHeaderView.m */, + C943B3B32A40A54600AF23C5 /* DWPlanetarySystemView.m */, + C943B3B42A40A54600AF23C5 /* DWTextField.m */, + C943B3B52A40A54600AF23C5 /* DWUsernameValidationView.h */, + C943B3B62A40A54600AF23C5 /* DWTextField.h */, + C943B3B72A40A54600AF23C5 /* DWPlanetarySystemView.h */, + C943B3B82A40A54600AF23C5 /* DWUsernameHeaderView.h */, + C943B3B92A40A54600AF23C5 /* DWUsernameValidationView.m */, + ); + path = Views; + sourceTree = ""; + }; + C943B3BA2A40A54600AF23C5 /* RegistrationCompleted */ = { + isa = PBXGroup; + children = ( + C943B3BB2A40A54600AF23C5 /* DWRegistrationCompletedViewController.m */, + C943B3BC2A40A54600AF23C5 /* DWRegistrationCompletedViewController.h */, + ); + path = RegistrationCompleted; + sourceTree = ""; + }; + C943B3C12A40A54600AF23C5 /* ConfirmUsername */ = { isa = PBXGroup; children = ( - 47AE8BE028C1305E00490F5E /* DWExploreTestnetContentsView.h */, - 47AE8BE228C1305E00490F5E /* DWExploreTestnetContentsView.m */, - 47AE8BE328C1305E00490F5E /* DWExploreHeaderView.h */, - 47AE8BE128C1305E00490F5E /* DWExploreHeaderView.m */, + C943B3C22A40A54600AF23C5 /* DWConfirmUsernameViewController.m */, + C943B3C32A40A54600AF23C5 /* DWConfirmUsernameViewController.h */, + C943B3C42A40A54600AF23C5 /* Views */, ); - path = Views; + path = ConfirmUsername; sourceTree = ""; }; - 47AE8C1628C63F9400490F5E /* Views */ = { + C943B3C42A40A54600AF23C5 /* Views */ = { isa = PBXGroup; children = ( - 47AE8C1728C63F9C00490F5E /* PointOfUseDetailsView.swift */, - 47AE8BBC28C1305E00490F5E /* AtmDetailsView.swift */, + C943B3C52A40A54600AF23C5 /* DWConfirmUsernameContentView.m */, + C943B3C62A40A54600AF23C5 /* DWConfirmUsernameContentView.xib */, + C943B3C72A40A54600AF23C5 /* DWConfirmUsernameContentView.h */, ); path = Views; sourceTree = ""; }; - 47AF180329070B660025803E /* Application */ = { + C943B3C92A40A54600AF23C5 /* Welcome */ = { isa = PBXGroup; children = ( - 47FA3AFD293508E3008D58DC /* Syncyng Activity Monitor */, - 47AF180429070B720025803E /* Types.swift */, - 471DD1B7290A92CD00E030C8 /* Tools.swift */, - 478C983B2945801D00FAA0F0 /* Constants.swift */, - 475AE2B82974348F009A1055 /* App.swift */, + C943B3CA2A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.m */, + C943B3CB2A40A54600AF23C5 /* DWInvitationFlowViewController.m */, + C943B3CC2A40A54600AF23C5 /* DWDPWelcomeViewController.m */, + C943B3CD2A40A54600AF23C5 /* DWDPWelcomePageViewController.m */, + C943B3CE2A40A54600AF23C5 /* GetStarted */, + C943B3D72A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.h */, + C943B3D82A40A54600AF23C5 /* DWInvitationFlowViewController.h */, + C943B3D92A40A54600AF23C5 /* DWDPWelcomePageViewController.h */, + C943B3DA2A40A54600AF23C5 /* DWDPWelcomeViewController.h */, + C943B3DB2A40A54600AF23C5 /* Views */, ); - path = Application; + path = Welcome; sourceTree = ""; }; - 47B30D76290BFC840080C326 /* Foundation */ = { + C943B3CE2A40A54600AF23C5 /* GetStarted */ = { isa = PBXGroup; children = ( - 47B30D79290D035B0080C326 /* DashTextAttachment.swift */, + C943B3CF2A40A54600AF23C5 /* DWGetStartedContentViewController.m */, + C943B3D02A40A54600AF23C5 /* DWGetStarted.h */, + C943B3D12A40A54600AF23C5 /* DWGetStartedViewController.m */, + C943B3D22A40A54600AF23C5 /* DWGetStartedContentViewController.h */, + C943B3D32A40A54600AF23C5 /* DWGetStartedViewController.h */, + C943B3D42A40A54600AF23C5 /* Views */, ); - path = Foundation; + path = GetStarted; sourceTree = ""; }; - 47C661B028FDC70F00028A8D /* Transfer Amount */ = { + C943B3D42A40A54600AF23C5 /* Views */ = { isa = PBXGroup; children = ( - 47A50F392913B96F00C70123 /* Model */, - 47838B78290019310003E8AB /* Views */, - 47C661B128FDC72700028A8D /* TransferAmountViewController.swift */, + C943B3D52A40A54600AF23C5 /* DWGetStartedItemView.h */, + C943B3D62A40A54600AF23C5 /* DWGetStartedItemView.m */, ); - path = "Transfer Amount"; + path = Views; sourceTree = ""; }; - 47C6E6E129196B98003FEDF2 /* Views */ = { + C943B3DB2A40A54600AF23C5 /* Views */ = { isa = PBXGroup; children = ( - 47C6E6E229196D48003FEDF2 /* TerritoriesListCell.swift */, + C943B3DC2A40A54600AF23C5 /* DWPassthroughStackView.h */, + C943B3DD2A40A54600AF23C5 /* DWPassthroughView.m */, + C943B3DE2A40A54600AF23C5 /* DWPassthroughStackView.m */, + C943B3DF2A40A54600AF23C5 /* DWPassthroughView.h */, ); path = Views; sourceTree = ""; }; - 47CDEECA294A2B9F008AE06D /* Base */ = { + C943B3E82A40A54600AF23C5 /* Profile */ = { isa = PBXGroup; children = ( - 4789D26F29825F5400BAFEFA /* CoinbaseAmountViewController.swift */, - 47CDEECB294A2BAD008AE06D /* UIViewController+Coinbase.swift */, - 477F501429531C07003C7508 /* ViewModel+Coinbase.swift */, + C943B3E92A40A54600AF23C5 /* DWUserProfileViewController.h */, + C943B3EA2A40A54600AF23C5 /* DWModalUserProfileViewController.h */, + C943B3EB2A40A54600AF23C5 /* Model */, + C943B3F42A40A54600AF23C5 /* DWUserProfileViewController.m */, + C943B3F52A40A54600AF23C5 /* DWModalUserProfileViewController.m */, + C943B3F62A40A54600AF23C5 /* Views */, ); - path = Base; + path = Profile; sourceTree = ""; }; - 47CF469D296540D30067B6EE /* Accounts */ = { + C943B3EB2A40A54600AF23C5 /* Model */ = { isa = PBXGroup; children = ( - 47CF46AA2965B6440067B6EE /* Service */, - 47CF46A2296541120067B6EE /* Account */, - 47CF469E296540E40067B6EE /* AccountRepository.swift */, - 47CF46A0296540EF0067B6EE /* AccountService.swift */, + C943B3EC2A40A54600AF23C5 /* DataSource */, + C943B3F22A40A54600AF23C5 /* DWUserProfileModel.h */, + C943B3F32A40A54600AF23C5 /* DWUserProfileModel.m */, ); - path = Accounts; + path = Model; sourceTree = ""; }; - 47CF46A2296541120067B6EE /* Account */ = { + C943B3EC2A40A54600AF23C5 /* DataSource */ = { isa = PBXGroup; children = ( - 47CF46A429654E190067B6EE /* CBAccount.swift */, + C943B3ED2A40A54600AF23C5 /* DWUserProfileDataSource.h */, + C943B3EE2A40A54600AF23C5 /* DWUserProfileDataSourceObject.m */, + C943B3EF2A40A54600AF23C5 /* DWProfileTxsFetchedDataSource.m */, + C943B3F02A40A54600AF23C5 /* DWUserProfileDataSourceObject.h */, + C943B3F12A40A54600AF23C5 /* DWProfileTxsFetchedDataSource.h */, ); - path = Account; + path = DataSource; sourceTree = ""; }; - 47CF46A629655E7C0067B6EE /* Payment Methods */ = { + C943B3F62A40A54600AF23C5 /* Views */ = { isa = PBXGroup; children = ( - 47CF46A729655E8E0067B6EE /* PaymentMethods.swift */, + C943B3F72A40A54600AF23C5 /* DWStretchyHeaderListCollectionLayout.h */, + C943B3F82A40A54600AF23C5 /* DWUserProfileSendRequestCell.m */, + C943B3F92A40A54600AF23C5 /* DWUserProfileHeaderView.h */, + C943B3FA2A40A54600AF23C5 /* DWPendingContactInfoView.m */, + C943B3FB2A40A54600AF23C5 /* DWUserProfileContactActionsCell.h */, + C943B3FC2A40A54600AF23C5 /* DWUserProfileNavigationTitleView.h */, + C943B3FD2A40A54600AF23C5 /* DWStretchyHeaderListCollectionLayout.m */, + C943B3FE2A40A54600AF23C5 /* DWUserProfileHeaderView.m */, + C943B3FF2A40A54600AF23C5 /* DWUserProfileSendRequestCell.h */, + C943B4002A40A54600AF23C5 /* DWPendingContactInfoView.h */, + C943B4012A40A54600AF23C5 /* DWUserProfileNavigationTitleView.m */, + C943B4022A40A54600AF23C5 /* DWUserProfileContactActionsCell.m */, ); - path = "Payment Methods"; + path = Views; sourceTree = ""; }; - 47CF46A9296584A40067B6EE /* Services */ = { + C943B4032A40A54600AF23C5 /* Items */ = { isa = PBXGroup; children = ( - 47A2A2ED293E622700938DB7 /* CBSecureTokenService.swift */, - 47EEE23A293F041E00049E0B /* CBUserManager.swift */, + C943B4042A40A54600AF23C5 /* Objects */, + C943B41E2A40A54600AF23C5 /* Views */, + C943B4382A40A54600AF23C5 /* Protocols */, + C943B4462A40A54600AF23C5 /* Factory */, ); - path = Services; + path = Items; sourceTree = ""; }; - 47CF46AA2965B6440067B6EE /* Service */ = { + C943B4042A40A54600AF23C5 /* Objects */ = { isa = PBXGroup; children = ( - 47CF46AB2965B65B0067B6EE /* CBAccountManager.swift */, + C943B4052A40A54600AF23C5 /* DWDPNewIncomingRequestObject.m */, + C943B4062A40A54600AF23C5 /* DWDPContactObject.h */, + C943B4072A40A54600AF23C5 /* DWDPEstablishedContactObject.m */, + C943B4082A40A54600AF23C5 /* DWDPPendingRequestObject.h */, + C943B4092A40A54600AF23C5 /* DWDPUserObject.m */, + C943B40A2A40A54600AF23C5 /* DWDPTxObject.h */, + C943B40B2A40A54600AF23C5 /* DWDPRespondedIncomingRequestObject.h */, + C943B40C2A40A54600AF23C5 /* DWDPIncomingRequestObject.m */, + C943B40D2A40A54600AF23C5 /* DWDPContactObject.m */, + C943B40E2A40A54600AF23C5 /* DWDPNewIncomingRequestObject.h */, + C943B40F2A40A54600AF23C5 /* DWDPEstablishedContactObject.h */, + C943B4102A40A54600AF23C5 /* DWDPTxObject.m */, + C943B4112A40A54600AF23C5 /* DWDPUserObject.h */, + C943B4122A40A54600AF23C5 /* DWDPPendingRequestObject.m */, + C943B4132A40A54600AF23C5 /* DWDPIncomingRequestObject.h */, + C943B4142A40A54600AF23C5 /* Notifications */, + C943B41D2A40A54600AF23C5 /* DWDPRespondedIncomingRequestObject.m */, ); - path = Service; + path = Objects; sourceTree = ""; }; - 47E71F73286AED0E00CDF2BD /* Utils */ = { + C943B4142A40A54600AF23C5 /* Notifications */ = { isa = PBXGroup; children = ( - 472D13E0299E1F2F006903F1 /* CSVBuilder.swift */, + C943B4152A40A54600AF23C5 /* DWDPAcceptedRequestNotificationObject.m */, + C943B4162A40A54600AF23C5 /* DWDPOutgoingRequestNotificationObject.m */, + C943B4172A40A54600AF23C5 /* DWDPNewIncomingRequestNotificationObject.m */, + C943B4182A40A54600AF23C5 /* DWDPEstablishedContactNotificationObject.h */, + C943B4192A40A54600AF23C5 /* DWDPAcceptedRequestNotificationObject.h */, + C943B41A2A40A54600AF23C5 /* DWDPOutgoingRequestNotificationObject.h */, + C943B41B2A40A54600AF23C5 /* DWDPEstablishedContactNotificationObject.m */, + C943B41C2A40A54600AF23C5 /* DWDPNewIncomingRequestNotificationObject.h */, ); - path = Utils; + path = Notifications; sourceTree = ""; }; - 47E94B95296D7CF9000FE68E /* Custodial Swaps */ = { + C943B41E2A40A54600AF23C5 /* Views */ = { isa = PBXGroup; children = ( - 4751CABD296EFCB400F63AC4 /* Order Preview */, - 47E94B98296D7F92000FE68E /* Model */, - 47E94B96296D7D5B000FE68E /* CustodialSwapsViewController.swift */, + C943B41F2A40A54600AF23C5 /* DWDPTextStatusCell.m */, + C943B4202A40A54600AF23C5 /* ContentViews */, + C943B42B2A40A54600AF23C5 /* DWDPBasicCell.h */, + C943B42C2A40A54600AF23C5 /* DWDPTxListCell.h */, + C943B42D2A40A54600AF23C5 /* DWDPImageStatusCell.m */, + C943B42E2A40A54600AF23C5 /* DWDPIncomingRequestCell.m */, + C943B42F2A40A54600AF23C5 /* UIFont+DWDPItem.h */, + C943B4302A40A54600AF23C5 /* UICollectionView+DWDPItemDequeue.m */, + C943B4312A40A54600AF23C5 /* DWDPBasicCell.m */, + C943B4322A40A54600AF23C5 /* DWDPTextStatusCell.h */, + C943B4332A40A54600AF23C5 /* DWDPTxListCell.m */, + C943B4342A40A54600AF23C5 /* UICollectionView+DWDPItemDequeue.h */, + C943B4352A40A54600AF23C5 /* UIFont+DWDPItem.m */, + C943B4362A40A54600AF23C5 /* DWDPIncomingRequestCell.h */, + C943B4372A40A54600AF23C5 /* DWDPImageStatusCell.h */, ); - path = "Custodial Swaps"; + path = Views; sourceTree = ""; }; - 47E94B98296D7F92000FE68E /* Model */ = { + C943B4202A40A54600AF23C5 /* ContentViews */ = { isa = PBXGroup; children = ( - 47E94B99296D7F99000FE68E /* CustodialSwapsModel.swift */, + C943B4212A40A54600AF23C5 /* DWDPGenericStatusItemView.h */, + C943B4222A40A54600AF23C5 /* DWDPGenericContactRequestItemView.m */, + C943B4232A40A54600AF23C5 /* DWDPTxItemView.m */, + C943B4242A40A54600AF23C5 /* DWDPGenericImageItemView.h */, + C943B4252A40A54600AF23C5 /* DWDPGenericItemView.m */, + C943B4262A40A54600AF23C5 /* DWDPGenericContactRequestItemView.h */, + C943B4272A40A54600AF23C5 /* DWDPGenericStatusItemView.m */, + C943B4282A40A54600AF23C5 /* DWDPGenericImageItemView.m */, + C943B4292A40A54600AF23C5 /* DWDPTxItemView.h */, + C943B42A2A40A54600AF23C5 /* DWDPGenericItemView.h */, ); - path = Model; + path = ContentViews; sourceTree = ""; }; - 47F006002971B1CE0029EB10 /* Currency Exchanger */ = { + C943B4382A40A54600AF23C5 /* Protocols */ = { isa = PBXGroup; children = ( - 47A2E3A72972B14B0032A63B /* Data Provider */, - 47F006012971B1FE0029EB10 /* CurrencyExchanger.swift */, - 472D13EB299E6579006903F1 /* CurrencyExchanger_Objc.h */, - 472D13EC299E6579006903F1 /* CurrencyExchanger_Objc.m */, + C943B4392A40A54600AF23C5 /* DWDPBasicItem.h */, + C943B43A2A40A54600AF23C5 /* DWDPNewIncomingRequestItem.h */, + C943B43B2A40A54600AF23C5 /* DWDPPendingRequestItem.h */, + C943B43C2A40A54600AF23C5 /* Items Associated Data */, + C943B4402A40A54600AF23C5 /* DWDPNotificationItem.h */, + C943B4412A40A54600AF23C5 /* DWDPEstablishedContactItem.h */, + C943B4422A40A54600AF23C5 /* DWDPTxItem.h */, + C943B4432A40A54600AF23C5 /* DWDPRespondedRequestItem.h */, + C943B4442A40A54600AF23C5 /* DWDPBasicUserItem.h */, + C943B4452A40A54600AF23C5 /* DWDPIncomingRequestItem.h */, ); - path = "Currency Exchanger"; + path = Protocols; sourceTree = ""; }; - 47F2C67B28602D3800C2B774 /* PageSheet */ = { + C943B43C2A40A54600AF23C5 /* Items Associated Data */ = { isa = PBXGroup; children = ( - 47F2C67C28602D4F00C2B774 /* BasePageSheetViewController.swift */, + C943B43D2A40A54600AF23C5 /* DWDPFriendRequestBackedItem.h */, + C943B43E2A40A54600AF23C5 /* DWDPBlockchainIdentityBackedItem.h */, + C943B43F2A40A54600AF23C5 /* DWDPDashpayUserBackedItem.h */, ); - path = PageSheet; + path = "Items Associated Data"; sourceTree = ""; }; - 47F2C67E2860314A00C2B774 /* Details */ = { + C943B4462A40A54600AF23C5 /* Factory */ = { isa = PBXGroup; children = ( - 2A1B7D9E23239C7500BA8C6A /* Model */, - 2A1B7D82232156A600BA8C6A /* Views */, - 47A5145F2848F75B005A8E3E /* TxDetailViewController.swift */, + C943B4472A40A54600AF23C5 /* DWDPSearchItemsFactory.h */, + C943B4482A40A54600AF23C5 /* DWDPContactsItemsFactory.m */, + C943B4492A40A54600AF23C5 /* DWDPSearchItemsFactory.m */, + C943B44A2A40A54600AF23C5 /* DWDPContactsItemsFactory.h */, ); - path = Details; + path = Factory; sourceTree = ""; }; - 47F2C6802860315D00C2B774 /* Reclassify Transactions */ = { + C943B44B2A40A54600AF23C5 /* Invites */ = { isa = PBXGroup; children = ( - 47F2C6812860319400C2B774 /* TxReclassifyTransactionsInfoViewController.swift */, - 47F2C6832860513900C2B774 /* TxReclassifyTransactionsWhereToChangeViewController.swift */, + C943B44C2A40A54600AF23C5 /* DWSendInviteFirstStepViewController.h */, + C943B44D2A40A54600AF23C5 /* Additions */, + C943B4502A40A54600AF23C5 /* DWSendInviteFlowController.h */, + C943B4512A40A54600AF23C5 /* Confirmation */, + C943B4572A40A54600AF23C5 /* DWSendInviteFirstStepViewController.m */, + C943B4582A40A54600AF23C5 /* History */, + C943B46C2A40A54600AF23C5 /* Preview */, + C943B46F2A40A54600AF23C5 /* DWSendInviteFlowController.m */, + C943B4702A40A54600AF23C5 /* Invitation */, ); - path = "Reclassify Transactions"; + path = Invites; sourceTree = ""; }; - 47F4B6C5294842A500AED4C9 /* Confirm Transaction Controller */ = { + C943B44D2A40A54600AF23C5 /* Additions */ = { isa = PBXGroup; children = ( - 47F4B6CB29485A6700AED4C9 /* Views */, - 47F4B6C829484C8600AED4C9 /* Model */, - 47F4B6C6294842DF00AED4C9 /* ConfirmOrderController.swift */, + C943B44E2A40A54600AF23C5 /* DPAlertViewController+DWInvite.h */, + C943B44F2A40A54600AF23C5 /* DPAlertViewController+DWInvite.m */, ); - path = "Confirm Transaction Controller"; + path = Additions; sourceTree = ""; }; - 47F4B6C829484C8600AED4C9 /* Model */ = { + C943B4512A40A54600AF23C5 /* Confirmation */ = { isa = PBXGroup; children = ( - 47F4B6C929484C9800AED4C9 /* ConfirmOrderModel.swift */, + C943B4522A40A54600AF23C5 /* DWConfirmInvitationViewController.m */, + C943B4532A40A54600AF23C5 /* DWConfirmInvitationContentView.m */, + C943B4542A40A54600AF23C5 /* DWConfirmInvitationContentView.h */, + C943B4552A40A54600AF23C5 /* DWConfirmInvitationViewController.h */, + C943B4562A40A54600AF23C5 /* DWConfirmInvitationContentView.xib */, ); - path = Model; + path = Confirmation; sourceTree = ""; }; - 47F4B6CB29485A6700AED4C9 /* Views */ = { + C943B4582A40A54600AF23C5 /* History */ = { isa = PBXGroup; children = ( - 47F4B6CC29485A8B00AED4C9 /* ConfirmOrderCells.swift */, + C943B4592A40A54600AF23C5 /* DWInvitationHistoryViewController.m */, + C943B45A2A40A54600AF23C5 /* Models */, + C943B45F2A40A54600AF23C5 /* Filter */, + C943B4642A40A54600AF23C5 /* Views */, + C943B46B2A40A54600AF23C5 /* DWInvitationHistoryViewController.h */, ); - path = Views; + path = History; sourceTree = ""; }; - 47FA3AFD293508E3008D58DC /* Syncyng Activity Monitor */ = { + C943B45A2A40A54600AF23C5 /* Models */ = { isa = PBXGroup; children = ( - 47FA3AFE29350929008D58DC /* SyncingActivityMonitor.swift */, + C943B45B2A40A54600AF23C5 /* DWInvitationHistoryFilter.h */, + C943B45C2A40A54600AF23C5 /* DWInvitationHistoryModel.m */, + C943B45D2A40A54600AF23C5 /* DWInvitationHistoryModel.h */, + C943B45E2A40A54600AF23C5 /* DWInvitationItem.h */, ); - path = "Syncyng Activity Monitor"; + path = Models; sourceTree = ""; }; - 47FA3B0029364743008D58DC /* Networking */ = { + C943B45F2A40A54600AF23C5 /* Filter */ = { isa = PBXGroup; children = ( - 47FA3B0129364991008D58DC /* HTTPClient.swift */, + C943B4602A40A54600AF23C5 /* DWHistoryFilterContentView.m */, + C943B4612A40A54600AF23C5 /* DWHistoryFilterViewController.m */, + C943B4622A40A54600AF23C5 /* DWHistoryFilterContentView.h */, + C943B4632A40A54600AF23C5 /* DWHistoryFilterViewController.h */, ); - path = Networking; + path = Filter; sourceTree = ""; }; - 75D5F3B5191EC270004AB296 = { + C943B4642A40A54600AF23C5 /* Views */ = { isa = PBXGroup; children = ( - C9D2C9552A320AC100D15901 /* DashPay */, - 2ADF83FB23632D55008459A7 /* Shared */, - 75D5F3C7191EC270004AB296 /* DashWallet */, - BAE12BE81B2DEE7F00895CC5 /* TodayExtension */, - 75D5F3EC191EC270004AB296 /* DashWalletTests */, - BA913BDF1BD57E4D005A7C0E /* WatchApp */, - BA913BEE1BD57E4D005A7C0E /* WatchApp Extension */, - 2A4663012279DC2F0027533B /* DashWalletScreenshotsUITests */, - 75D5F3C0191EC270004AB296 /* Frameworks */, - 75D5F3BF191EC270004AB296 /* Products */, - EBFC2EA47915CD4F5BA81564 /* Pods */, + C943B4652A40A54600AF23C5 /* DWHistoryHeaderView.h */, + C943B4662A40A54600AF23C5 /* DWInvitationTableViewCell.m */, + C943B4672A40A54600AF23C5 /* DWCreateInvitationButton.m */, + C943B4682A40A54600AF23C5 /* DWInvitationTableViewCell.h */, + C943B4692A40A54600AF23C5 /* DWHistoryHeaderView.m */, + C943B46A2A40A54600AF23C5 /* DWCreateInvitationButton.h */, ); + path = Views; sourceTree = ""; }; - 75D5F3BF191EC270004AB296 /* Products */ = { + C943B46C2A40A54600AF23C5 /* Preview */ = { isa = PBXGroup; children = ( - 75D5F3BE191EC270004AB296 /* dashwallet.app */, - 75D5F3E5191EC270004AB296 /* DashWalletTests.xctest */, - BAE12BE51B2DEE7F00895CC5 /* TodayExtension.appex */, - BA913BDE1BD57E4D005A7C0E /* WatchApp.app */, - BA913BEA1BD57E4D005A7C0E /* WatchApp Extension.appex */, - 2A4663002279DC2F0027533B /* DashWalletScreenshotsUITests.xctest */, - C9D2C9532A320AA000D15901 /* dashpay.app */, + C943B46D2A40A54600AF23C5 /* DWInvitationPreviewViewController.h */, + C943B46E2A40A54600AF23C5 /* DWInvitationPreviewViewController.m */, ); - name = Products; + path = Preview; sourceTree = ""; }; - 75D5F3C0191EC270004AB296 /* Frameworks */ = { + C943B4702A40A54600AF23C5 /* Invitation */ = { isa = PBXGroup; children = ( - 472CEE022925259B00656B48 /* libPods-WatchApp Extension.a */, - 0FC75A8E28F03E22000E4858 /* libDashSync.a */, - 2A5279BB23D994BC00F856D3 /* CoreNFC.framework */, - 2AA08533237D6CF500797F95 /* CloudKit.framework */, - 2A0C6C852362EFAE00C86F37 /* libDashSync-Shared.a */, - FB3E9F5F236125F600C09C5C /* BackgroundTasks.framework */, - 2ABCA9172357A61B00092C09 /* Foundation.framework */, - 2AD1CE9822DE63FA00C99324 /* GameplayKit.framework */, - FB4FA9C222505DD60060B017 /* AudioToolbox.framework */, - FBEF3AED2182395800917AB6 /* DashSync.framework */, - FB248B621F79BB7C00405AE0 /* SafariServices.framework */, - FB248B5C1F73803100405AE0 /* UserNotifications.framework */, - FBF3F4301E42B02800C7248E /* ImageIO.framework */, - FBF3F42E1E42B01E00C7248E /* CoreGraphics.framework */, - FBF3F42C1E42B00C00C7248E /* UIKit.framework */, - FBF3F42A1E42AF8F00C7248E /* QuartzCore.framework */, - 225383001C694D7400968BEE /* CoreLocation.framework */, - 22D3613C1C56F2CD0057CF76 /* libsqlite3.tbd */, - 222E7F571C46E9BE009AB45D /* SystemConfiguration.framework */, - 222E7F551C46E9B8009AB45D /* Security.framework */, - 222040C51C1A1940005CE1C3 /* WebKit.framework */, - 22B6A4471C0E963900673913 /* libbz2.tbd */, - 75F2E0B61BE2D5F000EAE861 /* Accelerate.framework */, - BAE12BE61B2DEE7F00895CC5 /* NotificationCenter.framework */, - A169BE797EC811F83373CCB9 /* Pods_dashwallet_no_watch.framework */, - BEAE29AA65F429DD9EF1A1A7 /* libPods-DashWalletTests.a */, - 02771AC5DDCA0A1749C6A05B /* libPods-TodayExtension.a */, - 07283055DE20FE578E399BE7 /* libPods-WatchApp.a */, - C47D5A9D319D41B450A9B96B /* libPods-WatchApp Extension.a */, - 0CDD4C961516ED20BC9F01FA /* libPods-DashWalletScreenshotsUITests.a */, - CE89DF632BC53160BB8FBED1 /* libPods-dashpay.a */, - 17427514C25A58AB4AEDF999 /* libPods-dashwallet.a */, + C943B4712A40A54600AF23C5 /* DWInvitationLinkBuilder.m */, + C943B4722A40A54600AF23C5 /* BaseInvitationViewController.swift */, + C943B4732A40A54600AF23C5 /* SuccessInvitationViewController.swift */, + C943B4742A40A54600AF23C5 /* DWInvitationLinkBuilder.h */, + C943B4752A40A54600AF23C5 /* Views */, ); - name = Frameworks; + path = Invitation; sourceTree = ""; }; - 75D5F3C7191EC270004AB296 /* DashWallet */ = { + C943B4752A40A54600AF23C5 /* Views */ = { isa = PBXGroup; children = ( - 2A44313B22CF62FC009BAF7F /* Sources */, - 2A4430F022CBD566009BAF7F /* Resources */, - FB6DD3811F7FA48500BC1E4D /* dashwallet.entitlements */, - 75D5F3C8191EC270004AB296 /* Supporting Files */, - FB66977E212C0B940034BE4F /* LaunchScreen.storyboard */, - 2A4430E522CBB6EC009BAF7F /* AppDelegate.h */, - 2A4430E622CBB6EC009BAF7F /* AppDelegate.m */, + C943B4762A40A54600AF23C5 /* InvitationBottomView.swift */, + C943B4772A40A54600AF23C5 /* InvitationTopView.swift */, + C943B4782A40A54600AF23C5 /* DWSuccessInvitationView.m */, + C943B4792A40A54600AF23C5 /* DWInvitationMessageView.h */, + C943B47A2A40A54600AF23C5 /* DWInvitationActionsView.m */, + C943B47B2A40A54600AF23C5 /* DWSuccessInvitationView.h */, + C943B47C2A40A54600AF23C5 /* SuccessInvitationTopView.swift */, + C943B47D2A40A54600AF23C5 /* DWInvitationMessageView.m */, + C943B47E2A40A54600AF23C5 /* DWInvitationActionsView.h */, ); - path = DashWallet; + path = Views; sourceTree = ""; }; - 75D5F3C8191EC270004AB296 /* Supporting Files */ = { + C943B47F2A40A54600AF23C5 /* Views */ = { isa = PBXGroup; children = ( - C94F5E8729D3E7E30034FD57 /* GoogleService-Info.plist */, - 47AE8BAF28BFF28400490F5E /* explore.db */, - 47A5145E2848F75A005A8E3E /* dashwallet-Bridging-Header.h */, - 2A8F420821BED16300858B91 /* DashSyncCurrentCommit */, - 75D5F3C9191EC270004AB296 /* Info.plist */, - 757E09971ADB8EEB006FD352 /* Localizable.strings */, - 2ADC722723B5547000D9DD37 /* Localizable.stringsdict */, - 75D5F3CD191EC270004AB296 /* main.m */, - 75D5F3CF191EC270004AB296 /* DashWallet-Prefix.pch */, + C943B4802A40A54600AF23C5 /* DWNetworkUnavailableView.m */, + C943B4812A40A54600AF23C5 /* UIColor+DWDashPay.m */, + C943B4822A40A54600AF23C5 /* DWDPSmallContactView.h */, + C943B4842A40A54600AF23C5 /* DWDashPayAnimationView.h */, + C943B4852A40A54600AF23C5 /* UIColor+DWDashPay.h */, + C943B4862A40A54600AF23C5 /* DWNetworkUnavailableView.h */, + C943B4872A40A54600AF23C5 /* DWDPSmallContactView.m */, + C943B4882A40A54600AF23C5 /* DWDashPayAnimationView.m */, ); - name = "Supporting Files"; + path = Views; sourceTree = ""; }; - 75D5F3EC191EC270004AB296 /* DashWalletTests */ = { + C943B48A2A40A54600AF23C5 /* Error */ = { isa = PBXGroup; children = ( - 47A184E4299CC19A0017E32C /* rates.json */, - 75CE50B51B5216F100DBC18C /* Info.plist */, - 471DD1BE290AE9F000E030C8 /* DashWalletTests-Bridging-Header.h */, - 2A2120EB22145CAE009906DC /* DWAmountInputValidatorTests.m */, - 471DD1BB290AE30B00E030C8 /* String+DashWalletTests.swift */, - 47976E492987772700612988 /* AmountObjectTests.swift */, + C943B48B2A40A54600AF23C5 /* DWNetworkErrorViewController.m */, + C943B48C2A40A54600AF23C5 /* DWNetworkErrorViewController.h */, ); - path = DashWalletTests; + path = Error; sourceTree = ""; }; - BA913BDF1BD57E4D005A7C0E /* WatchApp */ = { + C943B48E2A40A54600AF23C5 /* Global */ = { isa = PBXGroup; children = ( - BA913BE01BD57E4D005A7C0E /* Interface.storyboard */, - BA913BE31BD57E4D005A7C0E /* Assets.xcassets */, - BA7B537A1BDD1DCF00355E8D /* LoadingIndicator.xcassets */, - BA913BE51BD57E4D005A7C0E /* Info.plist */, + C943B48F2A40A54600AF23C5 /* UIImageView+DWDPAvatar.m */, + C943B4902A40A54600AF23C5 /* DSBlockchainIdentity+DWDisplayName.h */, + C943B4912A40A54600AF23C5 /* DWDashPayContactsActions.m */, + C943B4922A40A54600AF23C5 /* DWDashPayContactsUpdater.h */, + C943B4932A40A54600AF23C5 /* DSBlockchainIdentity+DWDisplayName.m */, + C943B4942A40A54600AF23C5 /* UIImageView+DWDPAvatar.h */, + C943B4952A40A54600AF23C5 /* DWDashPayContactsActions.h */, + C943B4962A40A54600AF23C5 /* DWDashPayContactsUpdater.m */, + C943B4972A40A54600AF23C5 /* Notifications */, ); - path = WatchApp; + path = Global; sourceTree = ""; }; - BA913BEE1BD57E4D005A7C0E /* WatchApp Extension */ = { + C943B4972A40A54600AF23C5 /* Notifications */ = { isa = PBXGroup; children = ( - BA913BF11BD57E4D005A7C0E /* ExtensionDelegate.swift */, - BAA6E3EA1BD5C78500773205 /* DWSetupInfoInterfaceController.swift */, - BAA6E3EE1BD5CA0E00773205 /* BRAWBalanceInterfaceController.swift */, - BAA6E3F01BD5CA5900773205 /* BRAWReceiveMoneyInterfaceController.swift */, - BA6645951BD924EB007A6BB1 /* BRAWTransactionRowControl.swift */, - BA4A72661BE11AFA00E39C01 /* BRAWWeakTimerTarget.swift */, - 22F45A0A1C30EAB700B07A15 /* BRAWKeypad.swift */, - 2AB7F7E823846F6000C173AD /* DWMainInterfaceController.swift */, - 2AB7F7E62384676200C173AD /* DWWatchDataManager.swift */, - 2AB7F7EA2384752C00C173AD /* DWTxInfoDisplayableInterfaceController.swift */, - BA913BF71BD57E4D005A7C0E /* Info.plist */, - BAC7B6C41BD9C9B600165B84 /* WatchApp Extension-Bridging-Header.h */, + C943B4982A40A54600AF23C5 /* DWNotificationsProvider.m */, + C943B4992A40A54600AF23C5 /* DWNotificationsData.m */, + C943B49A2A40A54600AF23C5 /* DWNotificationsFetchedDataSource.h */, + C943B49B2A40A54600AF23C5 /* DWNotificationsProvider.h */, + C943B49C2A40A54600AF23C5 /* DWNotificationsFetchedDataSource.m */, + C943B49D2A40A54600AF23C5 /* DWNotificationsData.h */, ); - path = "WatchApp Extension"; + path = Notifications; sourceTree = ""; }; - BAE12BE81B2DEE7F00895CC5 /* TodayExtension */ = { + C943B49E2A40A54600AF23C5 /* Notifications */ = { isa = PBXGroup; children = ( - 2A741DC02363993600840ADF /* DWTodayViewController.h */, - BAE12C051B2DEEF700895CC5 /* DWTodayViewController.m */, - 759816E519357D6F005060EA /* BRBubbleView.h */, - 759816E619357D6F005060EA /* BRBubbleView.m */, - 2A741DC123639A9700840ADF /* TodayExtension.storyboard */, - BA54D3CD1B2EA74000C9CB28 /* TodayExtensionAssets.xcassets */, - BAE12BEA1B2DEE7F00895CC5 /* Info.plist */, - FB1212A91FFFB0F3000E407E /* dashwalletTodayExtension.entitlements */, + C943B49F2A40A54600AF23C5 /* Cells */, + C943B4A42A40A54600AF23C5 /* DWListCollectionLayout.h */, + C943B4A52A40A54600AF23C5 /* DWNotificationsViewController.h */, + C943B4A62A40A54600AF23C5 /* Models */, + C943B4A92A40A54600AF23C5 /* DWListCollectionLayout.m */, + C943B4AA2A40A54600AF23C5 /* DWNotificationsViewController.m */, ); - path = TodayExtension; + path = Notifications; sourceTree = ""; }; - C3CA2029247E4AC300158074 /* Notifications */ = { + C943B49F2A40A54600AF23C5 /* Cells */ = { isa = PBXGroup; children = ( - 2A7AF3192480E619001D74F9 /* Models */, - C3CA202D247E549B00158074 /* Cells */, - C3CA202A247E4AF300158074 /* DWNotificationsViewController.h */, - C3CA202B247E4AF300158074 /* DWNotificationsViewController.m */, - 2A827B7024B5CA1800A42042 /* DWListCollectionLayout.h */, - 2A827B7124B5CA1800A42042 /* DWListCollectionLayout.m */, + C943B4A02A40A54600AF23C5 /* DWNotificationsInvitationCell.h */, + C943B4A12A40A54600AF23C5 /* DWNoNotificationsCell.m */, + C943B4A22A40A54600AF23C5 /* DWNotificationsInvitationCell.m */, + C943B4A32A40A54600AF23C5 /* DWNoNotificationsCell.h */, ); - path = Notifications; + path = Cells; sourceTree = ""; }; - C3CA202D247E549B00158074 /* Cells */ = { + C943B4A62A40A54600AF23C5 /* Models */ = { isa = PBXGroup; children = ( - C3CA202E247E54C400158074 /* DWNoNotificationsCell.h */, - C3CA202F247E54C400158074 /* DWNoNotificationsCell.m */, + C943B4A72A40A54600AF23C5 /* DWNotificationsModel.m */, + C943B4A82A40A54600AF23C5 /* DWNotificationsModel.h */, ); - path = Cells; + path = Models; sourceTree = ""; }; - C3DAD265246AA6CF0001624F /* ScreenshotWarning */ = { + C943B5392A40A6BE00AF23C5 /* DPAlert */ = { isa = PBXGroup; children = ( - C3DAD266246AA6F10001624F /* DWScreenshotWarningViewController.h */, - C3DAD267246AA6F10001624F /* DWScreenshotWarningViewController.m */, + C943B53A2A40A6BE00AF23C5 /* DPAlertChildContentsView.h */, + C943B53B2A40A6BE00AF23C5 /* DPAlertViewController.m */, + C943B53C2A40A6BE00AF23C5 /* DPAlertChildContentsView.m */, + C943B53D2A40A6BE00AF23C5 /* DPAlertViewController.h */, ); - path = ScreenshotWarning; + path = DPAlert; sourceTree = ""; }; - C3DAD2BD24747F2D0001624F /* Base */ = { + C943B5402A40AFC400AF23C5 /* Tx Details */ = { isa = PBXGroup; children = ( - C3DAD2BE24747F580001624F /* DWSearchViewController.h */, - C3DAD2BF24747F580001624F /* DWSearchViewController.m */, + C943B5412A40AFD000AF23C5 /* DWTxDetailPopupViewController.h */, + C943B5422A40AFD100AF23C5 /* DWTxDetailPopupViewController.m */, ); - path = Base; + path = "Tx Details"; sourceTree = ""; }; - C909615629F29C7500002D82 /* Cell */ = { + C943B5452A40B52500AF23C5 /* Autolayout */ = { isa = PBXGroup; children = ( - C909615829F29C9200002D82 /* KeysOverviewCell.swift */, + C943B5592A40DD3F00AF23C5 /* NSArray+DWFlatten.h */, + C943B55A2A40DD4000AF23C5 /* NSArray+DWFlatten.m */, + C943B5472A40B52F00AF23C5 /* NSLayoutConstraint+DWAutolayout.h */, + C943B5462A40B52F00AF23C5 /* NSLayoutConstraint+DWAutolayout.m */, + C943B5492A40B52F00AF23C5 /* UIView+DWAutolayout.h */, + C943B5482A40B52F00AF23C5 /* UIView+DWAutolayout.m */, ); - path = Cell; + path = Autolayout; sourceTree = ""; }; - C909615729F29C7E00002D82 /* Model */ = { + C943B54F2A40C21A00AF23C5 /* Filter View */ = { isa = PBXGroup; children = ( - C909614C29EFF7D600002D82 /* WalletKeysOverviewModel.swift */, + C943B5522A40C23500AF23C5 /* DWFilterHeaderView.xib */, ); - path = Model; + path = "Filter View"; + sourceTree = ""; + }; + C943B5552A40DA2F00AF23C5 /* Containers */ = { + isa = PBXGroup; + children = ( + C943B5562A40DA3600AF23C5 /* DWFullScreenModalControllerViewController.h */, + C943B5572A40DA3700AF23C5 /* DWFullScreenModalControllerViewController.m */, + ); + path = Containers; sourceTree = ""; }; C9D2C9552A320AC100D15901 /* DashPay */ = { @@ -6415,6 +7113,12 @@ C9D2C9582A386A5B00D15901 /* Presentation */ = { isa = PBXGroup; children = ( + C943B5552A40DA2F00AF23C5 /* Containers */, + C943B54F2A40C21A00AF23C5 /* Filter View */, + C943B5402A40AFC400AF23C5 /* Tx Details */, + C943B3482A40A4C500AF23C5 /* InfoPopup */, + C943B33D2A408DB900AF23C5 /* Menu */, + C943B2B22A408CAF00AF23C5 /* Profile */, C9D2C96D2A38777A00D15901 /* Setup */, C9D2C9592A386A6700D15901 /* Home */, C9D2C95A2A386D5200D15901 /* Shared */, @@ -6425,6 +7129,7 @@ C9D2C9592A386A6700D15901 /* Home */ = { isa = PBXGroup; children = ( + C943B5442A40B4AF00AF23C5 /* DWDashPayReadyProtocol.h */, C9D2C9662A3875AB00D15901 /* Model */, C9D2C95F2A386D9700D15901 /* Views */, ); @@ -6434,6 +7139,14 @@ C9D2C95A2A386D5200D15901 /* Shared */ = { isa = PBXGroup; children = ( + C943B5452A40B52500AF23C5 /* Autolayout */, + C943B5372A40A65B00AF23C5 /* DWScrollingViewController.h */, + C943B5362A40A65B00AF23C5 /* DWScrollingViewController.m */, + C943B3452A409FFA00AF23C5 /* DWDPAvatarView.h */, + C943B3442A409FFA00AF23C5 /* DWDPAvatarView.m */, + C943B3412A409F9E00AF23C5 /* UIImageView+DWDPAvatar.h */, + C943B3422A409F9E00AF23C5 /* UIImageView+DWDPAvatar.m */, + C943B5392A40A6BE00AF23C5 /* DPAlert */, C9D2C95B2A386D6C00D15901 /* Buttons */, ); path = Shared; @@ -6442,6 +7155,8 @@ C9D2C95B2A386D6C00D15901 /* Buttons */ = { isa = PBXGroup; children = ( + C943B54D2A40B6B500AF23C5 /* DWColoredButton.h */, + C943B54C2A40B6B500AF23C5 /* DWColoredButton.m */, C9D2C95C2A386D7E00D15901 /* DWBasePressableControl.h */, C9D2C95D2A386D7E00D15901 /* DWBasePressableControl.m */, ); @@ -6469,8 +7184,6 @@ C9D2C9662A3875AB00D15901 /* Model */ = { isa = PBXGroup; children = ( - C9D2C96A2A38762700D15901 /* DWDPUpdateProfileModel.h */, - C9D2C96B2A38762800D15901 /* DWDPUpdateProfileModel.m */, C9D2C9682A3875BA00D15901 /* DWCurrentUserProfileModel.h */, C9D2C9672A3875BA00D15901 /* DWCurrentUserProfileModel.m */, ); @@ -6867,7 +7580,6 @@ 47838B7F290160860003E8AB /* ExploreDash.storyboard in Resources */, 474C720D298A19D100475CA6 /* TxDetailHeaderCell.xib in Resources */, 2AB231D42196E27300A6E7E6 /* StartStoryboard.storyboard in Resources */, - 2A60C9452444BF3A00AF72CF /* DWConfirmUsernameContentView.xib in Resources */, 2A1AF6DF23C7681B00442AF5 /* DWShortcutCollectionViewCell~iphone.xib in Resources */, 2A4431D622D52F67009BAF7F /* DWInfoTextCell.xib in Resources */, 2A44314922CF82EF009BAF7F /* BiometricAuth.storyboard in Resources */, @@ -6896,7 +7608,6 @@ 47AE8BB028BFF28700490F5E /* explore.db in Resources */, 2A0C69F423179C0F001B8C90 /* DWConfirmPaymentContentView.xib in Resources */, 0F36937F2919A70B007F4E91 /* Coinbase.storyboard in Resources */, - C3DAD2C9247538AA0001624F /* DWTitleActionHeaderView.xib in Resources */, 2A10EB3E2358BDA500C38B61 /* ImportWalletInfo.storyboard in Resources */, C9F42FAD29DC115A001BC549 /* ReceiveContentView.xib in Resources */, 2AD1CE8622DC9B7300C99324 /* VerifySeedPhrase.storyboard in Resources */, @@ -6972,19 +7683,18 @@ C9D2C90E2A320AA000D15901 /* TxDetailHeaderCell.xib in Resources */, C9D2C9652A38733B00D15901 /* DPAssets.xcassets in Resources */, C9D2C90F2A320AA000D15901 /* StartStoryboard.storyboard in Resources */, - C9D2C9102A320AA000D15901 /* DWConfirmUsernameContentView.xib in Resources */, C9D2C9112A320AA000D15901 /* DWShortcutCollectionViewCell~iphone.xib in Resources */, C9D2C9122A320AA000D15901 /* DWInfoTextCell.xib in Resources */, C9D2C9132A320AA000D15901 /* BiometricAuth.storyboard in Resources */, C9D2C9142A320AA000D15901 /* TxListEmptyTableViewCell.xib in Resources */, C9D2C9152A320AA000D15901 /* Payments.storyboard in Resources */, - C9D2C9162A320AA000D15901 /* DWFilterHeaderView.xib in Resources */, C9D2C9172A320AA000D15901 /* Localizable.strings in Resources */, C9D2C9182A320AA000D15901 /* LockScreen.storyboard in Resources */, C9D2C9192A320AA000D15901 /* UpholdMainStoryboard.storyboard in Resources */, C9D2C91A2A320AA000D15901 /* Migrations.bundle in Resources */, C9D2C91B2A320AA000D15901 /* TxDetailTaxCategoryCell.xib in Resources */, C9D2C91C2A320AA000D15901 /* Localizable.stringsdict in Resources */, + C943B4C12A40A54600AF23C5 /* DWTitleActionHeaderView.xib in Resources */, C9D2C91D2A320AA000D15901 /* ShortcutsView.xib in Resources */, C9D2C91E2A320AA000D15901 /* HomeBalanceView.xib in Resources */, C9D2C91F2A320AA000D15901 /* DWDPRegistrationStatusTableViewCell.xib in Resources */, @@ -7001,7 +7711,6 @@ C9D2C92A2A320AA000D15901 /* explore.db in Resources */, C9D2C92B2A320AA000D15901 /* DWConfirmPaymentContentView.xib in Resources */, C9D2C92C2A320AA000D15901 /* Coinbase.storyboard in Resources */, - C9D2C92D2A320AA000D15901 /* DWTitleActionHeaderView.xib in Resources */, C9D2C92E2A320AA000D15901 /* ImportWalletInfo.storyboard in Resources */, C9D2C92F2A320AA000D15901 /* ReceiveContentView.xib in Resources */, C9D2C9302A320AA000D15901 /* VerifySeedPhrase.storyboard in Resources */, @@ -7011,6 +7720,7 @@ C9D2C9342A320AA000D15901 /* Onboarding.storyboard in Resources */, C9D2C9352A320AA000D15901 /* OperationStatus.storyboard in Resources */, C9D2C9362A320AA000D15901 /* UpholdLogoutTutorialStoryboard.storyboard in Resources */, + C943B5542A40C23500AF23C5 /* DWFilterHeaderView.xib in Resources */, C9D2C9372A320AA000D15901 /* UpholdAuthStoryboard.storyboard in Resources */, C9D2C9382A320AA000D15901 /* CrowdNode.storyboard in Resources */, C9D2C9392A320AA000D15901 /* DWDPRegistrationDoneTableViewCell.xib in Resources */, @@ -7027,7 +7737,9 @@ C9D2C9442A320AA000D15901 /* uphold-logout.jpg in Resources */, C9D2C9452A320AA000D15901 /* AmountPreviewView.xib in Resources */, C9D2C9462A320AA000D15901 /* TxDetailActionCell.xib in Resources */, + C943B4DA2A40A54600AF23C5 /* DWConfirmUsernameContentView.xib in Resources */, C9D2C9472A320AA000D15901 /* CNCreateAccountCell.xib in Resources */, + C943B50F2A40A54600AF23C5 /* DWConfirmInvitationContentView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -7313,11 +8025,13 @@ "${PODS_ROOT}/Target Support Files/Pods-dashpay/Pods-dashpay-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/DashSync/DashSync.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DashSync.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -7428,12 +8142,10 @@ 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 */, 47AE8BF228C1306000490F5E /* PointOfUseItemCell.swift in Sources */, 47083B3229892D770010AF71 /* DSTransaction+DashWallet.swift in Sources */, - 2AE8B66423CF09000016F221 /* DWConfirmUsernameViewController.m in Sources */, C9F42F9F29DA82E5001BC549 /* PayableViewController.swift in Sources */, 47AE8C1528C6378E00490F5E /* HairlineView.swift in Sources */, 2A913E9523A3F75F006A2A59 /* DWInitialViewController.m in Sources */, @@ -7441,13 +8153,10 @@ 2A7A7BD62348CB6600451078 /* DWSettingsMenuViewController.m in Sources */, 47AE8C1E28C7491C00490F5E /* AboutViewController.swift in Sources */, 2A0C69DA2314727F001B8C90 /* UISpringTimingParameters+DWInit.m in Sources */, - 2A9D72AC249A0EE000F79CD8 /* DWDPNewIncomingRequestObject.m in Sources */, 2A7A7BDD2348DC0A00451078 /* DWToolsMenuModel.m in Sources */, - C3CA202C247E4AF300158074 /* DWNotificationsViewController.m in Sources */, 47C661AD28F972BD00028A8D /* NumberKeyboardButton.swift in Sources */, 11AE3DD82997C599000856EE /* IsDefaultEmail.swift in Sources */, 2A1B7DC323266C8400BA8C6A /* DWHomeViewController+DWSecureWalletDelegateImpl.m in Sources */, - 2AEC5CB52493D87D00F4A689 /* DWNotificationsProvider.m in Sources */, 2A9FFF2A2233E60F00956D5F /* DWUpholdAccountObject.m in Sources */, 2A9FFE812230FF4700956D5F /* DWFormTableViewController.m in Sources */, C909614D29EFF7D600002D82 /* WalletKeysOverviewModel.swift in Sources */, @@ -7477,7 +8186,6 @@ 2A74EFFB2305464C00C475EB /* DWRecoverTextView.m in Sources */, 4709C315287EA11900B4BD48 /* TxUserInfo.swift in Sources */, C91E91AE29FFC8A1003E7883 /* ExtendedPublicKeysViewController.swift in Sources */, - 2A7AF3182480E35A001D74F9 /* DWContactsFetchedDataSource.m in Sources */, 47AE8BFE28C1306000490F5E /* AtmListViewController.swift in Sources */, 47A514602848F75B005A8E3E /* TxDetailViewController.swift in Sources */, 472D13EA299E5396006903F1 /* NSAttributedString+Builder.swift in Sources */, @@ -7485,10 +8193,8 @@ 2A858A11237EE89C0097A7B5 /* DWPhoneWCSessionManager.m in Sources */, 47C661AB28F9707A00028A8D /* NumberKeyboard.swift in Sources */, 2A4431E622D73617009BAF7F /* DWSeedPhraseTitledView.m in Sources */, - 2A7AF38924829AF2001D74F9 /* UICollectionView+DWDPItemDequeue.m in Sources */, 119E8D0C2907C61400D406C1 /* CrowdNodeTopUpTx.swift in Sources */, C9F42FB429DD86FB001BC549 /* BackupInfoItemView.swift in Sources */, - C3DAD2D52476886D0001624F /* DWContactsSearchInfoHeaderView.m in Sources */, 118A1393291AA21B002641E4 /* AccountCreatingController.swift in Sources */, 2A4E534422F02BC300E5168A /* UIView+DWReuseHelper.m in Sources */, 2A913E9C23A53B8E006A2A59 /* DWExtendedContainerViewController.m in Sources */, @@ -7496,19 +8202,15 @@ 0F71317528F4365C0072F454 /* ServiceEntryPointModel.swift in Sources */, 47AE8B9528BFACA100490F5E /* ExploreDash.swift in Sources */, 2A7A7BE02348DC1900451078 /* DWToolsMenuViewController.m in Sources */, - 2AE8B66B23CF0F390016F221 /* DWUsernamePendingViewController.m in Sources */, 4789D22F2981067C00BAFEFA /* UpholdAmountModel.swift in Sources */, 2A0C69BD23140779001B8C90 /* DWModalInteractiveTransition.m in Sources */, - 2AD6E5632487E16400B52F14 /* DWRequestsViewController.m in Sources */, 2A9FFEA12230FF4700956D5F /* DWUpholdMainViewController.m in Sources */, - 2A7AF36324825A0C001D74F9 /* DWDPUserObject.m in Sources */, BAA4843C1B3EFFAF0075C749 /* UIImage+Utils.m in Sources */, 47083B3629893E700010AF71 /* CNCreateAccountCell.swift in Sources */, 2A4430EE22CBBAC9009BAF7F /* DWSetupViewController.m in Sources */, 47AE8BAB28BFAE5800490F5E /* ExploreDatabaseConnection.swift in Sources */, 111C3C4C296C51B500788E18 /* WithdrawalLimitsController.swift in Sources */, 0F6EDFCD28C896BD000427E7 /* CoinbaseTransactionResponse.swift in Sources */, - 2A7AF3152480DA51001D74F9 /* DWIncomingFetchedDataSource.m in Sources */, 4709C31F28818BAE00B4BD48 /* TxDetailModel.swift in Sources */, 11BD737E28E6BD7400A34022 /* NewAccountViewController.swift in Sources */, 47AE8BFA28C1306000490F5E /* PointOfUseListModel.swift in Sources */, @@ -7534,7 +8236,6 @@ 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 */, @@ -7543,8 +8244,6 @@ 1193FF3629602835004EA8D7 /* CrowdNodeTransferModel.swift in Sources */, 2A44312622CCC14F009BAF7F /* DWSetPinViewController.m in Sources */, 47F4B6C7294842DF00AED4C9 /* ConfirmOrderController.swift in Sources */, - 2A1AE78E23F4668B00179A6E /* DWUsernameHeaderView.m in Sources */, - 2A7AF3992482E32E001D74F9 /* DWDashPayContactsActions.m in Sources */, 2A10EB442358D2CA00C38B61 /* DWResetWalletInfoViewController.m in Sources */, 2AD46232232A286000C71557 /* DWLockScreenModel.m in Sources */, 2AD1CE8A22DC9C6F00C99324 /* DWVerifySeedPhraseContentView.m in Sources */, @@ -7566,11 +8265,8 @@ 2A74EFFE2305763F00C475EB /* DWRecoverModel.m in Sources */, 2A4431C822D4D92A009BAF7F /* DWCenteredTableView.m in Sources */, 2A913E6F23A26663006A2A59 /* DWOnboardingViewController.m in Sources */, - 2A9E7DCA23F6BD5200CDA1EE /* DWUsernameValidationView.m in Sources */, 111C3C52296D620D00788E18 /* CrowdNode+UserDefaults.swift in Sources */, - 2A7AF37224826CDF001D74F9 /* DWDPAcceptedRequestNotificationObject.m in Sources */, 2A44313922CE2CB9009BAF7F /* DWPinView.m in Sources */, - 2A9E7DCE23F6C01A00CDA1EE /* DWUsernameValidationRule.m in Sources */, 2AB3416523A8213C004E37A7 /* DWBaseReceiveModel.m in Sources */, 2A1F6412238D650200A9B505 /* DWSegmentSlider.m in Sources */, 0F6EDFCA28C896BD000427E7 /* CoinbasePlaceBuyOrderRequest.swift in Sources */, @@ -7585,7 +8281,6 @@ 2A9FFE062230FF2B00956D5F /* DWUpholdCardObject.m in Sources */, 2A2CD72222F9B571008C7BC9 /* DWIntrinsicCollectionView.m in Sources */, 2A913E7F23A30609006A2A59 /* DWRootModelStub.m in Sources */, - 2A7AF3662482666C001D74F9 /* DWDPIncomingRequestObject.m in Sources */, 2A2CD6EE22F48159008C7BC9 /* UIDevice+DashWallet.m in Sources */, 47CF46AC2965B65B0067B6EE /* CBAccountManager.swift in Sources */, 47522F522927CBEE00EE143E /* FailedOperationStatusViewController.swift in Sources */, @@ -7599,11 +8294,9 @@ 2AD1CE9422DD078600C99324 /* DWSeedPhraseRow.m in Sources */, 0F71317928F436ED0072F454 /* ServiceOverviewViewController.swift in Sources */, 4751CAC4296EFE9500F63AC4 /* AccountListModel.swift in Sources */, - 2A7AF380248280CE001D74F9 /* DWDPSearchItemsFactory.m in Sources */, 2A3DC86E239717C2004B3DBA /* DWRecoverWalletCommand.m in Sources */, 2A9FFE832230FF4700956D5F /* DWSwitcherFormTableViewCell.m in Sources */, 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 */, @@ -7616,12 +8309,9 @@ 2A8B9E7D23034AC100FF8653 /* DWAppGroupOptions.m in Sources */, 2A913EA423A799F3006A2A59 /* DWTransactionListDataItemObject.m in Sources */, 2A9FFE052230FF2B00956D5F /* DWUpholdAPIProvider.m in Sources */, - 2A56EEFB2417E30F002C32F3 /* DWConfirmUsernameContentView.m in Sources */, 47F2C67D28602D4F00C2B774 /* BasePageSheetViewController.swift in Sources */, - 2A827B7224B5CA1800A42042 /* DWListCollectionLayout.m in Sources */, 119E8D0429051F9900D406C1 /* CrowdNodeRequest.swift in Sources */, 0F6EDFD228C896BD000427E7 /* CoinbaseExchangeRateResponse.swift in Sources */, - 2A6688FD24BCB739008E10F0 /* DWDPTxItemView.m in Sources */, 2A0C69EF2316B7F5001B8C90 /* DWConfirmPaymentContentView.m in Sources */, 117728A3297A7D24006F1553 /* CrowdNodePortalItem.swift in Sources */, 47C661B628FE75A700028A8D /* BaseViewController.swift in Sources */, @@ -7633,10 +8323,7 @@ 4730586E295AB3BE004641DA /* UIDevice+Compatibility.swift in Sources */, 2A4431E222D7219E009BAF7F /* DWSeedWordView.m in Sources */, 2A7F3B1F238C651200DEA3EF /* DWSecurityStatusView.m in Sources */, - 2AD6E5602487E13F00B52F14 /* DWRequestsContentViewController.m in Sources */, 2ACCD8D1231D33EA00A96B62 /* DWAnimatedShapeLayer.m in Sources */, - 2ADC9D732462D4AD001D7C0D /* DWUserProfileNavigationTitleView.m in Sources */, - 2A9E7DC723F6928C00CDA1EE /* DWTextField.m in Sources */, C3DAD268246AA6F10001624F /* DWScreenshotWarningViewController.m in Sources */, 4751CACD297021EE00F63AC4 /* ConvertCryptoOrderPreviewController.swift in Sources */, 4708119F2990F56F003FCA3D /* TransactionDataItem.swift in Sources */, @@ -7644,8 +8331,6 @@ 2AD1CE8D22DCB3B600C99324 /* DWVerifySeedPhraseModel.m in Sources */, C9F42FA629DC092B001BC549 /* ReceiveViewController.swift in Sources */, 118B7A3F29865A3A00FBB6CC /* ConfirmationTransactionQRController.swift in Sources */, - 2ADB396924223D9B00A6F898 /* DWDashPayAnimationView.m in Sources */, - 2AEC5CBE24940EC200F4A689 /* DWDPOutgoingRequestNotificationObject.m in Sources */, 47E4B7B7292F85E800CE0EB6 /* Numbers+Dash.swift in Sources */, 478C983229433C5700FAA0F0 /* PaymentMethodsController.swift in Sources */, 47AE8C1328C5F0A700490F5E /* PointOfUseDetailsViewController.swift in Sources */, @@ -7655,15 +8340,11 @@ 2A4E535C22F335C200E5168A /* DWTransactionListDataProvider.m in Sources */, 2A1B7DA82323AF4300BA8C6A /* DWModalPopupTransition.m in Sources */, 2A7F3B1B238C646600DEA3EF /* DWAdvancedSecurityViewController.m in Sources */, - 2AE8B64423CDC0F50016F221 /* DWInputUsernameViewController.m in Sources */, 4751CAD6297024ED00F63AC4 /* OrderPreviewModel.swift in Sources */, - 2A7AF34924823167001D74F9 /* DWDPGenericImageItemView.m in Sources */, 2A885FD6244DFEF100B9F679 /* UIView+DWFindConstraints.m in Sources */, 2A1B7DA52323AC1F00BA8C6A /* DWModalPopupPresentationController.m in Sources */, 11517C8A294B11DD004FC7BF /* CrowdNodeBalance.swift in Sources */, 471DD1BA290A962B00E030C8 /* String+DashWallet.swift in Sources */, - C3DAD2C3247512BA0001624F /* DWBaseContactsContentViewController.m in Sources */, - 2AE2F0F9245C16C8001DD722 /* DWUserProfileViewController.m in Sources */, 2A0C69D723143B2F001B8C90 /* DWBaseModalViewController.m in Sources */, 2A7A7BD02348A34800451078 /* DWSecurityMenuModel.m in Sources */, C909615929F29C9200002D82 /* KeysOverviewCell.swift in Sources */, @@ -7672,7 +8353,6 @@ 47838B8C29068F110003E8AB /* BaseResponse.swift in Sources */, 2AB3417723A92978004E37A7 /* DWDemoAdvancedSecurityViewController.m in Sources */, 474C721A298A803200475CA6 /* TxListTableViewCell.swift in Sources */, - 2ADC9D722462D4AD001D7C0D /* DWStretchyHeaderListCollectionLayout.m in Sources */, 111C3C50296D5A4700788E18 /* TxWithinTimePeriod.swift in Sources */, 0F6EDFC528C896BD000427E7 /* CoinbaseSwapeTradeRequest.swift in Sources */, 2A44311722CBF0B2009BAF7F /* UIFont+DWFont.m in Sources */, @@ -7681,18 +8361,14 @@ 4751136F28D9B50E00223B77 /* CoinbaseInfoViewController.swift in Sources */, 2A9FFE7E2230FF4600956D5F /* DWSelectorFormCellModel.m in Sources */, FB2E5537218BA161003A1B7C /* DWVersionManager.m in Sources */, - 2A7AF378248270A4001D74F9 /* DWDPEstablishedContactNotificationObject.m in Sources */, 2A0C69C623142AA4001B8C90 /* DWModalPresentationController.m in Sources */, 47AE8C1128C5430300490F5E /* PointOfUseInfoViewController.swift in Sources */, 47C661B428FDCF7800028A8D /* ActionButtonViewController.swift in Sources */, 4774DCE528F4668B008CF87D /* PortalServiceItemCell.swift in Sources */, - 2A7AF35624824F97001D74F9 /* DWDPImageStatusCell.m in Sources */, 111B8C00299BD973004A4129 /* WithdrawalConfirmationController.swift in Sources */, 2A7A7BC92347E0D700451078 /* DWBaseFormTableViewCell.m in Sources */, 4751CAC7296FAEBB00F63AC4 /* AccountCell.swift in Sources */, 2A913EA823A79AD2006A2A59 /* DWTransactionListDataProviderStub.m in Sources */, - 2A885FD02449F37A00B9F679 /* DWUserSearchModel.m in Sources */, - 2A7AF3502482374D001D74F9 /* DWDPBasicCell.m in Sources */, 47AE8BFF28C1306000490F5E /* DWExploreHeaderView.m in Sources */, 0F6EDFC628C896BD000427E7 /* CoinbaseBaseIDForCurrencyResponse.swift in Sources */, 2A7A7BE42348E85400451078 /* DWBaseActionButton.m in Sources */, @@ -7701,7 +8377,6 @@ 2A36A88B2350A05B0014DC60 /* DWBackupSeedPhraseViewController.m in Sources */, 114CFED0296469D9005F421B /* CrowdNodeDepositTx.swift in Sources */, 2A9FFE9E2230FF4700956D5F /* DWUpholdViewController.m in Sources */, - 2A7AF31C2480E6AD001D74F9 /* DWNotificationsModel.m in Sources */, 2A9FFF192233E56E00956D5F /* DWIntrinsicTableView.m in Sources */, 2A913E6623A11DFE006A2A59 /* DWURLActions.m in Sources */, C9903A5429E6A5F600535A4E /* KeysOverviewViewController.swift in Sources */, @@ -7727,7 +8402,6 @@ 47AE8BF528C1306000490F5E /* MerchantListLocationOffCell.swift in Sources */, 47AE8C0028C1306000490F5E /* DWExploreTestnetContentsView.m in Sources */, 2A7A7BB6234792A600451078 /* DWMainMenuContentView.m in Sources */, - 2A7AF35324823EC8001D74F9 /* DWDPIncomingRequestCell.m in Sources */, 1186092529759C4B00279FCC /* CrowdNodeAPIConfirmationTx.swift in Sources */, 2A913E9023A31713006A2A59 /* UIViewController+DWEmbedding.m in Sources */, 2A2CD71622F98FEA008C7BC9 /* DWShadowView.m in Sources */, @@ -7740,9 +8414,7 @@ 2A913E8223A30623006A2A59 /* DWHomeModelStub.m in Sources */, 2ADC9D1C24603C4F001D7C0D /* UISearchBar+DWAdditions.m in Sources */, 2A74F0042305C59700C475EB /* UIViewController+DWTxFilter.m in Sources */, - 2ACCA3AD24BE117300DB32DE /* DWProfileTxsFetchedDataSource.m in Sources */, 117ED4A828ED66F9006E3EE4 /* TransactionFilter.swift in Sources */, - C3DAD2C024747F580001624F /* DWSearchViewController.m in Sources */, 111C3C54296D6A2D00788E18 /* CrowdNodeWithdrawalReceivedTx.swift in Sources */, 47AE8C0328C1F0C600490F5E /* PointOfUseListSegmentedCell.swift in Sources */, 2A913E6823A1473A006A2A59 /* DWURLRequestHandler.m in Sources */, @@ -7753,18 +8425,14 @@ 47AE8BA628BFADD900490F5E /* AtmDAO.swift in Sources */, 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 */, 2A741DC62363A06000840ADF /* DWURLParser.m in Sources */, - 2A7AF36F24826737001D74F9 /* DWDPContactObject.m in Sources */, 2A8B9E6C23029D2B00FF8653 /* DWPaymentInputBuilder.m in Sources */, 2AC92C841FEB0A6D008CAEE0 /* DWQRScanViewController.m in Sources */, 2A9FFE802230FF4600956D5F /* DWSelectorFormTableViewCell.m in Sources */, - 2A3CCEF9242BB1B900300AF8 /* DWRegistrationCompletedViewController.m in Sources */, - 2ADC9D712462D4AD001D7C0D /* DWUserProfileHeaderView.m in Sources */, 47305872295C971F004641DA /* UIViewController+AlertPresenting.swift in Sources */, 2A858A0F237EE89C0097A7B5 /* DSWatchTransactionDataObject.m in Sources */, 47FA3B0229364991008D58DC /* HTTPClient.swift in Sources */, @@ -7774,43 +8442,33 @@ 47A2A2EC293E618600938DB7 /* CBUser.swift in Sources */, 47A2A2EE293E622700938DB7 /* CBSecureTokenService.swift in Sources */, C9F42FA129DA95F5001BC549 /* PayTableViewCell.swift in Sources */, - C3DAD2C8247538AA0001624F /* DWTitleActionHeaderView.m in Sources */, - 2ACCA3B524BF280A00DB32DE /* DWDPTxObject.m in Sources */, 0F71317728F436920072F454 /* ServiceOverviewTableCell.swift in Sources */, - 2AD6E5532487D50200B52F14 /* DWContactsModel.m in Sources */, 47838B87290670630003E8AB /* CoinbaseEntryPointViewController.swift in Sources */, 2AD1CE8022DC92BF00C99324 /* NSString+DWTextSize.m in Sources */, 47AE8C0528C1F74A00490F5E /* PointOfUseListFiltersCell.swift in Sources */, 47081199298CF57D003FCA3D /* TransactionListDataSource.swift in Sources */, - 2A5BD5952451DCAF00688A8D /* DWAllowedCharactersUsernameValidationRule.m in Sources */, 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 */, - 2A7AF3752482703C001D74F9 /* DWDPNewIncomingRequestNotificationObject.m in Sources */, 4709C312287E78BD00B4BD48 /* SeedDB.swift in Sources */, 2ACCD8F0231ECDE000A96B62 /* DWPinField.m in Sources */, 2AD1CEA222DFC80900C99324 /* DWCenteredScrollView.m in Sources */, - 2A7AF36C248266FB001D74F9 /* DWDPEstablishedContactObject.m in Sources */, 2A9172C425233DC50024B4C5 /* DWPhraseRepairViewController.m in Sources */, 2A44314C22CF8801009BAF7F /* DWBiometricAuthModel.m in Sources */, 2ACCD8ED231ECCF000A96B62 /* DWNumberKeyboardInputViewAudioFeedback.m in Sources */, 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 */, C917023F29D44E0B008C034D /* SendReceivePageController.swift in Sources */, 2A7A7BD92348CB7300451078 /* DWSettingsMenuModel.m in Sources */, - 2A80F3D924DC55CD003E3B1E /* DWUserProfileSendRequestCell.m in Sources */, 478A2C7228DC909C00AD1420 /* BaseNavigationController.swift in Sources */, 47CDEECC294A2BAD008AE06D /* UIViewController+Coinbase.swift in Sources */, - 2A5BD59E2451F39500688A8D /* DWCheckExistenceUsernameValidationRule.m in Sources */, 472D13E1299E1F2F006903F1 /* CSVBuilder.swift in Sources */, 2A5E4548243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.m in Sources */, 0F6EDFC428C896BD000427E7 /* CoinbaseUserAuthInformation.swift in Sources */, @@ -7835,13 +8493,11 @@ 4709C318287EA23000B4BD48 /* TxUserInfoDAO.swift in Sources */, 2A0C69E323155A0E001B8C90 /* DWModalContentView.m in Sources */, 2A9FFE042230FF2B00956D5F /* DWUpholdConstants.m in Sources */, - 2A5BD5982451DD0700688A8D /* DWMaxLengthUsernameValidationRule.m in Sources */, 0F6EDFC728C896BD000427E7 /* CoinbaseSwapeTradeResponse.swift in Sources */, 2AC92C871FEB0AE8008CAEE0 /* DWQRScanView.m in Sources */, 4774DCDF28F43AB4008CF87D /* ServiceItem.swift in Sources */, 47EE171729560DC200BA1986 /* ErrorPresentable.swift in Sources */, 2ACCD8E2231E507900A96B62 /* DWPinInputStepView.m in Sources */, - 2AC52AD6241BB5FC00D9A829 /* DWDashPaySetupFlowController.m in Sources */, 2A74EFED2305318000C475EB /* DWRecoverViewController.m in Sources */, 2A919F9F24A65CE00018C9A3 /* DWDPAmountContactView.m in Sources */, 2A913EAE23A7AC86006A2A59 /* DWBaseTransactionListDataProvider.m in Sources */, @@ -7851,14 +8507,12 @@ 47F005FF297164600029EB10 /* UITableView+DashWallet.swift in Sources */, 47C661B228FDC72700028A8D /* TransferAmountViewController.swift in Sources */, C94F5E8E29D404850034FD57 /* ShortcutCell.swift in Sources */, - 2A7AF34424822AE0001D74F9 /* DWDPGenericContactRequestItemView.m in Sources */, 2A44312122CCA2A0009BAF7F /* UIColor+DWStyle.m in Sources */, FBEF3AF121823CD800917AB6 /* DWEnvironment.m in Sources */, 47AE8BF028C1306000490F5E /* AtmItemCell.swift in Sources */, 0F6EDFD728C896BD000427E7 /* CoinbaseService.swift in Sources */, 2A8B9E5622FEDF2900FF8653 /* DWControllerCollectionView.m in Sources */, 2A9FFDF52230FF1A00956D5F /* SFSafariViewController+DashWallet.m in Sources */, - 2AD6E54D2487CE0100B52F14 /* DWContactsDataSourceObject.m in Sources */, 474C7218298A422500475CA6 /* TransactionItemView.swift in Sources */, 474C7211298A1A9500475CA6 /* UIView+Reuse.swift in Sources */, 2A9FFE072230FF2B00956D5F /* DWUpholdClient.m in Sources */, @@ -7869,20 +8523,14 @@ 2A6300452328D07500827825 /* DWLockPinInputView.m in Sources */, 2A0C69AC23125074001B8C90 /* UIView+DWHUD.m in Sources */, 2A8B9E6822FFE4CC00FF8653 /* DWPayOptionModel.m in Sources */, - 2A7AF36924826681001D74F9 /* DWDPRespondedIncomingRequestObject.m in Sources */, 0F6EDFC928C896BD000427E7 /* CoinbaseTokenResponse.swift in Sources */, C9F42FB229DD5141001BC549 /* BackupInfoViewController.swift in Sources */, - 2A1AE79223F468CD00179A6E /* DWPlanetarySystemView.m in Sources */, 47C6E6E5291A68B6003FEDF2 /* AppliedFiltersView.swift in Sources */, 2A7A7BAE234770C900451078 /* DWCaptureSessionManager.m in Sources */, 2A11F59F2194BD6200E7B563 /* DWDataMigrationManager.m in Sources */, - 2AD6E56A2487E1DB00B52F14 /* DWRequestsModel.m in Sources */, - 2A7AF35924824FEB001D74F9 /* DWDPTextStatusCell.m in Sources */, - 2AD6E55A2487D9AF00B52F14 /* DWContactsViewController.m in Sources */, 11EA863A29952E9800ABD7B4 /* OutlinedTextField.swift in Sources */, 472D13E6299E3C82006903F1 /* AmountPreviewView.swift in Sources */, 4759D514292FEFFC002F20DC /* ModalNavigationController.swift in Sources */, - 2A919F9C24A4DCAD0018C9A3 /* DWDPSmallContactView.m in Sources */, 47A50F3B2913BC0900C70123 /* TransferAmountModel.swift in Sources */, 47305870295AD62B004641DA /* NavigationBarAppearanceCustomizable.swift in Sources */, 471DD1B6290A901200E030C8 /* AmountObject.swift in Sources */, @@ -7892,8 +8540,6 @@ 2A7A7C1D234B771400451078 /* DWLocalCurrencyModel.m in Sources */, 2A7A7BC3234797FC00451078 /* DWMainMenuModel.m in Sources */, FB8ACEB622E0502200EE5035 /* DWUpholdMainnetConstants.m in Sources */, - 2A885FCC2449F08700B9F679 /* DWUserSearchResultViewController.m in Sources */, - 2A7AF34C24823315001D74F9 /* DWDPGenericStatusItemView.m in Sources */, C9F42FA929DC09CF001BC549 /* Style.swift in Sources */, C917024129D462C6008C034D /* PayViewController.swift in Sources */, 479E7924287C00EC00D0F7D7 /* DatabaseConnection.swift in Sources */, @@ -7901,7 +8547,6 @@ 2A74EFF823053ECE00C475EB /* DWIntrinsicTextView.m in Sources */, 47AE8C0128C1306000490F5E /* DWExploreTestnetViewController.m in Sources */, 471A2608289ACDA00056B7B2 /* AddressUserInfoDAO.swift in Sources */, - 2AEC5CC2249755BB00F4A689 /* DWDashPayContactsUpdater.m in Sources */, 2A0C69D323143568001B8C90 /* DWModalTransition.m in Sources */, 2A9FFE9F2230FF4700956D5F /* DWUpholdMainModel.m in Sources */, 47AE8BF928C1306000490F5E /* MerchantsDataProvider.swift in Sources */, @@ -7912,18 +8557,14 @@ 47AE8BFB28C1306000490F5E /* ExploreMapAnnotationView.swift in Sources */, 2A1B7DC0232669FF00BA8C6A /* DWHomeViewController+DWBackupReminder.m in Sources */, 2A4E533822F023AB00E5168A /* DWHomeModel.m in Sources */, - 2ADC9D7624640A2B001D7C0D /* DWUserProfileModel.m in Sources */, 2A9CEBB922E1FA1000A50237 /* DWHomeViewController.m in Sources */, 1186092129758B2F00279FCC /* IsAddressInUse.swift in Sources */, - C3DAD26F246D46BF0001624F /* DWFetchedResultsDataSource.m in Sources */, 2A8B9E4022FD75C000FF8653 /* DWSegmentedControl.m in Sources */, 47AE8BF328C1306000490F5E /* MerchantItemCell.swift in Sources */, 478C98372943A60000FAA0F0 /* PaymentMethodCell.swift in Sources */, - 2AD6E5572487D8C000B52F14 /* DWContactsContentViewController.m in Sources */, 47EEE23B293F041E00049E0B /* CBUserManager.swift in Sources */, C909615B29F6535300002D82 /* DerivationPathKeysHeaderView.swift in Sources */, 4751CAD02970224D00F63AC4 /* ConvertCryptoOrderPreviewModel.swift in Sources */, - C3DAD2CC24757A210001624F /* DWContactsSearchDataSourceObject.m in Sources */, 47A2E3A92972B15F0032A63B /* RatesProvider.swift in Sources */, 47A2A2E9293E612900938DB7 /* CBAuth.swift in Sources */, 2A307CBF22E8A44200A18347 /* DWButton.m in Sources */, @@ -7937,15 +8578,12 @@ C94F5E8A29D3FCCF0034FD57 /* ShortcutAction.swift in Sources */, 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 */, 472D13DF299DF5C6006903F1 /* TaxReportGenerator.swift in Sources */, 119E8D122909513F00D406C1 /* CrowdNodeError.swift in Sources */, 111C3C4E296C52F800788E18 /* WithdrawalLimit.swift in Sources */, 2A8B9E6522FFE43500FF8653 /* DWPayModel.m in Sources */, - 2A885FC82449B66500B9F679 /* DWSearchStateViewController.m in Sources */, - 2ACCA3A824BE0C7D00DB32DE /* DWDPTxListCell.m in Sources */, 47AE8C1828C63F9C00490F5E /* PointOfUseDetailsView.swift in Sources */, 47E4F7C7297596D9006BEA68 /* DSChain+DashWallet.m in Sources */, 1193FF3B2961960B004EA8D7 /* FromLabel.swift in Sources */, @@ -7954,32 +8592,23 @@ 0F6EDFCF28C896BD000427E7 /* CoinbaseAccountAddress.swift in Sources */, 2A7A7BBE2347950700451078 /* DWMainMenuTableViewCell.m in Sources */, 1141E4C2291BB12200ACDA9E /* CrowdNodeTransferViewController.swift in Sources */, - 2AB2373824488DB80081B62C /* DWUserSearchViewController.m in Sources */, 478483E829629C0700E05A5A /* CBAuthInterop.swift in Sources */, - 2A7AF38F2482BDF1001D74F9 /* UIFont+DWDPItem.m in Sources */, 4789D27029825F5400BAFEFA /* CoinbaseAmountViewController.swift in Sources */, 2A9FFE8E2230FF4700956D5F /* DWUpholdAuthViewController.m in Sources */, 2A1F640F238D5C0900A9B505 /* DWSegmentSliderFormCellModel.m in Sources */, 2AD1CE6022D8E9D900C99324 /* DWSeedPhraseView.m in Sources */, - 2A3CCEFC242BB1DD00300AF8 /* DWDPAvatarView.m in Sources */, 47B30D7C29100ABA0080C326 /* UIStackView+DashWallet.swift in Sources */, - 2ADC9D7C24644E46001D7C0D /* DWUserProfileContactActionsCell.m in Sources */, 47AE8B9C28BFAD3600490F5E /* ExplorePointOfUse.swift in Sources */, - 2ACCA3B224BE3CDC00DB32DE /* DWUserProfileDataSourceObject.m in Sources */, 2A56EF0624193AEB002C32F3 /* DWDashPayModel.m in Sources */, 117ED4A128EC86E0006E3EE4 /* TransactionObserver.swift in Sources */, 2A4431D522D52F67009BAF7F /* DWInfoTextCell.m in Sources */, 47EE17192959CDC200BA1986 /* ColorizedText.swift in Sources */, - 2A7AF37B2482756D001D74F9 /* DWDPPendingRequestObject.m in Sources */, - 2A80F39524D86201003E3B1E /* DWModalUserProfileViewController.m in Sources */, C9F42FA429DBC6E5001BC549 /* PaymentsViewController.swift in Sources */, - 2A7AF3832482954C001D74F9 /* DWDPContactsItemsFactory.m in Sources */, 4774DCDD28F43A68008CF87D /* ServiceDataSource.swift in Sources */, 47838B7A2900196F0003E8AB /* ConverterView.swift in Sources */, 2A0C69CA23142E11001B8C90 /* DWModalBaseAnimation.m in Sources */, C9F42FB829DFC507001BC549 /* SpendableTransaction.swift in Sources */, 11ED906B29681773003784F9 /* StakingInfoDialogController.swift in Sources */, - 2AE8B64123CDB98A0016F221 /* DWCreateUsernameViewController.m in Sources */, 0F36937E2919A5DB007F4E91 /* TwoFactorAuthViewController.swift in Sources */, 471A260A289ACDF70056B7B2 /* Taxes.swift in Sources */, 2AD1CE9722DD0E8E00C99324 /* DWSeedWordModel+DWLayoutSupport.m in Sources */, @@ -7991,8 +8620,6 @@ 2A7A7BCD2347F01B00451078 /* DWSecurityMenuViewController.m in Sources */, 11BD738128E7356100A34022 /* CrowdNode.swift in Sources */, 47AE8B9F28BFAD8200490F5E /* SQLite+ExloreDash.swift in Sources */, - 2A4E534B22F03A9E00E5168A /* DWFilterHeaderView.m in Sources */, - 2A951CE423D1B92C00602824 /* DWBaseContactsModel.m in Sources */, 2A1F6415238FEEA900A9B505 /* DWAdvancedSecurityModel.m in Sources */, 47AE8BF828C1306000490F5E /* AtmDataProvider.swift in Sources */, 110C67952921147F006B580C /* GettingStartedViewController.swift in Sources */, @@ -8010,7 +8637,6 @@ 4751CAD92970509600F63AC4 /* ConvertCryptoOrderPreviewViews.swift in Sources */, 2A8F422021BEFEEA00858B91 /* DWAboutModel.m in Sources */, 47083B3B298948B70010AF71 /* CNCreateAccountTxDetailsViewController.swift in Sources */, - 2AB7303E24D0BC0400DCB420 /* UIColor+DWDashPay.m in Sources */, 1141E4C5291FDC7A00ACDA9E /* WelcomeToCrowdNodeViewController.swift in Sources */, 47C6E6E329196D48003FEDF2 /* TerritoriesListCell.swift in Sources */, 2A4E531D22EA49FE00E5168A /* DWProgressView.m in Sources */, @@ -8071,7 +8697,9 @@ C9D2C6962A320AA000D15901 /* DWBaseViewController.m in Sources */, C9D2C6972A320AA000D15901 /* DWBiometricAuthViewController.m in Sources */, C9D2C6982A320AA000D15901 /* CrowdNodeWebService.swift in Sources */, + C943B4EE2A40A54600AF23C5 /* DWStretchyHeaderListCollectionLayout.m in Sources */, C9D2C6992A320AA000D15901 /* DWStartViewController.m in Sources */, + C943B4C02A40A54600AF23C5 /* DWContactsSearchInfoHeaderView.m in Sources */, C9D2C69A2A320AA000D15901 /* CrowdNodeAPI.swift in Sources */, C9D2C69B2A320AA000D15901 /* DWStartModel.m in Sources */, C9D2C69C2A320AA000D15901 /* DWAdvancedSecurityModelStub.m in Sources */, @@ -8083,88 +8711,112 @@ C9D2C6A22A320AA000D15901 /* DWSegmentSliderFormTableViewCell.m in Sources */, C9D2C6A32A320AA000D15901 /* ApiCode.swift in Sources */, C9D2C6A42A320AA000D15901 /* CoinbaseEntryPointModel.swift in Sources */, + C943B4F02A40A54600AF23C5 /* DWUserProfileNavigationTitleView.m in Sources */, C9D2C6A52A320AA000D15901 /* SyncingAlertViewController.swift in Sources */, - C9D2C6A62A320AA000D15901 /* DWDashPayConstants.m in Sources */, C9D2C6A72A320AA000D15901 /* CrowdNodeResponse.swift in Sources */, C9D2C6A82A320AA000D15901 /* FileManager+DashWallet.swift in Sources */, C9D2C6A92A320AA000D15901 /* PointOfUseItemCell.swift in Sources */, C9D2C6AA2A320AA000D15901 /* DSTransaction+DashWallet.swift in Sources */, - C9D2C6AB2A320AA000D15901 /* DWConfirmUsernameViewController.m in Sources */, + C943B4C52A40A54600AF23C5 /* DWUserSearchModel.m in Sources */, C9D2C6AC2A320AA000D15901 /* PayableViewController.swift in Sources */, C9D2C6AD2A320AA000D15901 /* HairlineView.swift in Sources */, + C943B51B2A40A54600AF23C5 /* BaseInvitationViewController.swift in Sources */, C9D2C6AE2A320AA000D15901 /* DWInitialViewController.m in Sources */, + C943B52D2A40A54600AF23C5 /* DWDashPayContactsUpdater.m in Sources */, C9D2C6AF2A320AA000D15901 /* PortalViewController.swift in Sources */, C9D2C6B02A320AA000D15901 /* DWSettingsMenuViewController.m in Sources */, C9D2C6B12A320AA000D15901 /* AboutViewController.swift in Sources */, + C943B5022A40A54600AF23C5 /* DWDPGenericStatusItemView.m in Sources */, C9D2C6B22A320AA000D15901 /* UISpringTimingParameters+DWInit.m in Sources */, - C9D2C6B32A320AA000D15901 /* DWDPNewIncomingRequestObject.m in Sources */, C9D2C6B42A320AA000D15901 /* DWToolsMenuModel.m in Sources */, - C9D2C6B52A320AA000D15901 /* DWNotificationsViewController.m in Sources */, C9D2C6B62A320AA000D15901 /* NumberKeyboardButton.swift in Sources */, C9D2C6B72A320AA000D15901 /* IsDefaultEmail.swift in Sources */, C9D2C6B82A320AA000D15901 /* DWHomeViewController+DWSecureWalletDelegateImpl.m in Sources */, - C9D2C6B92A320AA000D15901 /* DWNotificationsProvider.m in Sources */, C9D2C6BA2A320AA000D15901 /* DWUpholdAccountObject.m in Sources */, C9D2C6BB2A320AA000D15901 /* DWFormTableViewController.m in Sources */, + C943B34D2A40A4C500AF23C5 /* DWInfoPopupViewController.m in Sources */, + C943B32A2A408CED00AF23C5 /* DWAvatarExternalSourceView.m in Sources */, C9D2C6BC2A320AA000D15901 /* WalletKeysOverviewModel.swift in Sources */, C9D2C6BD2A320AA000D15901 /* UIAssembly.swift in Sources */, + C943B4CE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m in Sources */, + C943B4B32A40A54600AF23C5 /* DWContactsSearchDataSourceObject.m in Sources */, C9D2C6BE2A320AA000D15901 /* DWLocationManager.swift in Sources */, + C943B3432A409F9E00AF23C5 /* UIImageView+DWDPAvatar.m in Sources */, + C943B5212A40A54600AF23C5 /* SuccessInvitationTopView.swift in Sources */, C9D2C6BF2A320AA000D15901 /* FetchingNextPageCell.swift in Sources */, + C943B4CA2A40A54600AF23C5 /* DWUsernameValidationRule.m in Sources */, + C943B3172A408CED00AF23C5 /* DWErrorUpdatingUserProfileView.m in Sources */, + C943B4E32A40A54600AF23C5 /* DWPassthroughStackView.m in Sources */, + C943B32E2A408CED00AF23C5 /* DWAvatarGravatarViewController.m in Sources */, C9D2C6C02A320AA000D15901 /* Transaction.swift in Sources */, + C943B3232A408CED00AF23C5 /* DWFaceDetector.m in Sources */, C9D2C6C12A320AA000D15901 /* AllMerchantLocationsViewController.swift in Sources */, C9D2C6C22A320AA000D15901 /* DWPaymentOutput.m in Sources */, C9D2C6C32A320AA000D15901 /* DWPayModelStub.m in Sources */, C9D2C6C42A320AA000D15901 /* DWRecoverContentView.m in Sources */, C9D2C6C52A320AA000D15901 /* DWSeedPhraseViewLayout.m in Sources */, + C943B5252A40A54600AF23C5 /* DWDPSmallContactView.m in Sources */, C9D2C6C62A320AA000D15901 /* Coinbase+Error.swift in Sources */, C9D2C6C72A320AA000D15901 /* DWPaymentOutput+DWView.m in Sources */, + C943B5092A40A54600AF23C5 /* UIFont+DWDPItem.m in Sources */, C9D2C6C82A320AA000D15901 /* UIView+DWRecursiveSubview.m in Sources */, C9D2C6C92A320AA000D15901 /* AddressStatus.swift in Sources */, C9D2C6CA2A320AA000D15901 /* ExtendedPublicKeysModel.swift in Sources */, C9D2C6CB2A320AA000D15901 /* OrderPreviewViewController.swift in Sources */, + C943B5342A40A54600AF23C5 /* DWListCollectionLayout.m in Sources */, C9D2C6CC2A320AA000D15901 /* ActivePaymentMethodView.swift in Sources */, C9D2C6CD2A320AA000D15901 /* BasicInfoController.swift in Sources */, C9D2C6CE2A320AA000D15901 /* DWPreviewSeedPhraseModel.m in Sources */, + C943B5222A40A54600AF23C5 /* DWInvitationMessageView.m in Sources */, C9D2C6CF2A320AA000D15901 /* DWModalPresentationAnimation.m in Sources */, C9D2C6D02A320AA000D15901 /* UINavigationController+CrowdNode.swift in Sources */, C9D2C6D12A320AA000D15901 /* DWReceiveModel.m in Sources */, C9D2C6D22A320AA000D15901 /* TransactionWrapper.swift in Sources */, C9D2C6D32A320AA000D15901 /* Transactions.swift in Sources */, + C943B5282A40A54600AF23C5 /* DWNetworkErrorViewController.m in Sources */, + C943B4D42A40A54600AF23C5 /* DWUsernameValidationView.m in Sources */, C9D2C6D42A320AA000D15901 /* DWRecoverTextView.m in Sources */, + C943B5012A40A54600AF23C5 /* DWDPGenericItemView.m in Sources */, C9D2C6D52A320AA000D15901 /* TxUserInfo.swift in Sources */, C9D2C6D62A320AA000D15901 /* ExtendedPublicKeysViewController.swift in Sources */, - C9D2C6D72A320AA000D15901 /* DWContactsFetchedDataSource.m in Sources */, C9D2C6D82A320AA000D15901 /* AtmListViewController.swift in Sources */, C9D2C6D92A320AA000D15901 /* TxDetailViewController.swift in Sources */, C9D2C6DA2A320AA000D15901 /* NSAttributedString+Builder.swift in Sources */, + C943B4DE2A40A54600AF23C5 /* DWDPWelcomePageViewController.m in Sources */, C9D2C6DB2A320AA000D15901 /* DWPreviewSeedPhraseContentView.m in Sources */, C9D2C6DC2A320AA000D15901 /* DWPhoneWCSessionManager.m in Sources */, + C943B5232A40A54600AF23C5 /* DWNetworkUnavailableView.m in Sources */, + C943B3222A408CED00AF23C5 /* DWImgurInfoViewController.m in Sources */, C9D2C6DD2A320AA000D15901 /* NumberKeyboard.swift in Sources */, C9D2C6DE2A320AA000D15901 /* DWSeedPhraseTitledView.m in Sources */, - C9D2C6DF2A320AA000D15901 /* UICollectionView+DWDPItemDequeue.m in Sources */, + C943B4D02A40A54600AF23C5 /* DWCreateUsernameViewController.m in Sources */, C9D2C6E02A320AA000D15901 /* CrowdNodeTopUpTx.swift in Sources */, + C943B3302A408CED00AF23C5 /* DWSaveAlertChildView.m in Sources */, C9D2C6E12A320AA000D15901 /* BackupInfoItemView.swift in Sources */, - C9D2C6E22A320AA000D15901 /* DWContactsSearchInfoHeaderView.m in Sources */, C9D2C6E32A320AA000D15901 /* AccountCreatingController.swift in Sources */, C9D2C6E42A320AA000D15901 /* UIView+DWReuseHelper.m in Sources */, C9D2C6E52A320AA000D15901 /* DWExtendedContainerViewController.m in Sources */, C9D2C6E62A320AA000D15901 /* CrowdNodeEndpoint.swift in Sources */, C9D2C6E72A320AA000D15901 /* ServiceEntryPointModel.swift in Sources */, + C943B4B72A40A54600AF23C5 /* DWBaseContactsModel.m in Sources */, C9D2C6E82A320AA000D15901 /* ExploreDash.swift in Sources */, C9D2C6E92A320AA000D15901 /* DWToolsMenuViewController.m in Sources */, - C9D2C6EA2A320AA000D15901 /* DWUsernamePendingViewController.m in Sources */, C9D2C6EB2A320AA000D15901 /* UpholdAmountModel.swift in Sources */, + C943B4DF2A40A54600AF23C5 /* DWGetStartedContentViewController.m in Sources */, + C943B4F42A40A54600AF23C5 /* DWDPUserObject.m in Sources */, C9D2C6EC2A320AA000D15901 /* DWModalInteractiveTransition.m in Sources */, - C9D2C6ED2A320AA000D15901 /* DWRequestsViewController.m in Sources */, C9D2C6EE2A320AA000D15901 /* DWUpholdMainViewController.m in Sources */, - C9D2C6EF2A320AA000D15901 /* DWDPUserObject.m in Sources */, + C943B31D2A408CED00AF23C5 /* DWUserProfileContainerView.m in Sources */, C9D2C6F02A320AA000D15901 /* UIImage+Utils.m in Sources */, C9D2C6F12A320AA000D15901 /* CNCreateAccountCell.swift in Sources */, + C943B4BE2A40A54600AF23C5 /* DWGlobalMatchFailedHeaderView.m in Sources */, + C943B4C22A40A54600AF23C5 /* BaseCollectionReusableView.m in Sources */, C9D2C6F22A320AA000D15901 /* DWSetupViewController.m in Sources */, C9D2C6F32A320AA000D15901 /* ExploreDatabaseConnection.swift in Sources */, C9D2C6F42A320AA000D15901 /* WithdrawalLimitsController.swift in Sources */, + C943B31B2A408CED00AF23C5 /* DWUpdatingUserProfileView.m in Sources */, C9D2C6F52A320AA000D15901 /* CoinbaseTransactionResponse.swift in Sources */, - C9D2C6F62A320AA000D15901 /* DWIncomingFetchedDataSource.m in Sources */, + C943B52F2A40A54600AF23C5 /* DWNotificationsData.m in Sources */, C9D2C6F72A320AA000D15901 /* TxDetailModel.swift in Sources */, C9D2C6F82A320AA000D15901 /* NewAccountViewController.swift in Sources */, C9D2C6F92A320AA000D15901 /* PointOfUseListModel.swift in Sources */, @@ -8172,6 +8824,7 @@ C9D2C6FB2A320AA000D15901 /* TxReclassifyTransactionsWhereToChangeViewController.swift in Sources */, C9D2C6FC2A320AA000D15901 /* DWLocalCurrencyViewController.m in Sources */, C9D2C6FD2A320AA000D15901 /* BadgeView.swift in Sources */, + C943B5162A40A54600AF23C5 /* DWCreateInvitationButton.m in Sources */, C9D2C6FE2A320AA000D15901 /* HomeHeaderView.swift in Sources */, C9D2C6FF2A320AA000D15901 /* ProgressView.swift in Sources */, C9D2C7002A320AA000D15901 /* DWAmountInputValidator.m in Sources */, @@ -8180,88 +8833,115 @@ C9D2C7032A320AA000D15901 /* UIViewController+DWDisplayError.m in Sources */, C9D2C7042A320AA000D15901 /* BRAppleWatchData.m in Sources */, C9D2C7052A320AA000D15901 /* DWRequestAmountContentView.m in Sources */, + C943B3312A408CED00AF23C5 /* DWProfileAboutCellModel.m in Sources */, + C943B4C42A40A54600AF23C5 /* DWSearchViewController.m in Sources */, C9D2C7062A320AA000D15901 /* DWOnboardingModel.m in Sources */, C9D2C7072A320AA000D15901 /* AmountInputTypeSwitcher.swift in Sources */, + C943B4FA2A40A54600AF23C5 /* DWDPOutgoingRequestNotificationObject.m in Sources */, C9D2C7082A320AA000D15901 /* DWPlaceholderFormCellModel.m in Sources */, C9D2C7092A320AA000D15901 /* DWOverlapControl.m in Sources */, C9D2C70A2A320AA000D15901 /* CoinsToAddressTxFilter.swift in Sources */, C9D2C95E2A386D7E00D15901 /* DWBasePressableControl.m in Sources */, + C943B4BF2A40A54600AF23C5 /* DWContactsSearchPlaceholderView.m in Sources */, C9D2C70B2A320AA000D15901 /* DWDemoAppRootViewController.m in Sources */, C9D2C70C2A320AA000D15901 /* DWUpholdLogoutTutorialViewController.m in Sources */, C9D2C70D2A320AA000D15901 /* CBAccount.swift in Sources */, C9D2C70E2A320AA000D15901 /* Types.swift in Sources */, C9D2C70F2A320AA000D15901 /* DWConfirmPaymentViewController.m in Sources */, - C9D2C7102A320AA000D15901 /* DWDPGenericItemView.m in Sources */, + C943B33B2A408CED00AF23C5 /* DWUploadAvatarChildView.m in Sources */, C9D2C7112A320AA000D15901 /* Coinbase.swift in Sources */, C9D2C7122A320AA000D15901 /* SyncModel.swift in Sources */, + C943B4D32A40A54600AF23C5 /* DWTextField.m in Sources */, C9D2C7132A320AA000D15901 /* ReceiveContentView.swift in Sources */, + C943B4FB2A40A54600AF23C5 /* DWDPNewIncomingRequestNotificationObject.m in Sources */, C9D2C7142A320AA000D15901 /* AmountView.swift in Sources */, C9D2C7152A320AA000D15901 /* DWBasePayViewController.m in Sources */, + C943B4AC2A40A54600AF23C5 /* DWContactsViewController.m in Sources */, C9D2C7162A320AA000D15901 /* CrowdNodeTransferModel.swift in Sources */, C9D2C7172A320AA000D15901 /* DWSetPinViewController.m in Sources */, + C943B3322A408CED00AF23C5 /* DWProfileDisplayNameCellModel.m in Sources */, C9D2C7182A320AA000D15901 /* ConfirmOrderController.swift in Sources */, - C9D2C7192A320AA000D15901 /* DWUsernameHeaderView.m in Sources */, - C9D2C71A2A320AA000D15901 /* DWDashPayContactsActions.m in Sources */, C9D2C71B2A320AA000D15901 /* DWResetWalletInfoViewController.m in Sources */, C9D2C71C2A320AA000D15901 /* DWLockScreenModel.m in Sources */, + C943B5182A40A54600AF23C5 /* DWInvitationPreviewViewController.m in Sources */, C9D2C71D2A320AA000D15901 /* DWVerifySeedPhraseContentView.m in Sources */, C9D2C71E2A320AA000D15901 /* Coinbase+Constants.swift in Sources */, C9D2C71F2A320AA000D15901 /* PointOfUseListSearchCell.swift in Sources */, + C943B4FD2A40A54600AF23C5 /* DWDPRespondedIncomingRequestObject.m in Sources */, C9D2C7202A320AA000D15901 /* ServiceDataProvider.swift in Sources */, C9D2C7212A320AA000D15901 /* PaymentButton.swift in Sources */, C9D2C7222A320AA000D15901 /* DWPreviewSeedPhraseViewController.m in Sources */, C9D2C7232A320AA000D15901 /* DashPayProfileView.swift in Sources */, + C943B4B42A40A54600AF23C5 /* DWIncomingFetchedDataSource.m in Sources */, + C943B4B02A40A54600AF23C5 /* DWContactsContentViewController.m in Sources */, C9D2C7242A320AA000D15901 /* WKWebView+CrowdNode.swift in Sources */, C9D2C7252A320AA000D15901 /* SyncingActivityMonitor.swift in Sources */, C9D2C7262A320AA000D15901 /* EmptyView.swift in Sources */, C9D2C7272A320AA000D15901 /* DWConfirmSendPaymentViewController.m in Sources */, C9D2C7282A320AA000D15901 /* DWQRScanModel.m in Sources */, C9D2C7292A320AA000D15901 /* DWHomeViewController+DWImportPrivateKeyDelegateImpl.m in Sources */, + C943B3292A408CED00AF23C5 /* DWExternalSourceViewController.m in Sources */, C9D2C72A2A320AA000D15901 /* SyncView.swift in Sources */, C9D2C72B2A320AA000D15901 /* SuccessfulOperationStatusViewController.swift in Sources */, + C943B5202A40A54600AF23C5 /* DWInvitationActionsView.m in Sources */, + C943B5172A40A54600AF23C5 /* DWHistoryHeaderView.m in Sources */, C9D2C72C2A320AA000D15901 /* CustodialSwapsModel.swift in Sources */, C9D2C72D2A320AA000D15901 /* DWLocalCurrencyTableViewCell.m in Sources */, C9D2C72E2A320AA000D15901 /* DWRecoverModel.m in Sources */, C9D2C72F2A320AA000D15901 /* DWCenteredTableView.m in Sources */, C9D2C7302A320AA000D15901 /* DWOnboardingViewController.m in Sources */, - C9D2C7312A320AA000D15901 /* DWUsernameValidationView.m in Sources */, C9D2C7322A320AA000D15901 /* CrowdNode+UserDefaults.swift in Sources */, - C9D2C7332A320AA000D15901 /* DWDPAcceptedRequestNotificationObject.m in Sources */, C9D2C7342A320AA000D15901 /* DWPinView.m in Sources */, - C9D2C7352A320AA000D15901 /* DWUsernameValidationRule.m in Sources */, + C943B3242A408CED00AF23C5 /* DWRootEditProfileViewController.m in Sources */, C9D2C7362A320AA000D15901 /* DWBaseReceiveModel.m in Sources */, + C943B52E2A40A54600AF23C5 /* DWNotificationsProvider.m in Sources */, + C943B4F12A40A54600AF23C5 /* DWUserProfileContactActionsCell.m in Sources */, C9D2C7372A320AA000D15901 /* DWSegmentSlider.m in Sources */, + C943B4AE2A40A54600AF23C5 /* DWNoContactsViewController.m in Sources */, C9D2C7382A320AA000D15901 /* CoinbasePlaceBuyOrderRequest.swift in Sources */, + C943B5262A40A54600AF23C5 /* DWDashPayAnimationView.m in Sources */, + C943B4EF2A40A54600AF23C5 /* DWUserProfileHeaderView.m in Sources */, C9D2C7392A320AA000D15901 /* CoinbaseAmount.swift in Sources */, C9D2C73A2A320AA000D15901 /* DWDPRegistrationErrorTableViewCell.m in Sources */, C9D2C73B2A320AA000D15901 /* SendAmountModel.swift in Sources */, C9D2C73C2A320AA000D15901 /* DWDecimalInputValidator.m in Sources */, C9D2C73D2A320AA000D15901 /* PaymentMethods.swift in Sources */, C9D2C73E2A320AA000D15901 /* DWTitleDetailCellModel.m in Sources */, + C943B4E22A40A54600AF23C5 /* DWPassthroughView.m in Sources */, + C943B5332A40A54600AF23C5 /* DWNotificationsModel.m in Sources */, C9D2C73F2A320AA000D15901 /* BuyDashModel.swift in Sources */, C9D2C7402A320AA000D15901 /* MerchantListViewController.swift in Sources */, C9D2C7412A320AA000D15901 /* DWUpholdCardObject.m in Sources */, C9D2C7422A320AA000D15901 /* DWIntrinsicCollectionView.m in Sources */, + C943B4CC2A40A54600AF23C5 /* DWLengthUsernameValidationRule.m in Sources */, C9D2C7432A320AA000D15901 /* DWRootModelStub.m in Sources */, - C9D2C7442A320AA000D15901 /* DWDPIncomingRequestObject.m in Sources */, + C943B4D52A40A54600AF23C5 /* DWRegistrationCompletedViewController.m in Sources */, + C943B3272A408CED00AF23C5 /* DWAvatarEditSelectorViewController.m in Sources */, C9D2C7452A320AA000D15901 /* UIDevice+DashWallet.m in Sources */, C9D2C7462A320AA000D15901 /* CBAccountManager.swift in Sources */, C9D2C7472A320AA000D15901 /* FailedOperationStatusViewController.swift in Sources */, C9D2C7482A320AA000D15901 /* UIPanGestureRecognizer+DWProjected.m in Sources */, + C943B50A2A40A54600AF23C5 /* DWDPContactsItemsFactory.m in Sources */, C9D2C7492A320AA000D15901 /* DWCurrencyObject.m in Sources */, + C943B52B2A40A54600AF23C5 /* DWDashPayContactsActions.m in Sources */, + C943B50B2A40A54600AF23C5 /* DWDPSearchItemsFactory.m in Sources */, C9D2C74A2A320AA000D15901 /* ShortcutsView.swift in Sources */, C9D2C74B2A320AA000D15901 /* DWFormSectionModel.m in Sources */, C9D2C74C2A320AA000D15901 /* DWActionButton.m in Sources */, C9D2C74D2A320AA000D15901 /* CoinbasePlaceBuyOrderResponse.swift in Sources */, C9D2C74E2A320AA000D15901 /* ExploreMapView.swift in Sources */, + C943B32F2A408CED00AF23C5 /* DWSaveAlertViewController.m in Sources */, + C943B4AB2A40A54600AF23C5 /* DWContactsPlaceholderViewController.m in Sources */, C9D2C74F2A320AA000D15901 /* DWSeedPhraseRow.m in Sources */, + C943B5132A40A54600AF23C5 /* DWHistoryFilterContentView.m in Sources */, + C943B4BC2A40A54600AF23C5 /* DWRootContactsViewController.m in Sources */, + C943B4C32A40A54600AF23C5 /* DWTitleActionHeaderView.m in Sources */, C9D2C7502A320AA000D15901 /* ServiceOverviewViewController.swift in Sources */, C9D2C7512A320AA000D15901 /* AccountListModel.swift in Sources */, - C9D2C7522A320AA000D15901 /* DWDPSearchItemsFactory.m in Sources */, + C943B4D12A40A54600AF23C5 /* DWUsernameHeaderView.m in Sources */, C9D2C7532A320AA000D15901 /* DWRecoverWalletCommand.m in Sources */, C9D2C7542A320AA000D15901 /* DWSwitcherFormTableViewCell.m in Sources */, C9D2C7552A320AA000D15901 /* DWPaymentInput.m in Sources */, - C9D2C7562A320AA000D15901 /* DWBaseContactsViewController.m in Sources */, C9D2C7572A320AA000D15901 /* OnlineAccountDetailsController.swift in Sources */, C9D2C7582A320AA000D15901 /* UIApplication+DashWallet.swift in Sources */, C9D2C7592A320AA000D15901 /* OnlineAccountInfoController.swift in Sources */, @@ -8272,38 +8952,45 @@ C9D2C75E2A320AA000D15901 /* KeyboardHeader.swift in Sources */, C9D2C75F2A320AA000D15901 /* DWUpholdOTPViewController.m in Sources */, C9D2C7602A320AA000D15901 /* DWAppGroupOptions.m in Sources */, + C943B5352A40A54600AF23C5 /* DWNotificationsViewController.m in Sources */, C9D2C7612A320AA000D15901 /* DWTransactionListDataItemObject.m in Sources */, + C943B3362A408CED00AF23C5 /* DWEditProfileBaseCell.m in Sources */, C9D2C7622A320AA000D15901 /* DWUpholdAPIProvider.m in Sources */, - C9D2C7632A320AA000D15901 /* DWConfirmUsernameContentView.m in Sources */, + C943B5102A40A54600AF23C5 /* DWSendInviteFirstStepViewController.m in Sources */, + C943B4E72A40A54600AF23C5 /* DWUserProfileDataSourceObject.m in Sources */, + C943B50C2A40A54600AF23C5 /* DPAlertViewController+DWInvite.m in Sources */, + C943B3352A408CED00AF23C5 /* DWEditProfileTextViewCell.m in Sources */, C9D2C7642A320AA000D15901 /* BasePageSheetViewController.swift in Sources */, - C9D2C7652A320AA000D15901 /* DWListCollectionLayout.m in Sources */, + C943B4B92A40A54600AF23C5 /* DWRequestsContentViewController.m in Sources */, + C943B54E2A40B6B500AF23C5 /* DWColoredButton.m in Sources */, C9D2C7662A320AA000D15901 /* CrowdNodeRequest.swift in Sources */, C9D2C7672A320AA000D15901 /* CoinbaseExchangeRateResponse.swift in Sources */, - C9D2C7682A320AA000D15901 /* DWDPTxItemView.m in Sources */, C9D2C7692A320AA000D15901 /* DWConfirmPaymentContentView.m in Sources */, C9D2C76A2A320AA000D15901 /* CrowdNodePortalItem.swift in Sources */, + C943B4C82A40A54600AF23C5 /* DWSearchStateViewController.m in Sources */, C9D2C76B2A320AA000D15901 /* BaseViewController.swift in Sources */, C9D2C76C2A320AA000D15901 /* AccountListController.swift in Sources */, C9D2C76D2A320AA000D15901 /* DWUpholdTransactionObject.m in Sources */, + C943B5052A40A54600AF23C5 /* DWDPIncomingRequestCell.m in Sources */, C9D2C76E2A320AA000D15901 /* DWBaseSeedViewController.m in Sources */, + C943B51D2A40A54600AF23C5 /* InvitationBottomView.swift in Sources */, + C943B32D2A408CED00AF23C5 /* DWAvatarPublicURLViewController.m in Sources */, C9D2C76F2A320AA000D15901 /* DWProgressAnimator.mm in Sources */, + C943B3192A408CED00AF23C5 /* DWDPUpdateProfileModel.m in Sources */, C9D2C7702A320AA000D15901 /* CrowdNodeModel.swift in Sources */, C9D2C7712A320AA000D15901 /* UIDevice+Compatibility.swift in Sources */, C9D2C7722A320AA000D15901 /* DWSeedWordView.m in Sources */, C9D2C7732A320AA000D15901 /* DWSecurityStatusView.m in Sources */, - C9D2C7742A320AA000D15901 /* DWRequestsContentViewController.m in Sources */, C9D2C7752A320AA000D15901 /* DWAnimatedShapeLayer.m in Sources */, - C9D2C7762A320AA000D15901 /* DWUserProfileNavigationTitleView.m in Sources */, - C9D2C7772A320AA000D15901 /* DWTextField.m in Sources */, + C943B4BD2A40A54600AF23C5 /* DWGlobalMatchHeaderView.m in Sources */, C9D2C7782A320AA000D15901 /* DWScreenshotWarningViewController.m in Sources */, + C943B4B82A40A54600AF23C5 /* DWContactsModel.m in Sources */, C9D2C7792A320AA000D15901 /* ConvertCryptoOrderPreviewController.swift in Sources */, C9D2C77A2A320AA000D15901 /* TransactionDataItem.swift in Sources */, C9D2C77B2A320AA000D15901 /* DWSetPinModel.m in Sources */, C9D2C77C2A320AA000D15901 /* DWVerifySeedPhraseModel.m in Sources */, C9D2C77D2A320AA000D15901 /* ReceiveViewController.swift in Sources */, C9D2C77E2A320AA000D15901 /* ConfirmationTransactionQRController.swift in Sources */, - C9D2C77F2A320AA000D15901 /* DWDashPayAnimationView.m in Sources */, - C9D2C7802A320AA000D15901 /* DWDPOutgoingRequestNotificationObject.m in Sources */, C9D2C7812A320AA000D15901 /* Numbers+Dash.swift in Sources */, C9D2C7822A320AA000D15901 /* PaymentMethodsController.swift in Sources */, C9D2C7832A320AA000D15901 /* PointOfUseDetailsViewController.swift in Sources */, @@ -8311,59 +8998,65 @@ C9D2C7852A320AA000D15901 /* BalanceModel.swift in Sources */, C9D2C7862A320AA000D15901 /* TxReclassifyTransactionsInfoViewController.swift in Sources */, C9D2C7872A320AA000D15901 /* DWTransactionListDataProvider.m in Sources */, + C943B53E2A40A6BE00AF23C5 /* DPAlertViewController.m in Sources */, C9D2C9692A3875BA00D15901 /* DWCurrentUserProfileModel.m in Sources */, C9D2C7882A320AA000D15901 /* DWModalPopupTransition.m in Sources */, C9D2C7892A320AA000D15901 /* DWAdvancedSecurityViewController.m in Sources */, - C9D2C78A2A320AA000D15901 /* DWInputUsernameViewController.m in Sources */, C9D2C78B2A320AA000D15901 /* OrderPreviewModel.swift in Sources */, - C9D2C78C2A320AA000D15901 /* DWDPGenericImageItemView.m in Sources */, C9D2C78D2A320AA000D15901 /* UIView+DWFindConstraints.m in Sources */, C9D2C78E2A320AA000D15901 /* DWModalPopupPresentationController.m in Sources */, C9D2C78F2A320AA000D15901 /* CrowdNodeBalance.swift in Sources */, C9D2C7902A320AA000D15901 /* String+DashWallet.swift in Sources */, - C9D2C7912A320AA000D15901 /* DWBaseContactsContentViewController.m in Sources */, - C9D2C7922A320AA000D15901 /* DWUserProfileViewController.m in Sources */, C9D2C7932A320AA000D15901 /* DWBaseModalViewController.m in Sources */, C9D2C7942A320AA000D15901 /* DWSecurityMenuModel.m in Sources */, + C943B32C2A408CED00AF23C5 /* DWAvatarExternalSourceConfig.m in Sources */, C9D2C7952A320AA000D15901 /* KeysOverviewCell.swift in Sources */, C9D2C7962A320AA000D15901 /* UIViewController+DWShareReceiveInfo.m in Sources */, C9D2C7972A320AA000D15901 /* OnlineAccountConfirmationController.swift in Sources */, + C943B4F52A40A54600AF23C5 /* DWDPIncomingRequestObject.m in Sources */, + C943B4BA2A40A54600AF23C5 /* DWRequestsModel.m in Sources */, C9D2C7982A320AA000D15901 /* BaseResponse.swift in Sources */, C9D2C7992A320AA000D15901 /* DWDemoAdvancedSecurityViewController.m in Sources */, + C943B3212A408CED00AF23C5 /* DWImgurItemView.m in Sources */, C9D2C79A2A320AA000D15901 /* TxListTableViewCell.swift in Sources */, - C9D2C79B2A320AA000D15901 /* DWStretchyHeaderListCollectionLayout.m in Sources */, C9D2C79C2A320AA000D15901 /* TxWithinTimePeriod.swift in Sources */, C9D2C79D2A320AA000D15901 /* CoinbaseSwapeTradeRequest.swift in Sources */, + C943B4DB2A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.m in Sources */, C9D2C79E2A320AA000D15901 /* UIFont+DWFont.m in Sources */, C9D2C79F2A320AA000D15901 /* CustodialSwapsViewController.swift in Sources */, C9D2C7A02A320AA000D15901 /* NSAttributedString+DWHighlightText.m in Sources */, C9D2C7A12A320AA000D15901 /* CoinbaseInfoViewController.swift in Sources */, C9D2C7A22A320AA000D15901 /* DWSelectorFormCellModel.m in Sources */, + C943B5142A40A54600AF23C5 /* DWHistoryFilterViewController.m in Sources */, C9D2C7A32A320AA000D15901 /* DWVersionManager.m in Sources */, - C9D2C7A42A320AA000D15901 /* DWDPEstablishedContactNotificationObject.m in Sources */, + C943B4AF2A40A54600AF23C5 /* DWInvitationSuggestionView.m in Sources */, C9D2C7A52A320AA000D15901 /* DWModalPresentationController.m in Sources */, C9D2C7A62A320AA000D15901 /* PointOfUseInfoViewController.swift in Sources */, C9D2C7A72A320AA000D15901 /* ActionButtonViewController.swift in Sources */, C9D2C7A82A320AA000D15901 /* PortalServiceItemCell.swift in Sources */, - C9D2C7A92A320AA000D15901 /* DWDPImageStatusCell.m in Sources */, C9D2C7AA2A320AA000D15901 /* WithdrawalConfirmationController.swift in Sources */, + C943B5002A40A54600AF23C5 /* DWDPTxItemView.m in Sources */, + C943B4C92A40A54600AF23C5 /* DWUsernamePendingViewController.m in Sources */, C9D2C7AB2A320AA000D15901 /* DWBaseFormTableViewCell.m in Sources */, C9D2C7AC2A320AA000D15901 /* AccountCell.swift in Sources */, + C943B4D82A40A54600AF23C5 /* DWConfirmUsernameViewController.m in Sources */, C9D2C7AD2A320AA000D15901 /* DWTransactionListDataProviderStub.m in Sources */, - C9D2C7AE2A320AA000D15901 /* DWUserSearchModel.m in Sources */, - C9D2C7AF2A320AA000D15901 /* DWDPBasicCell.m in Sources */, + C943B33A2A408CED00AF23C5 /* DWUploadAvatarModel.m in Sources */, + C943B3392A408CED00AF23C5 /* DWUploadAvatarViewController.m in Sources */, C9D2C7B02A320AA000D15901 /* DWExploreHeaderView.m in Sources */, C9D2C7B12A320AA000D15901 /* CoinbaseBaseIDForCurrencyResponse.swift in Sources */, C9D2C7B22A320AA000D15901 /* DWBaseActionButton.m in Sources */, C9D2C7B32A320AA000D15901 /* SpecifyAmountViewController.swift in Sources */, + C943B4EA2A40A54600AF23C5 /* DWUserProfileViewController.m in Sources */, C9D2C7B42A320AA000D15901 /* DWUpholdTransactionObject+DWView.m in Sources */, C9D2C7B52A320AA000D15901 /* DWBackupSeedPhraseViewController.m in Sources */, + C943B54A2A40B52F00AF23C5 /* NSLayoutConstraint+DWAutolayout.m in Sources */, C9D2C7B62A320AA000D15901 /* CrowdNodeDepositTx.swift in Sources */, C9D2C7B72A320AA000D15901 /* DWUpholdViewController.m in Sources */, - C9D2C7B82A320AA000D15901 /* DWNotificationsModel.m in Sources */, C9D2C7B92A320AA000D15901 /* DWIntrinsicTableView.m in Sources */, C9D2C7BA2A320AA000D15901 /* DWURLActions.m in Sources */, C9D2C7BB2A320AA000D15901 /* KeysOverviewViewController.swift in Sources */, + C943B4FE2A40A54600AF23C5 /* DWDPTextStatusCell.m in Sources */, C9D2C7BC2A320AA000D15901 /* AccountRepository.swift in Sources */, C9D2C7BD2A320AA000D15901 /* DWSharedUIConstants.m in Sources */, C9D2C7BE2A320AA000D15901 /* BRAppleWatchTransactionData.m in Sources */, @@ -8373,6 +9066,7 @@ C9D2C7C22A320AA000D15901 /* DWUpholdConfirmTransferModel.m in Sources */, C9D2C7C32A320AA000D15901 /* DWDPRegistrationStatus.m in Sources */, C9D2C7C42A320AA000D15901 /* CurrencyExchanger_Objc.m in Sources */, + C943B32B2A408CED00AF23C5 /* DWAvatarExternalLoadingView.m in Sources */, C9D2C7C52A320AA000D15901 /* GiftCardInfoViewController.swift in Sources */, C9D2C7C62A320AA000D15901 /* Tools.swift in Sources */, C9D2C7C72A320AA000D15901 /* PortalModel.swift in Sources */, @@ -8386,102 +9080,117 @@ C9D2C7CF2A320AA000D15901 /* MerchantListLocationOffCell.swift in Sources */, C9D2C7D02A320AA000D15901 /* DWExploreTestnetContentsView.m in Sources */, C9D2C7D12A320AA000D15901 /* DWMainMenuContentView.m in Sources */, - C9D2C7D22A320AA000D15901 /* DWDPIncomingRequestCell.m in Sources */, C9D2C7D32A320AA000D15901 /* CrowdNodeAPIConfirmationTx.swift in Sources */, C9D2C7D42A320AA000D15901 /* UIViewController+DWEmbedding.m in Sources */, C9D2C7D52A320AA000D15901 /* DWShadowView.m in Sources */, C9D2C7D62A320AA000D15901 /* CALayer+DWShadow.m in Sources */, C9D2C7D72A320AA000D15901 /* MerchantDAO.swift in Sources */, C9D2C7D82A320AA000D15901 /* ExploreDatabaseSyncManager.swift in Sources */, + C943B5312A40A54600AF23C5 /* DWNoNotificationsCell.m in Sources */, C9D2C7D92A320AA000D15901 /* DWOnboardingCollectionViewCell.m in Sources */, + C943B4B22A40A54600AF23C5 /* DWContactsDataSourceObject.m in Sources */, C9D2C7DA2A320AA000D15901 /* CrowdNodeErrorResponse.swift in Sources */, + C943B4F72A40A54600AF23C5 /* DWDPTxObject.m in Sources */, C9D2C7DB2A320AA000D15901 /* DWHomeViewController+DWShortcuts.m in Sources */, C9D2C7DC2A320AA000D15901 /* DWHomeModelStub.m in Sources */, C9D2C7DD2A320AA000D15901 /* UISearchBar+DWAdditions.m in Sources */, + C943B5122A40A54600AF23C5 /* DWInvitationHistoryModel.m in Sources */, + C943B31A2A408CED00AF23C5 /* DWCurrentUserProfileView.m in Sources */, C9D2C7DE2A320AA000D15901 /* UIViewController+DWTxFilter.m in Sources */, - C9D2C7DF2A320AA000D15901 /* DWProfileTxsFetchedDataSource.m in Sources */, + C943B4F32A40A54600AF23C5 /* DWDPEstablishedContactObject.m in Sources */, C9D2C7E02A320AA000D15901 /* TransactionFilter.swift in Sources */, - C9D2C7E12A320AA000D15901 /* DWSearchViewController.m in Sources */, C9D2C7E22A320AA000D15901 /* CrowdNodeWithdrawalReceivedTx.swift in Sources */, + C943B4B62A40A54600AF23C5 /* DWFetchedResultsDataSource.m in Sources */, C9D2C7E32A320AA000D15901 /* PointOfUseListSegmentedCell.swift in Sources */, + C943B33C2A408CED00AF23C5 /* DWHourGlassAnimationView.m in Sources */, C9D2C7E42A320AA000D15901 /* DWURLRequestHandler.m in Sources */, C9D2C7E52A320AA000D15901 /* CoinbaseAPIEndpoint.swift in Sources */, C9D2C7E62A320AA000D15901 /* ConfirmOrderCells.swift in Sources */, + C943B4D22A40A54600AF23C5 /* DWPlanetarySystemView.m in Sources */, C9D2C7E72A320AA000D15901 /* CoinbaseUserAccountData.swift in Sources */, + C943B3202A408CED00AF23C5 /* DWImgurInfoChildView.m in Sources */, C9D2C7E82A320AA000D15901 /* DWDPRegistrationDoneTableViewCell.m in Sources */, C9D2C7E92A320AA000D15901 /* AtmDAO.swift in Sources */, C9D2C7EA2A320AA000D15901 /* App.swift in Sources */, C9D2C7EB2A320AA000D15901 /* DWPaymentProcessor.m in Sources */, - C9D2C7EC2A320AA000D15901 /* DWNotificationsData.m in Sources */, C9D2C7ED2A320AA000D15901 /* SyncingHeaderView.swift in Sources */, C9D2C7EE2A320AA000D15901 /* MinimumDepositBanner.swift in Sources */, + C943B4F92A40A54600AF23C5 /* DWDPAcceptedRequestNotificationObject.m in Sources */, C9D2C7EF2A320AA000D15901 /* BalanceView.swift in Sources */, C9D2C7F02A320AA000D15901 /* DWQRScanStatusView.m in Sources */, C9D2C7F12A320AA000D15901 /* DWURLParser.m in Sources */, - C9D2C7F22A320AA000D15901 /* DWDPContactObject.m in Sources */, C9D2C7F32A320AA000D15901 /* DWPaymentInputBuilder.m in Sources */, C9D2C7F42A320AA000D15901 /* DWQRScanViewController.m in Sources */, C9D2C7F52A320AA000D15901 /* DWSelectorFormTableViewCell.m in Sources */, - C9D2C7F62A320AA000D15901 /* DWRegistrationCompletedViewController.m in Sources */, - C9D2C7F72A320AA000D15901 /* DWUserProfileHeaderView.m in Sources */, C9D2C7F82A320AA000D15901 /* UIViewController+AlertPresenting.swift in Sources */, C9D2C7F92A320AA000D15901 /* DSWatchTransactionDataObject.m in Sources */, C9D2C7FA2A320AA000D15901 /* HTTPClient.swift in Sources */, C9D2C7FB2A320AA000D15901 /* DWPasteboardAddressExtractor.m in Sources */, + C943B4F22A40A54600AF23C5 /* DWDPNewIncomingRequestObject.m in Sources */, C9D2C7FC2A320AA000D15901 /* ViewModel+Coinbase.swift in Sources */, + C943B52C2A40A54600AF23C5 /* DSBlockchainIdentity+DWDisplayName.m in Sources */, C9D2C7FD2A320AA000D15901 /* CNCreateAccountTxDetailsModel.swift in Sources */, C9D2C7FE2A320AA000D15901 /* CBUser.swift in Sources */, C9D2C7FF2A320AA000D15901 /* CBSecureTokenService.swift in Sources */, C9D2C8002A320AA000D15901 /* PayTableViewCell.swift in Sources */, - C9D2C8012A320AA000D15901 /* DWTitleActionHeaderView.m in Sources */, - C9D2C8022A320AA000D15901 /* DWDPTxObject.m in Sources */, C9D2C8032A320AA000D15901 /* ServiceOverviewTableCell.swift in Sources */, - C9D2C8042A320AA000D15901 /* DWContactsModel.m in Sources */, C9D2C8052A320AA000D15901 /* CoinbaseEntryPointViewController.swift in Sources */, C9D2C8062A320AA000D15901 /* NSString+DWTextSize.m in Sources */, C9D2C8072A320AA000D15901 /* PointOfUseListFiltersCell.swift in Sources */, C9D2C8082A320AA000D15901 /* TransactionListDataSource.swift in Sources */, - C9D2C8092A320AA000D15901 /* DWAllowedCharactersUsernameValidationRule.m in Sources */, C9D2C80A2A320AA000D15901 /* DWMainMenuViewController.m in Sources */, C9D2C80B2A320AA000D15901 /* PointOfUseListFiltersViewController.swift in Sources */, C9D2C80C2A320AA000D15901 /* AllMerchantLocationsDataProvider.swift in Sources */, C9D2C80D2A320AA000D15901 /* MainTabbarController.swift in Sources */, C9D2C80E2A320AA000D15901 /* NumberFormatter+DashWallet.swift in Sources */, - C9D2C80F2A320AA000D15901 /* DWNotificationsFetchedDataSource.m in Sources */, C9D2C8102A320AA000D15901 /* BaseAmountModel.swift in Sources */, - C9D2C8112A320AA000D15901 /* DWDPNewIncomingRequestNotificationObject.m in Sources */, + C943B3182A408CED00AF23C5 /* DSBlockchainIdentity+DWDisplayTitleSubtitle.m in Sources */, C9D2C8122A320AA000D15901 /* SeedDB.swift in Sources */, C9D2C8132A320AA000D15901 /* DWPinField.m in Sources */, + C943B3252A408CED00AF23C5 /* DWEditProfileViewController.m in Sources */, + C943B4DC2A40A54600AF23C5 /* DWInvitationFlowViewController.m in Sources */, C9D2C8142A320AA000D15901 /* DWCenteredScrollView.m in Sources */, - C9D2C8152A320AA000D15901 /* DWDPEstablishedContactObject.m in Sources */, C9D2C8162A320AA000D15901 /* DWPhraseRepairViewController.m in Sources */, + C943B4D62A40A54600AF23C5 /* DWDashPaySetupFlowController.m in Sources */, C9D2C8172A320AA000D15901 /* DWBiometricAuthModel.m in Sources */, + C943B52A2A40A54600AF23C5 /* UIImageView+DWDPAvatar.m in Sources */, C9D2C8182A320AA000D15901 /* DWNumberKeyboardInputViewAudioFeedback.m in Sources */, + C943B5042A40A54600AF23C5 /* DWDPImageStatusCell.m in Sources */, + C943B4AD2A40A54600AF23C5 /* DWBaseContactsContentViewController.m in Sources */, C9D2C8192A320AA000D15901 /* CrowdNode+Constants.swift in Sources */, C9D2C81A2A320AA000D15901 /* CurrencyExchanger.swift in Sources */, C9D2C9622A386DA200D15901 /* DWDPWelcomeView.m in Sources */, - C9D2C81B2A320AA000D15901 /* DWMinLengthUsernameValidationRule.m in Sources */, + C943B55E2A40E6F200AF23C5 /* DWFilterHeaderView.m in Sources */, + C943B5072A40A54600AF23C5 /* DWDPBasicCell.m in Sources */, + C943B4EC2A40A54600AF23C5 /* DWUserProfileSendRequestCell.m in Sources */, C9D2C81C2A320AA000D15901 /* DemoMainTabbarViewController.swift in Sources */, + C943B4C62A40A54600AF23C5 /* DWUserSearchViewController.m in Sources */, + C943B4C72A40A54600AF23C5 /* DWUserSearchResultViewController.m in Sources */, C9D2C81D2A320AA000D15901 /* MessageStatus.swift in Sources */, C9D2C81E2A320AA000D15901 /* DWGlobalOptions.m in Sources */, + C943B31C2A408CED00AF23C5 /* DWDPWelcomeMenuView.m in Sources */, C9D2C81F2A320AA000D15901 /* AppDelegate.m in Sources */, C9D2C8202A320AA000D15901 /* SendReceivePageController.swift in Sources */, C9D2C8212A320AA000D15901 /* DWSettingsMenuModel.m in Sources */, - C9D2C8222A320AA000D15901 /* DWUserProfileSendRequestCell.m in Sources */, + C943B5242A40A54600AF23C5 /* UIColor+DWDashPay.m in Sources */, + C943B4E82A40A54600AF23C5 /* DWProfileTxsFetchedDataSource.m in Sources */, C9D2C8232A320AA000D15901 /* BaseNavigationController.swift in Sources */, C9D2C8242A320AA000D15901 /* UIViewController+Coinbase.swift in Sources */, - C9D2C8252A320AA000D15901 /* DWCheckExistenceUsernameValidationRule.m in Sources */, C9D2C8262A320AA000D15901 /* CSVBuilder.swift in Sources */, C9D2C8272A320AA000D15901 /* DWDPRegistrationStatusTableViewCell.m in Sources */, C9D2C8282A320AA000D15901 /* CoinbaseUserAuthInformation.swift in Sources */, C9D2C8292A320AA000D15901 /* DWTransactionStub.m in Sources */, C9D2C82A2A320AA000D15901 /* OnlineAccountEmailController.swift in Sources */, C9D2C82B2A320AA000D15901 /* DWBaseActionButtonViewController.m in Sources */, + C943B3402A408E5500AF23C5 /* DWMainMenuViewController+DashPay.m in Sources */, C9D2C82C2A320AA000D15901 /* CoinbasePaymentMethodsResponse.swift in Sources */, C9D2C82D2A320AA000D15901 /* UIView+DWEmbedding.m in Sources */, C9D2C82E2A320AA000D15901 /* DWLockActionButton.m in Sources */, + C943B55B2A40DD4000AF23C5 /* NSArray+DWFlatten.m in Sources */, C9D2C82F2A320AA000D15901 /* PointOfUseDAO.swift in Sources */, + C943B5292A40A54600AF23C5 /* DWDashPayConstants.m in Sources */, C9D2C8302A320AA000D15901 /* ProvideAmountViewController.swift in Sources */, + C943B4E92A40A54600AF23C5 /* DWUserProfileModel.m in Sources */, C9D2C8312A320AA000D15901 /* DWCheckbox.m in Sources */, C9D2C8322A320AA000D15901 /* DWRequestAmountViewController.m in Sources */, C9D2C8332A320AA000D15901 /* DerivationPathKeysModel.swift in Sources */, @@ -8495,56 +9204,57 @@ C9D2C83B2A320AA000D15901 /* TxUserInfoDAO.swift in Sources */, C9D2C83C2A320AA000D15901 /* DWModalContentView.m in Sources */, C9D2C83D2A320AA000D15901 /* DWUpholdConstants.m in Sources */, - C9D2C83E2A320AA000D15901 /* DWMaxLengthUsernameValidationRule.m in Sources */, C9D2C83F2A320AA000D15901 /* CoinbaseSwapeTradeResponse.swift in Sources */, + C943B4FF2A40A54600AF23C5 /* DWDPGenericContactRequestItemView.m in Sources */, C9D2C8402A320AA000D15901 /* DWQRScanView.m in Sources */, C9D2C8412A320AA000D15901 /* ServiceItem.swift in Sources */, C9D2C8422A320AA000D15901 /* ErrorPresentable.swift in Sources */, C9D2C8432A320AA000D15901 /* DWPinInputStepView.m in Sources */, - C9D2C8442A320AA000D15901 /* DWDashPaySetupFlowController.m in Sources */, C9D2C8452A320AA000D15901 /* DWRecoverViewController.m in Sources */, C9D2C8462A320AA000D15901 /* DWDPAmountContactView.m in Sources */, C9D2C8472A320AA000D15901 /* DWBaseTransactionListDataProvider.m in Sources */, C9D2C8482A320AA000D15901 /* CoinbaseTransactionsRequest.swift in Sources */, + C943B3372A408CED00AF23C5 /* DWEditProfileTextFieldCell.m in Sources */, C9D2C8492A320AA000D15901 /* DWAppRootViewController.m in Sources */, C9D2C84A2A320AA000D15901 /* DWUpholdConfirmViewController.m in Sources */, C9D2C84B2A320AA000D15901 /* UITableView+DashWallet.swift in Sources */, C9D2C84C2A320AA000D15901 /* TransferAmountViewController.swift in Sources */, C9D2C84D2A320AA000D15901 /* ShortcutCell.swift in Sources */, - C9D2C84E2A320AA000D15901 /* DWDPGenericContactRequestItemView.m in Sources */, + C943B4BB2A40A54600AF23C5 /* DWRequestsViewController.m in Sources */, C9D2C84F2A320AA000D15901 /* UIColor+DWStyle.m in Sources */, C9D2C8502A320AA000D15901 /* DWEnvironment.m in Sources */, C9D2C8512A320AA000D15901 /* AtmItemCell.swift in Sources */, C9D2C8522A320AA000D15901 /* CoinbaseService.swift in Sources */, + C943B4E02A40A54600AF23C5 /* DWGetStartedViewController.m in Sources */, C9D2C8532A320AA000D15901 /* DWControllerCollectionView.m in Sources */, C9D2C8542A320AA000D15901 /* SFSafariViewController+DashWallet.m in Sources */, - C9D2C8552A320AA000D15901 /* DWContactsDataSourceObject.m in Sources */, + C943B4F82A40A54600AF23C5 /* DWDPPendingRequestObject.m in Sources */, C9D2C8562A320AA000D15901 /* TransactionItemView.swift in Sources */, C9D2C8572A320AA000D15901 /* UIView+Reuse.swift in Sources */, C9D2C8582A320AA000D15901 /* DWUpholdClient.m in Sources */, + C943B4CD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m in Sources */, C9D2C8592A320AA000D15901 /* CrowdNodePortalViewController.swift in Sources */, C9D2C85A2A320AA000D15901 /* ConfirmOrderModel.swift in Sources */, + C943B53F2A40A6BE00AF23C5 /* DPAlertChildContentsView.m in Sources */, C9D2C85B2A320AA000D15901 /* PointOfUseListEmptyResultsView.swift in Sources */, C9D2C85C2A320AA000D15901 /* ShortcutsModel.swift in Sources */, C9D2C85D2A320AA000D15901 /* DWLockPinInputView.m in Sources */, C9D2C85E2A320AA000D15901 /* UIView+DWHUD.m in Sources */, C9D2C85F2A320AA000D15901 /* DWPayOptionModel.m in Sources */, - C9D2C8602A320AA000D15901 /* DWDPRespondedIncomingRequestObject.m in Sources */, C9D2C8612A320AA000D15901 /* CoinbaseTokenResponse.swift in Sources */, C9D2C8622A320AA000D15901 /* BackupInfoViewController.swift in Sources */, - C9D2C8632A320AA000D15901 /* DWPlanetarySystemView.m in Sources */, C9D2C8642A320AA000D15901 /* AppliedFiltersView.swift in Sources */, C9D2C8652A320AA000D15901 /* DWCaptureSessionManager.m in Sources */, C9D2C8662A320AA000D15901 /* DWDataMigrationManager.m in Sources */, - C9D2C8672A320AA000D15901 /* DWRequestsModel.m in Sources */, - C9D2C8682A320AA000D15901 /* DWDPTextStatusCell.m in Sources */, - C9D2C8692A320AA000D15901 /* DWContactsViewController.m in Sources */, C9D2C86A2A320AA000D15901 /* OutlinedTextField.swift in Sources */, + C943B51F2A40A54600AF23C5 /* DWSuccessInvitationView.m in Sources */, C9D2C86B2A320AA000D15901 /* AmountPreviewView.swift in Sources */, + C943B5322A40A54600AF23C5 /* DWNotificationsInvitationCell.m in Sources */, C9D2C86C2A320AA000D15901 /* ModalNavigationController.swift in Sources */, - C9D2C86D2A320AA000D15901 /* DWDPSmallContactView.m in Sources */, + C943B5032A40A54600AF23C5 /* DWDPGenericImageItemView.m in Sources */, C9D2C86E2A320AA000D15901 /* TransferAmountModel.swift in Sources */, C9D2C86F2A320AA000D15901 /* NavigationBarAppearanceCustomizable.swift in Sources */, + C943B3462A409FFA00AF23C5 /* DWDPAvatarView.m in Sources */, C9D2C8702A320AA000D15901 /* AmountObject.swift in Sources */, C9D2C8712A320AA000D15901 /* DerivationPathKeysViewController.swift in Sources */, C9D2C8722A320AA000D15901 /* DSTransaction+DashWallet.m in Sources */, @@ -8552,128 +9262,139 @@ C9D2C8742A320AA000D15901 /* DWLocalCurrencyModel.m in Sources */, C9D2C8752A320AA000D15901 /* DWMainMenuModel.m in Sources */, C9D2C8762A320AA000D15901 /* DWUpholdMainnetConstants.m in Sources */, - C9D2C8772A320AA000D15901 /* DWUserSearchResultViewController.m in Sources */, - C9D2C8782A320AA000D15901 /* DWDPGenericStatusItemView.m in Sources */, C9D2C8792A320AA000D15901 /* Style.swift in Sources */, C9D2C87A2A320AA000D15901 /* PayViewController.swift in Sources */, C9D2C87B2A320AA000D15901 /* DatabaseConnection.swift in Sources */, C9D2C87C2A320AA000D15901 /* DWSwitcherFormCellModel.m in Sources */, C9D2C87D2A320AA000D15901 /* DWIntrinsicTextView.m in Sources */, C9D2C87E2A320AA000D15901 /* DWExploreTestnetViewController.m in Sources */, - C9D2C96C2A38762800D15901 /* DWDPUpdateProfileModel.m in Sources */, C9D2C87F2A320AA000D15901 /* AddressUserInfoDAO.swift in Sources */, - C9D2C8802A320AA000D15901 /* DWDashPayContactsUpdater.m in Sources */, + C943B4CB2A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m in Sources */, + C943B5112A40A54600AF23C5 /* DWInvitationHistoryViewController.m in Sources */, C9D2C9712A38778E00D15901 /* DWDashPaySetupModel.m in Sources */, C9D2C8812A320AA000D15901 /* DWModalTransition.m in Sources */, C9D2C8822A320AA000D15901 /* DWUpholdMainModel.m in Sources */, + C943B4E12A40A54600AF23C5 /* DWGetStartedItemView.m in Sources */, C9D2C8832A320AA000D15901 /* MerchantsDataProvider.swift in Sources */, C9D2C8842A320AA000D15901 /* FullCrowdNodeSignUpTxSet.swift in Sources */, C9D2C8852A320AA000D15901 /* DWSeedPhraseModel.m in Sources */, + C943B4CF2A40A54600AF23C5 /* DWInputUsernameViewController.m in Sources */, C9D2C8862A320AA000D15901 /* Constants.swift in Sources */, C9D2C8872A320AA000D15901 /* DWBaseFormCellModel.m in Sources */, C9D2C8882A320AA000D15901 /* ExploreMapAnnotationView.swift in Sources */, C9D2C8892A320AA000D15901 /* DWHomeViewController+DWBackupReminder.m in Sources */, + C943B51A2A40A54600AF23C5 /* DWInvitationLinkBuilder.m in Sources */, C9D2C88A2A320AA000D15901 /* DWHomeModel.m in Sources */, - C9D2C88B2A320AA000D15901 /* DWUserProfileModel.m in Sources */, C9D2C88C2A320AA000D15901 /* DWHomeViewController.m in Sources */, C9D2C88D2A320AA000D15901 /* IsAddressInUse.swift in Sources */, - C9D2C88E2A320AA000D15901 /* DWFetchedResultsDataSource.m in Sources */, + C943B3342A408CED00AF23C5 /* DWTextViewFormCellModel.m in Sources */, C9D2C88F2A320AA000D15901 /* DWSegmentedControl.m in Sources */, C9D2C8902A320AA000D15901 /* MerchantItemCell.swift in Sources */, C9D2C8912A320AA000D15901 /* PaymentMethodCell.swift in Sources */, - C9D2C8922A320AA000D15901 /* DWContactsContentViewController.m in Sources */, C9D2C8932A320AA000D15901 /* CBUserManager.swift in Sources */, + C943B5082A40A54600AF23C5 /* DWDPTxListCell.m in Sources */, C9D2C8942A320AA000D15901 /* DerivationPathKeysHeaderView.swift in Sources */, C9D2C8952A320AA000D15901 /* ConvertCryptoOrderPreviewModel.swift in Sources */, - C9D2C8962A320AA000D15901 /* DWContactsSearchDataSourceObject.m in Sources */, C9D2C8972A320AA000D15901 /* RatesProvider.swift in Sources */, C9D2C8982A320AA000D15901 /* CBAuth.swift in Sources */, + C943B51C2A40A54600AF23C5 /* SuccessInvitationViewController.swift in Sources */, C9D2C8992A320AA000D15901 /* DWButton.m in Sources */, C9D2C89A2A320AA000D15901 /* CoinbaseCreateAddressesRequest.swift in Sources */, + C943B3382A408CED00AF23C5 /* DWEditProfileAvatarView.m in Sources */, + C943B5582A40DA3700AF23C5 /* DWFullScreenModalControllerViewController.m in Sources */, C9D2C89B2A320AA000D15901 /* DWImportWalletInfoViewController.m in Sources */, C9D2C89C2A320AA000D15901 /* TxListEmptyTableViewCell.swift in Sources */, C9D2C89D2A320AA000D15901 /* SyncingAlertContentView.swift in Sources */, C9D2C89E2A320AA000D15901 /* AddressUserInfo.swift in Sources */, C9D2C89F2A320AA000D15901 /* MerchantInfoViewController.swift in Sources */, + C943B5062A40A54600AF23C5 /* UICollectionView+DWDPItemDequeue.m in Sources */, C9D2C8A02A320AA000D15901 /* SendCoinsService.swift in Sources */, C9D2C8A12A320AA000D15901 /* ShortcutAction.swift in Sources */, C9D2C8A22A320AA000D15901 /* DWModalChevronView.m in Sources */, C9D2C8A32A320AA000D15901 /* DWBaseLegacyViewController.m in Sources */, - C9D2C8A42A320AA000D15901 /* DWNoNotificationsCell.m in Sources */, C9D2C8A52A320AA000D15901 /* HomeView.swift in Sources */, C9D2C8A62A320AA000D15901 /* CoinbaseRatesProvider.swift in Sources */, C9D2C8A72A320AA000D15901 /* TaxReportGenerator.swift in Sources */, C9D2C8A82A320AA000D15901 /* CrowdNodeError.swift in Sources */, C9D2C8A92A320AA000D15901 /* WithdrawalLimit.swift in Sources */, + C943B4B12A40A54600AF23C5 /* DWBaseContactsViewController.m in Sources */, + C943B5302A40A54600AF23C5 /* DWNotificationsFetchedDataSource.m in Sources */, C9D2C8AA2A320AA000D15901 /* DWPayModel.m in Sources */, - C9D2C8AB2A320AA000D15901 /* DWSearchStateViewController.m in Sources */, - C9D2C8AC2A320AA000D15901 /* DWDPTxListCell.m in Sources */, + C943B54B2A40B52F00AF23C5 /* UIView+DWAutolayout.m in Sources */, + C943B3332A408CED00AF23C5 /* DWTextFieldFormCellModel.m in Sources */, C9D2C8AD2A320AA000D15901 /* PointOfUseDetailsView.swift in Sources */, C9D2C8AE2A320AA000D15901 /* DSChain+DashWallet.m in Sources */, C9D2C8AF2A320AA000D15901 /* FromLabel.swift in Sources */, C9D2C8B02A320AA000D15901 /* DWPhraseRepairChildViewController.m in Sources */, C9D2C8B12A320AA000D15901 /* SingleInputAddressSelector.swift in Sources */, + C943B5382A40A65B00AF23C5 /* DWScrollingViewController.m in Sources */, C9D2C8B22A320AA000D15901 /* CoinbaseAccountAddress.swift in Sources */, C9D2C8B32A320AA000D15901 /* DWMainMenuTableViewCell.m in Sources */, C9D2C8B42A320AA000D15901 /* CrowdNodeTransferViewController.swift in Sources */, - C9D2C8B52A320AA000D15901 /* DWUserSearchViewController.m in Sources */, C9D2C8B62A320AA000D15901 /* CBAuthInterop.swift in Sources */, - C9D2C8B72A320AA000D15901 /* UIFont+DWDPItem.m in Sources */, C9D2C8B82A320AA000D15901 /* CoinbaseAmountViewController.swift in Sources */, C9D2C8B92A320AA000D15901 /* DWUpholdAuthViewController.m in Sources */, C9D2C8BA2A320AA000D15901 /* DWSegmentSliderFormCellModel.m in Sources */, + C943B5192A40A54600AF23C5 /* DWSendInviteFlowController.m in Sources */, C9D2C8BB2A320AA000D15901 /* DWSeedPhraseView.m in Sources */, - C9D2C8BC2A320AA000D15901 /* DWDPAvatarView.m in Sources */, C9D2C8BD2A320AA000D15901 /* UIStackView+DashWallet.swift in Sources */, - C9D2C8BE2A320AA000D15901 /* DWUserProfileContactActionsCell.m in Sources */, + C943B34E2A40A4C500AF23C5 /* DWInfoPopupContentView.m in Sources */, C9D2C8BF2A320AA000D15901 /* ExplorePointOfUse.swift in Sources */, - C9D2C8C02A320AA000D15901 /* DWUserProfileDataSourceObject.m in Sources */, C9D2C8C12A320AA000D15901 /* DWDashPayModel.m in Sources */, C9D2C8C22A320AA000D15901 /* TransactionObserver.swift in Sources */, + C943B4EB2A40A54600AF23C5 /* DWModalUserProfileViewController.m in Sources */, C9D2C8C32A320AA000D15901 /* DWInfoTextCell.m in Sources */, + C943B50D2A40A54600AF23C5 /* DWConfirmInvitationViewController.m in Sources */, + C943B4F62A40A54600AF23C5 /* DWDPContactObject.m in Sources */, C9D2C8C42A320AA000D15901 /* ColorizedText.swift in Sources */, - C9D2C8C52A320AA000D15901 /* DWDPPendingRequestObject.m in Sources */, - C9D2C8C62A320AA000D15901 /* DWModalUserProfileViewController.m in Sources */, C9D2C8C72A320AA000D15901 /* PaymentsViewController.swift in Sources */, - C9D2C8C82A320AA000D15901 /* DWDPContactsItemsFactory.m in Sources */, C9D2C8C92A320AA000D15901 /* ServiceDataSource.swift in Sources */, C9D2C8CA2A320AA000D15901 /* ConverterView.swift in Sources */, C9D2C8CB2A320AA000D15901 /* DWModalBaseAnimation.m in Sources */, C9D2C8CC2A320AA000D15901 /* SpendableTransaction.swift in Sources */, + C943B31F2A408CED00AF23C5 /* DWUserProfileModalQRViewController.m in Sources */, C9D2C8CD2A320AA000D15901 /* StakingInfoDialogController.swift in Sources */, - C9D2C8CE2A320AA000D15901 /* DWCreateUsernameViewController.m in Sources */, C9D2C8CF2A320AA000D15901 /* TwoFactorAuthViewController.swift in Sources */, C9D2C8D02A320AA000D15901 /* Taxes.swift in Sources */, C9D2C8D12A320AA000D15901 /* DWSeedWordModel+DWLayoutSupport.m in Sources */, C9D2C8D22A320AA000D15901 /* CoinbaseAPIClient.swift in Sources */, + C943B4ED2A40A54600AF23C5 /* DWPendingContactInfoView.m in Sources */, C9D2C8D32A320AA000D15901 /* SendAmountViewController.swift in Sources */, C9D2C8D42A320AA000D15901 /* DWAboutViewController.m in Sources */, + C943B51E2A40A54600AF23C5 /* InvitationTopView.swift in Sources */, C9D2C8D52A320AA000D15901 /* AccountService.swift in Sources */, C9D2C8D62A320AA000D15901 /* ListHandlerView.swift in Sources */, + C943B5152A40A54600AF23C5 /* DWInvitationTableViewCell.m in Sources */, C9D2C8D72A320AA000D15901 /* DWSecurityMenuViewController.m in Sources */, C9D2C8D82A320AA000D15901 /* CrowdNode.swift in Sources */, C9D2C8D92A320AA000D15901 /* SQLite+ExloreDash.swift in Sources */, - C9D2C8DA2A320AA000D15901 /* DWFilterHeaderView.m in Sources */, - C9D2C8DB2A320AA000D15901 /* DWBaseContactsModel.m in Sources */, C9D2C8DC2A320AA000D15901 /* DWAdvancedSecurityModel.m in Sources */, C9D2C8DD2A320AA000D15901 /* AtmDataProvider.swift in Sources */, + C943B50E2A40A54600AF23C5 /* DWConfirmInvitationContentView.m in Sources */, C9D2C8DE2A320AA000D15901 /* GettingStartedViewController.swift in Sources */, C9D2C8DF2A320AA000D15901 /* NetworkUnavailableView.swift in Sources */, C9D2C8E02A320AA000D15901 /* DWBorderedActionButton.m in Sources */, + C943B4FC2A40A54600AF23C5 /* DWDPEstablishedContactNotificationObject.m in Sources */, + C943B4D92A40A54600AF23C5 /* DWConfirmUsernameContentView.m in Sources */, C9D2C8E12A320AA000D15901 /* DWLockScreenViewController.m in Sources */, C9D2C8E22A320AA000D15901 /* UpholdTransferViewController.swift in Sources */, + C943B31E2A408CED00AF23C5 /* DWUserProfileModalQRContentView.m in Sources */, C9D2C8E32A320AA000D15901 /* DWVerifySeedPhraseViewController.m in Sources */, C9D2C8E42A320AA000D15901 /* DerivationPathKeysCell.swift in Sources */, C9D2C8E52A320AA000D15901 /* Cells.swift in Sources */, C9D2C8E62A320AA000D15901 /* DWContainerViewController.m in Sources */, C9D2C8E72A320AA000D15901 /* DashTextAttachment.swift in Sources */, + C943B5432A40AFD100AF23C5 /* DWTxDetailPopupViewController.m in Sources */, C9D2C8E82A320AA000D15901 /* HomeBalanceView.swift in Sources */, C9D2C8E92A320AA000D15901 /* PointOfUseListFiltersModel.swift in Sources */, + C943B3262A408CED00AF23C5 /* DWCropAvatarViewController.m in Sources */, C9D2C8EA2A320AA000D15901 /* ConvertCryptoOrderPreviewViews.swift in Sources */, C9D2C8EB2A320AA000D15901 /* DWAboutModel.m in Sources */, + C943B4B52A40A54600AF23C5 /* DWContactsFetchedDataSource.m in Sources */, C9D2C8EC2A320AA000D15901 /* CNCreateAccountTxDetailsViewController.swift in Sources */, - C9D2C8ED2A320AA000D15901 /* UIColor+DWDashPay.m in Sources */, C9D2C8EE2A320AA000D15901 /* WelcomeToCrowdNodeViewController.swift in Sources */, + C943B3282A408CED00AF23C5 /* DWAvatarEditSelectorContentView.m in Sources */, + C943B4DD2A40A54600AF23C5 /* DWDPWelcomeViewController.m in Sources */, C9D2C8EF2A320AA000D15901 /* TerritoriesListCell.swift in Sources */, C9D2C8F02A320AA000D15901 /* DWProgressView.m in Sources */, C9D2C8F12A320AA000D15901 /* TxDetailCells.swift in Sources */, @@ -9432,6 +10153,7 @@ "PB_FIELD_32BIT=1", "PB_NO_PACKED_STRUCTS=1", "PB_ENABLE_MALLOC=1", + "DASHPAY=1", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( @@ -9446,7 +10168,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.0.1; + MARKETING_VERSION = 6.0.0; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLiteMigrationManager.swift/SQLiteMigrationManager.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/FBLPromises/PromisesObjC.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/SSZipArchive/SSZipArchive.modulemap\" -DDASHPAY"; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dashpaytnt; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -9485,6 +10207,7 @@ "PB_FIELD_32BIT=1", "PB_NO_PACKED_STRUCTS=1", "PB_ENABLE_MALLOC=1", + "DASHPAY=1", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( @@ -9499,7 +10222,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.0.1; + MARKETING_VERSION = 6.0.0; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLiteMigrationManager.swift/SQLiteMigrationManager.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/FBLPromises/PromisesObjC.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/SSZipArchive/SSZipArchive.modulemap\""; "OTHER_SWIFT_FLAGS[arch=*]" = "-DDebug -DDASH_TESTNET -DDASHPAY"; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dashpaytnt; @@ -9538,6 +10261,7 @@ "PB_FIELD_32BIT=1", "PB_NO_PACKED_STRUCTS=1", "PB_ENABLE_MALLOC=1", + "DASHPAY=1", ); "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ( "$(inherited)", @@ -9548,6 +10272,7 @@ "PB_FIELD_32BIT=1", "PB_NO_PACKED_STRUCTS=1", "PB_ENABLE_MALLOC=1", + "DASHPAY=1", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( @@ -9562,7 +10287,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.0.1; + MARKETING_VERSION = 6.0.0; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLiteMigrationManager.swift/SQLiteMigrationManager.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/FBLPromises/PromisesObjC.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/SSZipArchive/SSZipArchive.modulemap\" -DDASHPAY"; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dashpaytnt; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -9610,6 +10335,7 @@ "PB_FIELD_32BIT=1", "PB_NO_PACKED_STRUCTS=1", "PB_ENABLE_MALLOC=1", + "DASHPAY=1", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( @@ -9624,7 +10350,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.0.1; + MARKETING_VERSION = 6.0.0; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap\" -Xcc -fmodule-map-file=\"${PODS_CONFIGURATION_BUILD_DIR}/SQLiteMigrationManager.swift/SQLiteMigrationManager.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/FBLPromises/PromisesObjC.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/SSZipArchive/SSZipArchive.modulemap\""; PRODUCT_BUNDLE_IDENTIFIER = org.dashfoundation.dashpaytnt; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/DashWallet/AppDelegate.m b/DashWallet/AppDelegate.m index 501448223..a32ac80fb 100644 --- a/DashWallet/AppDelegate.m +++ b/DashWallet/AppDelegate.m @@ -55,6 +55,9 @@ #error "Debug flag FRESH_INSTALL is active during Release build. Comment this out to continue." #endif /* (FRESH_INSTALL && !DEBUG) */ +#if DASHPAY +NSNotificationName const DWDashPayAvailabilityStatusUpdatedNotification = @"DWDashPayAvailabilityStatusUpdatedNotification"; +#endif NS_ASSUME_NONNULL_BEGIN @interface AppDelegate () diff --git a/DashWallet/Sources/Models/DWGlobalOptions.h b/DashWallet/Sources/Models/DWGlobalOptions.h index 4ffc11e46..0e12278a3 100644 --- a/DashWallet/Sources/Models/DWGlobalOptions.h +++ b/DashWallet/Sources/Models/DWGlobalOptions.h @@ -51,18 +51,22 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, strong) NSDate *dateReclassifyYourTransactionsFlowActivated; @property (nullable, nonatomic, strong) NSDate *dateHistoricalRatesActivated; @property (nonatomic, assign) NSInteger paymentsScreenCurrentTab; - -@property (nullable, nonatomic, copy) NSString *dashpayUsername; -@property (nonatomic, assign) BOOL dashpayRegistrationCompleted; -@property (nullable, nonatomic, strong) NSDate *mostRecentViewedNotificationDate; - @property (nonatomic, assign, getter=isResyncingWallet) BOOL resyncingWallet; @property (nonatomic, assign) DWPaymentCurrency selectedPaymentCurrency; @property (nonatomic, assign) BOOL exploreDashMerchantsInfoShown; - @property (nonatomic, assign) BOOL coinbaseInfoShown; + +#ifdef DASHPAY +@property (nonatomic, assign) BOOL dashPayRegistrationOpenedOnce; +@property (nonatomic, assign) BOOL dashpayRegistrationCompleted; +@property (nonatomic, assign) BOOL dpInvitationFlowEnabled; +@property (nonatomic, assign) BOOL shouldShowInvitationsBadge; + +@property (nullable, nonatomic, copy) NSString *dashpayUsername; +@property (nullable, nonatomic, strong) NSDate *mostRecentViewedNotificationDate; +#endif // Non-dynamic - (BOOL)lockScreenDisabled; diff --git a/DashWallet/Sources/Models/DWGlobalOptions.m b/DashWallet/Sources/Models/DWGlobalOptions.m index 5ca10cec5..14f2614fa 100644 --- a/DashWallet/Sources/Models/DWGlobalOptions.m +++ b/DashWallet/Sources/Models/DWGlobalOptions.m @@ -37,9 +37,6 @@ @implementation DWGlobalOptions @dynamic balanceHidden; @dynamic shouldDisplayOnboarding; @dynamic paymentsScreenCurrentTab; -@dynamic dashpayUsername; -@dynamic dashpayRegistrationCompleted; -@dynamic mostRecentViewedNotificationDate; @dynamic resyncingWallet; @dynamic selectedPaymentCurrency; @dynamic shouldDisplayReclassifyYourTransactionsFlow; @@ -48,6 +45,12 @@ @implementation DWGlobalOptions @dynamic exploreDashMerchantsInfoShown; @dynamic coinbaseInfoShown; +#ifdef DASHPAY +@dynamic dashpayUsername; +@dynamic dashpayRegistrationCompleted; +@dynamic mostRecentViewedNotificationDate; +#endif + #pragma mark - Init - (instancetype)init { @@ -137,9 +140,6 @@ - (void)restoreToDefaults { self.shortcuts = nil; self.localNotificationsEnabled = YES; self.balanceHidden = NO; - self.dashpayUsername = nil; - self.dashpayRegistrationCompleted = NO; - self.mostRecentViewedNotificationDate = nil; self.resyncingWallet = NO; self.selectedPaymentCurrency = DWPaymentCurrencyDash; self.shouldDisplayReclassifyYourTransactionsFlow = YES; @@ -147,6 +147,13 @@ - (void)restoreToDefaults { self.dateHistoricalRatesActivated = nil; self.exploreDashMerchantsInfoShown = NO; self.coinbaseInfoShown = NO; + +#ifdef DASHPAY + self.dashpayUsername = nil; + self.dashpayRegistrationCompleted = NO; + self.mostRecentViewedNotificationDate = nil; + self.dashPayRegistrationOpenedOnce = NO; +#endif } @end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.m deleted file mode 100644 index e45599be2..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.m +++ /dev/null @@ -1,186 +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 "DWSearchViewController.h" - -#import - -#import "DWUIKit.h" -#import "UISearchBar+DWAdditions.h" -#import "UIView+DWRecursiveSubview.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWSearchViewController () - -@property (nonatomic, assign) BOOL requiresNoNavigationBar; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWSearchViewController - -@synthesize searchBar = _searchBar; -@synthesize contentView = _contentView; - -@synthesize requiresNoNavigationBar = _requiresNoNavigationBar; - -- (BOOL)requiresNoNavigationBar { - return _requiresNoNavigationBar; -} - -- (void)setRequiresNoNavigationBar:(BOOL)requiresNoNavigationBar { - _requiresNoNavigationBar = requiresNoNavigationBar; - - [self.navigationController setNavigationBarHidden:requiresNoNavigationBar animated:YES]; - [self setNeedsStatusBarAppearanceUpdate]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - [self.view addSubview:self.searchBar]; - [self.view addSubview:self.contentView]; - - [NSLayoutConstraint activateConstraints:@[ - [self.searchBar.topAnchor constraintEqualToAnchor:self.view.layoutMarginsGuide.topAnchor], - [self.searchBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [self.view.trailingAnchor constraintEqualToAnchor:self.searchBar.trailingAnchor], - - [self.contentView.topAnchor constraintEqualToAnchor:self.searchBar.bottomAnchor], - [self.contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [self.view.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], - [self.view.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor], - ]]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - // pre-layout view to avoid undesired animation if the keyboard is shown while appearing - [self.view layoutIfNeeded]; - [self ka_startObservingKeyboardNotifications]; -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - - [self ka_stopObservingKeyboardNotifications]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - - // Activate Search Bar initially - if (!self.disableSearchBarBecomesFirstResponderOnFirstAppearance) { - [self.searchBar becomeFirstResponder]; - self.disableSearchBarBecomesFirstResponderOnFirstAppearance = YES; - } -} - -- (UIStatusBarStyle)preferredStatusBarStyle { - return self.requiresNoNavigationBar ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent; -} - -- (void)viewDidLayoutSubviews { - [super viewDidLayoutSubviews]; - - if ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion >= 13) { - // hide semi-transparent overlays above UITextField in UISearchBar to achive basic white color - UISearchBar *searchBar = self.searchBar; - UITextField *searchTextField = (UITextField *)[searchBar dw_findSubviewOfClass:UITextField.class]; - UIView *searchTextFieldBackground = searchTextField.subviews.firstObject; - [searchTextFieldBackground.subviews makeObjectsPerformSelector:@selector(setHidden:) withObject:@YES]; - } -} - -#pragma mark - UISearchBarDelegate - -- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { - [searchBar setShowsCancelButton:YES animated:YES]; - self.requiresNoNavigationBar = YES; -} - -- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar { - dispatch_async(dispatch_get_main_queue(), ^{ - if (searchBar.showsCancelButton) { - [searchBar dw_enableCancelButton]; - } - }); -} - -- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { - // To be overriden -} - -- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { - [searchBar resignFirstResponder]; -} - -- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { - [searchBar setShowsCancelButton:NO animated:YES]; - [searchBar resignFirstResponder]; - - self.requiresNoNavigationBar = NO; -} - -- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { - return YES; -} - -#pragma mark - Keyboard - -- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height - animationDuration:(NSTimeInterval)animationDuration - animationCurve:(UIViewAnimationCurve)animationCurve { - // To be overriden -} - -#pragma mark - Private - -- (UISearchBar *)searchBar { - if (_searchBar == nil) { - UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectZero]; - searchBar.translatesAutoresizingMaskIntoConstraints = NO; - searchBar.delegate = self; - searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone; - searchBar.barTintColor = [UIColor labelColor]; - searchBar.searchBarStyle = UISearchBarStyleMinimal; - searchBar.tintColor = [UIColor dw_dashBlueColor]; - UITextField *searchTextField = (UITextField *)[searchBar dw_findSubviewOfClass:UITextField.class]; - searchTextField.tintColor = [UIColor dw_dashBlueColor]; - searchTextField.textColor = [UIColor dw_darkTitleColor]; - searchTextField.backgroundColor = [UIColor dw_backgroundColor]; - _searchBar = searchBar; - } - return _searchBar; -} - -- (UIView *)contentView { - if (_contentView == nil) { - UIView *contentView = [[UIView alloc] init]; - contentView.translatesAutoresizingMaskIntoConstraints = NO; - contentView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - _contentView = contentView; - } - return _contentView; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController+DWProtected.h b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController+DWProtected.h deleted file mode 100644 index 5bdc2b58f..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController+DWProtected.h +++ /dev/null @@ -1,43 +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 "DWBaseContactsContentViewController.h" - -#import "DWContactsSearchInfoHeaderView.h" -#import "DWFilterHeaderView.h" -#import "DWTitleActionHeaderView.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWBaseContactsContentViewController () - -@property (readonly, nonatomic, strong) id payModel; -@property (readonly, nonatomic, strong) id dataProvider; - -@property (null_resettable, nonatomic, strong) UICollectionView *collectionView; - -@property (null_resettable, nonatomic, strong) DWContactsSearchInfoHeaderView *measuringSearchHeaderView; -@property (null_resettable, nonatomic, strong) DWTitleActionHeaderView *measuringRequestsHeaderView; -@property (null_resettable, nonatomic, strong) DWFilterHeaderView *measuringContactsHeaderView; - -@property (null_resettable, nonatomic, copy) NSAttributedString *searchHeaderTitle; -@property (null_resettable, nonatomic, copy) NSString *requestsHeaderTitle; -@property (null_resettable, nonatomic, copy) NSAttributedString *contactsHeaderFilterButtonTitle; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.h deleted file mode 100644 index 3e9208327..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.h +++ /dev/null @@ -1,46 +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 - -#import "DWContactsDataSource.h" -#import "DWDPBasicUserItem.h" -#import "DWDPNewIncomingRequestItem.h" -#import "DWPayModelProtocol.h" -#import "DWTransactionListDataProviderProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWBaseContactsContentViewController : UIViewController - -@property (readonly, nonatomic, assign) NSUInteger maxVisibleContactRequestsCount; - -@property (nullable, nonatomic, weak) id itemsDelegate; -@property (nonatomic, strong) id dataSource; - -- (instancetype)initWithPayModel:(id)payModel - dataProvider:(id)dataProvider NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE; -- (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 - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.m deleted file mode 100644 index 4774989a0..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.m +++ /dev/null @@ -1,358 +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 "DWBaseContactsContentViewController+DWProtected.h" - -#import "DWDPBasicCell.h" -#import "DWListCollectionLayout.h" -#import "DWSharedUIConstants.h" -#import "DWUIKit.h" -#import "DWUserProfileViewController.h" -#import "UICollectionView+DWDPItemDequeue.h" - -typedef NS_ENUM(NSInteger, DWContactsContentSection) { - DWContactsContentSectionSearch, - DWContactsContentSectionRequests, - DWContactsContentSectionContacts, - CountOfDWContactsContentSections, -}; - -@implementation DWBaseContactsContentViewController - -- (instancetype)initWithPayModel:(id)payModel - dataProvider:(id)dataProvider { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _payModel = payModel; - _dataProvider = dataProvider; - } - return self; -} - -- (NSUInteger)maxVisibleContactRequestsCount { - return NSUIntegerMax; -} - -- (void)setDataSource:(id)dataSource { - _dataSource = dataSource; - - self.searchHeaderTitle = nil; - self.requestsHeaderTitle = nil; - self.contactsHeaderFilterButtonTitle = nil; - - // TODO: DP polishing: diff reload - [self.collectionView reloadData]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - [self.view addSubview:self.collectionView]; -} - -#pragma mark - UICollectionViewDataSource - -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { - return CountOfDWContactsContentSections; -} - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - if (section == DWContactsContentSectionSearch) { - return 0; - } - else if (section == DWContactsContentSectionRequests) { - return MIN(self.dataSource.requestsCount, self.maxVisibleContactRequestsCount); - } - else { - return self.dataSource.contactsCount; - } -} - -- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; - NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); - const CGFloat contentWidth = layout.contentWidth; - - id item = [self itemAtIndexPath:indexPath]; - DWDPBasicCell *cell = [collectionView dw_dequeueReusableCellForItem:item atIndexPath:indexPath]; - cell.contentWidth = contentWidth; - cell.displayItemBackgroundView = indexPath.section == DWContactsContentSectionRequests; - cell.delegate = self.itemsDelegate; - [cell setItem:item highlightedText:self.dataSource.trimmedQuery]; - return cell; -} - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - const NSInteger section = indexPath.section; - if ([self shouldDisplayHeaderForSection:section] == NO) { - return [[UICollectionReusableView alloc] init]; - } - - if (section == DWContactsContentSectionSearch) { - DWContactsSearchInfoHeaderView *headerView = (DWContactsSearchInfoHeaderView *)[collectionView - dequeueReusableSupplementaryViewOfKind:kind - withReuseIdentifier:DWContactsSearchInfoHeaderView.dw_reuseIdentifier - forIndexPath:indexPath]; - headerView.titleLabel.attributedText = self.searchHeaderTitle; - return headerView; - } - else if (section == DWContactsContentSectionRequests) { - const BOOL shouldHideViewAll = [self shouldHideViewAllRequests]; - DWTitleActionHeaderView *headerView = (DWTitleActionHeaderView *)[collectionView - dequeueReusableSupplementaryViewOfKind:kind - withReuseIdentifier:DWTitleActionHeaderView.dw_reuseIdentifier - forIndexPath:indexPath]; - headerView.titleLabel.text = self.requestsHeaderTitle; - headerView.delegate = self; - headerView.actionButton.hidden = shouldHideViewAll; - [headerView.actionButton setTitle:NSLocalizedString(@"View All", nil) forState:UIControlStateNormal]; - return headerView; - } - else { - DWFilterHeaderView *headerView = (DWFilterHeaderView *)[collectionView - dequeueReusableSupplementaryViewOfKind:kind - withReuseIdentifier:DWFilterHeaderView.dw_reuseIdentifier - forIndexPath:indexPath]; - headerView.titleLabel.text = NSLocalizedString(@"My Contacts", nil); - headerView.delegate = self; - [headerView.filterButton setAttributedTitle:self.contactsHeaderFilterButtonTitle forState:UIControlStateNormal]; - return headerView; - } -} - -#pragma mark - UICollectionViewDelegate - -- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - [collectionView deselectItemAtIndexPath:indexPath animated:YES]; - - id item = [self itemAtIndexPath:indexPath]; - - DWUserProfileViewController *controller = - [[DWUserProfileViewController alloc] initWithItem:item - payModel:self.payModel - dataProvider:self.dataProvider - shouldSkipUpdating:YES]; - [self.navigationController pushViewController:controller animated:YES]; -} - -#pragma mark - UICollectionViewDelegateFlowLayout - -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { - if ([self shouldDisplayHeaderForSection:section] == NO) { - return CGSizeZero; - } - - DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; - NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); - const CGFloat contentWidth = layout.contentWidth; - - UIView *measuringView = nil; - if (section == DWContactsContentSectionSearch) { - measuringView = self.measuringSearchHeaderView; - } - else if (section == DWContactsContentSectionRequests) { - measuringView = self.measuringRequestsHeaderView; - } - else { - measuringView = self.measuringContactsHeaderView; - } - measuringView.frame = CGRectMake(0, 0, contentWidth, 300); - CGSize size = [measuringView systemLayoutSizeFittingSize:CGSizeMake(contentWidth, UILayoutFittingExpandedSize.height) - withHorizontalFittingPriority:UILayoutPriorityRequired - verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; - - return size; -} - -#pragma mark - DWFilterHeaderViewDelegate - -- (void)filterHeaderView:(DWFilterHeaderView *)view filterButtonAction:(UIView *)sender { - // to be overriden -} - -#pragma mark - DWTitleActionHeaderViewDelegate - -- (void)titleActionHeaderView:(DWTitleActionHeaderView *)view buttonAction:(UIView *)sender { - // to be overriden -} - -#pragma mark - Private - -- (id)itemAtIndexPath:(NSIndexPath *)indexPath { - NSAssert(indexPath.section > 0, @"Section 0 is empty and should not have any data items"); - NSIndexPath *dataIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:indexPath.section - 1]; - id item = [self.dataSource itemAtIndexPath:dataIndexPath]; - return item; -} - -- (BOOL)shouldDisplayHeaderForSection:(NSInteger)section { - if (section == DWContactsContentSectionSearch && self.dataSource.isSearching == NO) { - return NO; - } - else if (section == DWContactsContentSectionRequests && self.dataSource.requestsCount == 0) { - return NO; - } - else if (section == DWContactsContentSectionContacts && self.dataSource.contactsCount == 0) { - return NO; - } - return YES; -} - -- (UICollectionView *)collectionView { - if (_collectionView == nil) { - DWListCollectionLayout *layout = [[DWListCollectionLayout alloc] init]; - - UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds - collectionViewLayout:layout]; - collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - collectionView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - collectionView.dataSource = self; - collectionView.delegate = self; - collectionView.alwaysBounceVertical = YES; - [collectionView dw_registerDPItemCells]; - [collectionView registerClass:DWContactsSearchInfoHeaderView.class - forSupplementaryViewOfKind:UICollectionElementKindSectionHeader - withReuseIdentifier:DWContactsSearchInfoHeaderView.dw_reuseIdentifier]; - [collectionView registerClass:DWTitleActionHeaderView.class - forSupplementaryViewOfKind:UICollectionElementKindSectionHeader - withReuseIdentifier:DWTitleActionHeaderView.dw_reuseIdentifier]; - [collectionView registerClass:DWFilterHeaderView.class - forSupplementaryViewOfKind:UICollectionElementKindSectionHeader - withReuseIdentifier:DWFilterHeaderView.dw_reuseIdentifier]; - - _collectionView = collectionView; - } - return _collectionView; -} - -- (DWContactsSearchInfoHeaderView *)measuringSearchHeaderView { - if (_measuringSearchHeaderView == nil) { - _measuringSearchHeaderView = [[DWContactsSearchInfoHeaderView alloc] initWithFrame:CGRectZero]; - } - _measuringSearchHeaderView.titleLabel.attributedText = self.searchHeaderTitle; - return _measuringSearchHeaderView; -} - -- (DWTitleActionHeaderView *)measuringRequestsHeaderView { - if (_measuringRequestsHeaderView == nil) { - DWTitleActionHeaderView *view = [[DWTitleActionHeaderView alloc] initWithFrame:CGRectZero]; - [view.actionButton setTitle:NSLocalizedString(@"View All", nil) forState:UIControlStateNormal]; - _measuringRequestsHeaderView = view; - } - _measuringRequestsHeaderView.titleLabel.text = self.requestsHeaderTitle; - _measuringRequestsHeaderView.actionButton.hidden = [self shouldHideViewAllRequests]; - return _measuringRequestsHeaderView; -} - -- (DWFilterHeaderView *)measuringContactsHeaderView { - if (_measuringContactsHeaderView == nil) { - DWFilterHeaderView *headerView = [[DWFilterHeaderView alloc] initWithFrame:CGRectZero]; - headerView.titleLabel.text = NSLocalizedString(@"My Contacts", nil); - _measuringContactsHeaderView = headerView; - } - [_measuringContactsHeaderView.filterButton setAttributedTitle:self.contactsHeaderFilterButtonTitle - forState:UIControlStateNormal]; - return _measuringContactsHeaderView; -} - -- (BOOL)shouldHideViewAllRequests { - id dataSource = self.dataSource; - const NSUInteger contactRequestsCount = dataSource.requestsCount; - const BOOL isSearching = dataSource.isSearching; - const BOOL hasMore = contactRequestsCount > self.maxVisibleContactRequestsCount; - return isSearching || !hasMore; -} - -- (NSAttributedString *)searchHeaderTitle { - if (_searchHeaderTitle == nil) { - NSString *query = self.dataSource.trimmedQuery; - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - [result beginEditing]; - NSDictionary *plainAttributes = @{ - NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote], - }; - NSAttributedString *prefix = - [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Search results for \"", @"Search results for \"John Doe\"") - attributes:plainAttributes]; - [result appendAttributedString:prefix]; - NSAttributedString *queryString = - [[NSAttributedString alloc] initWithString:query - attributes:@{ - NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline], - }]; - [result appendAttributedString:queryString]; - NSAttributedString *suffix = [[NSAttributedString alloc] initWithString:@"\"" attributes:plainAttributes]; - [result appendAttributedString:suffix]; - [result endEditing]; - _searchHeaderTitle = [result copy]; - } - return _searchHeaderTitle; -} - -- (NSString *)requestsHeaderTitle { - if (_requestsHeaderTitle == nil) { - const BOOL shouldHideViewAll = [self shouldHideViewAllRequests]; - if (shouldHideViewAll) { - _requestsHeaderTitle = NSLocalizedString(@"Contact Requests", nil); - } - else { - id dataSource = self.dataSource; - const NSUInteger contactRequestsCount = dataSource.requestsCount; - _requestsHeaderTitle = [NSString stringWithFormat:@"%@ (%ld)", - NSLocalizedString(@"Contact Requests", nil), - contactRequestsCount]; - } - } - return _requestsHeaderTitle; -} - -- (NSAttributedString *)contactsHeaderFilterButtonTitle { - if (_contactsHeaderFilterButtonTitle == nil) { - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - [result beginEditing]; - NSAttributedString *prefix = - [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Sort by", nil) - attributes:@{ - NSForegroundColorAttributeName : [UIColor dw_tertiaryTextColor], - NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleCaption1], - }]; - [result appendAttributedString:prefix]; - - [result appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; - NSString *optionValue = nil; - switch (self.dataSource.sortMode) { - case DWContactsSortMode_ByUsername: { - optionValue = NSLocalizedString(@"Name", nil); - break; - } - } - NSAttributedString *option = - [[NSAttributedString alloc] initWithString:optionValue - attributes:@{ - NSForegroundColorAttributeName : [UIColor dw_dashBlueColor], - NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote], - }]; - [result appendAttributedString:option]; - [result endEditing]; - _contactsHeaderFilterButtonTitle = [result copy]; - } - return _contactsHeaderFilterButtonTitle; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController+DWProtected.h b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController+DWProtected.h deleted file mode 100644 index 465dfb205..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController+DWProtected.h +++ /dev/null @@ -1,40 +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 "DWBaseContactsViewController.h" - -#import "DWBaseContactsContentViewController.h" -#import "DWBaseContactsModel.h" -#import "DWDPNewIncomingRequestItem.h" -#import "DWSearchStateViewController.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWBaseContactsViewController () - -@property (readonly, nonatomic, strong) id payModel; -@property (readonly, nonatomic, strong) id dataProvider; - -@property (readonly, nonatomic, strong) DWBaseContactsModel *model; -@property (readonly, nonatomic, strong) DWSearchStateViewController *stateController; -@property (readonly, nonatomic, strong) DWBaseContactsContentViewController *contentController; - -- (void)addContactButtonAction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.m deleted file mode 100644 index a1524f003..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.m +++ /dev/null @@ -1,141 +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 "DWBaseContactsViewController+DWProtected.h" - -#import "DWUserSearchViewController.h" -#import "UIView+DWFindConstraints.h" -#import "UIViewController+DWEmbedding.h" - -NS_ASSUME_NONNULL_BEGIN - -// Some sane limit to prevent breaking layout -static NSInteger const MAX_SEARCH_LENGTH = 100; - -NS_ASSUME_NONNULL_END - -@implementation DWBaseContactsViewController - -- (instancetype)initWithPayModel:(id)payModel - dataProvider:(id)dataProvider { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _payModel = payModel; - _dataProvider = dataProvider; - } - return self; -} - -- (void)dealloc { - DSLog(@"☠️ %@", NSStringFromClass(self.class)); -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.disableSearchBarBecomesFirstResponderOnFirstAppearance = YES; - - [self dw_embedChild:self.stateController inContainer:self.contentView]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - [self.model start]; -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - - [self.model stop]; -} - -#pragma mark - DWContactsModelDelegate - -- (void)contactsModelDidUpdate:(DWBaseContactsModel *)model { - self.searchBar.hidden = NO; - id dataSource = model.dataSource; - if (dataSource.isEmpty) { - if (dataSource.isSearching) { - [self.stateController setNoResultsLocalStateWithQuery:dataSource.trimmedQuery]; - } - else { - self.searchBar.hidden = YES; - [self.stateController setPlaceholderLocalState]; - } - [self.contentController dw_detachFromParent]; - } - else { - self.contentController.dataSource = dataSource; - - if (self.contentController.parentViewController == nil) { - [self dw_embedChild:self.contentController inContainer:self.contentView]; - } - } -} - -#pragma mark - UISearchBarDelegate - -- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { - [self.model searchWithQuery:self.searchBar.text]; -} - -- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { - NSString *resultText = [searchBar.text stringByReplacingCharactersInRange:range withString:text]; - return resultText.length <= MAX_SEARCH_LENGTH; -} - -#pragma mark - DWSearchStateViewControllerDelegate - -- (void)searchStateViewController:(DWSearchStateViewController *)controller buttonAction:(UIButton *)sender { - [self addContactButtonAction]; -} - -#pragma mark - DWDPNewIncomingRequestItemDelegate - -- (void)acceptIncomingRequest:(id)item { - [self.model acceptContactRequest:item]; -} - -- (void)declineIncomingRequest:(id)item { - [self.model declineContactRequest:item]; -} - -#pragma mark - Keyboard - -- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height - animationDuration:(NSTimeInterval)animationDuration - animationCurve:(UIViewAnimationCurve)animationCurve { - NSLayoutConstraint *constraint = [self.stateController.view dw_findConstraintWithAttribute:NSLayoutAttributeBottom]; - constraint.constant = height; - [self.view layoutIfNeeded]; -} - -#pragma mark - Actions - -- (void)addContactButtonAction { - if (!self.model.hasBlockchainIdentity) { - return; - } - - DWUserSearchViewController *controller = - [[DWUserSearchViewController alloc] initWithPayModel:self.payModel - dataProvider:self.dataProvider]; - [self.navigationController pushViewController:controller animated:YES]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.m deleted file mode 100644 index 8d56f3a20..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.m +++ /dev/null @@ -1,42 +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 "DWContactsContentViewController.h" - -#import "DWBaseContactsContentViewController+DWProtected.h" - -#import "DWUIKit.h" - -@implementation DWContactsContentViewController - -- (NSUInteger)maxVisibleContactRequestsCount { - return 3; -} - -#pragma mark - DWFilterHeaderViewDelegate - -- (void)filterHeaderView:(DWFilterHeaderView *)view filterButtonAction:(UIView *)sender { - [self.delegate contactsContentController:self contactsFilterButtonAction:sender]; -} - -#pragma mark - DWTitleActionHeaderViewDelegate - -- (void)titleActionHeaderView:(DWTitleActionHeaderView *)view buttonAction:(UIView *)sender { - [self.delegate contactsContentController:self contactRequestsButtonAction:sender]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsPlaceholderViewController.h similarity index 72% rename from DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.h rename to DashWallet/Sources/UI/DashPay/Contacts/DWContactsPlaceholderViewController.h index f27c6d389..bd817cc93 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.h +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsPlaceholderViewController.h @@ -15,17 +15,16 @@ // limitations under the License. // -#import "DWSearchViewController.h" +#import "DWBaseActionButtonViewController.h" -#import "DWPayModelProtocol.h" -#import "DWTransactionListDataProviderProtocol.h" +#import "DWDashPayProtocol.h" +#import "DWDashPayReadyProtocol.h" NS_ASSUME_NONNULL_BEGIN -@interface DWBaseContactsViewController : DWSearchViewController +@interface DWContactsPlaceholderViewController : DWBaseActionButtonViewController -- (instancetype)initWithPayModel:(id)payModel - dataProvider:(id)dataProvider NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDashPayModel:(id)dashPayModel dashPayReady:(id)dashPayReady; - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; - (nullable instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsPlaceholderViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsPlaceholderViewController.m new file mode 100644 index 000000000..fa45390d1 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsPlaceholderViewController.m @@ -0,0 +1,123 @@ +// +// 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 "DWContactsPlaceholderViewController.h" + +#import "DWDashPayModel.h" +#import "DWDashPaySetupFlowController.h" +#import "DWUIKit.h" + +@interface DWContactsPlaceholderViewController () + +@property (readonly, nonatomic, strong) id dashPayModel; +@property (readonly, nonatomic, strong) id dashPayReady; + +@end + +@implementation DWContactsPlaceholderViewController + +- (instancetype)initWithDashPayModel:(id)dashPayModel dashPayReady:(id)dashPayReady { + self = [super init]; + if (self) { + _dashPayModel = dashPayModel; + _dashPayReady = dashPayReady; + } + return self; +} + +- (NSString *)actionButtonTitle { + return NSLocalizedString(@"Upgrade", nil); +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"contacts_placeholder_icon"]]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.contentMode = UIViewContentModeCenter; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleTitle3]; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.text = NSLocalizedString(@"Upgrade to Evolution", nil); + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.numberOfLines = 0; + + UILabel *descriptionLabel = [[UILabel alloc] init]; + descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO; + descriptionLabel.textAlignment = NSTextAlignmentCenter; + descriptionLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + descriptionLabel.adjustsFontForContentSizeCategory = YES; + descriptionLabel.text = NSLocalizedString(@"Create your Username, find friends & family with their usernames and add them to your contacts", nil); + descriptionLabel.textColor = [UIColor dw_tertiaryTextColor]; + descriptionLabel.numberOfLines = 0; + + UIStackView *verticalStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ imageView, titleLabel, descriptionLabel ]]; + verticalStackView.translatesAutoresizingMaskIntoConstraints = NO; + verticalStackView.axis = UILayoutConstraintAxisVertical; + verticalStackView.spacing = 4.0; + [verticalStackView setCustomSpacing:26.0 afterView:imageView]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = self.view.backgroundColor; + [contentView addSubview:verticalStackView]; + + [NSLayoutConstraint activateConstraints:@[ + [verticalStackView.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor], + [verticalStackView.topAnchor constraintGreaterThanOrEqualToAnchor:contentView.topAnchor], + [contentView.bottomAnchor constraintGreaterThanOrEqualToAnchor:contentView.bottomAnchor], + [verticalStackView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:verticalStackView.trailingAnchor], + ]]; + + [self setupContentView:contentView]; + + // Model: + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(update) + name:DWDashPayRegistrationStatusUpdatedNotification + object:nil]; + [notificationCenter addObserver:self + selector:@selector(update) + name:DWDashPayAvailabilityStatusUpdatedNotification + object:nil]; + + [self update]; +} + +- (void)actionButtonAction:(id)sender { + DWDashPaySetupFlowController *controller = + [[DWDashPaySetupFlowController alloc] + initWithDashPayModel:self.dashPayModel + invitation:nil + definedUsername:nil]; + controller.modalPresentationStyle = UIModalPresentationFullScreen; + [self presentViewController:controller animated:YES completion:nil]; +} + +- (void)update { + self.actionButton.enabled = [self.dashPayReady isDashPayReady]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.m deleted file mode 100644 index 8fac85647..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.m +++ /dev/null @@ -1,130 +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 "DWContactsViewController.h" - -#import "DWBaseContactsViewController+DWProtected.h" -#import "DWContactsContentViewController.h" -#import "DWRequestsViewController.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWContactsViewController () - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWContactsViewController - -@synthesize model = _model; -@synthesize stateController = _stateController; -@synthesize contentController = _contentController; - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.title = NSLocalizedString(@"Contacts", nil); - self.searchBar.placeholder = NSLocalizedString(@"Search for a contact", nil); - - UIImage *image = [[UIImage imageNamed:@"dp_add_contact"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; - UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithImage:image - style:UIBarButtonItemStylePlain - target:self - action:@selector(addContactButtonAction)]; - self.navigationItem.rightBarButtonItem = button; -} - -#pragma mark - Private - -- (DWContactsModel *)model { - if (!_model) { - _model = [[DWContactsModel alloc] init]; - _model.delegate = self; - } - return _model; -} - -- (DWSearchStateViewController *)stateController { - if (_stateController == nil) { - _stateController = [[DWSearchStateViewController alloc] init]; - _stateController.delegate = self; - } - return _stateController; -} - -- (DWBaseContactsContentViewController *)contentController { - if (_contentController == nil) { - DWContactsContentViewController *controller = - [[DWContactsContentViewController alloc] initWithPayModel:self.payModel - dataProvider:self.dataProvider]; - controller.dataSource = self.model.dataSource; - controller.itemsDelegate = self; - controller.delegate = self; - _contentController = controller; - } - return _contentController; -} - -#pragma mark - DWContactsContentControllerDelegate - -- (void)contactsContentController:(DWContactsContentViewController *)controller - contactsFilterButtonAction:(UIButton *)sender { - NSString *title = NSLocalizedString(@"Sort Contacts", nil); - UIAlertController *alert = [UIAlertController - alertControllerWithTitle:title - message:nil - preferredStyle:UIAlertControllerStyleActionSheet]; - { - UIAlertAction *action = [UIAlertAction - actionWithTitle:NSLocalizedString(@"Name", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction *_Nonnull action) { - self.model.sortMode = DWContactsSortMode_ByUsername; - }]; - [alert addAction:action]; - } - - { - UIAlertAction *action = [UIAlertAction - actionWithTitle:NSLocalizedString(@"Cancel", nil) - style:UIAlertActionStyleCancel - handler:nil]; - [alert addAction:action]; - } - - if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { - alert.popoverPresentationController.sourceView = sender; - alert.popoverPresentationController.sourceRect = sender.bounds; - } - - [self presentViewController:alert - animated:YES - completion:nil]; -} - -- (void)contactsContentController:(DWContactsContentViewController *)controller - contactRequestsButtonAction:(UIButton *)sender { - DWRequestsModel *requestsModel = [self.model contactRequestsModel]; - DWRequestsViewController *requestsController = - [[DWRequestsViewController alloc] initWithModel:requestsModel - payModel:self.payModel - dataProvider:self.dataProvider]; - [self.navigationController pushViewController:requestsController animated:YES]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.h similarity index 75% rename from DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.h rename to DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.h index 4d902d250..4d7dfbdc7 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.h +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.h @@ -15,17 +15,21 @@ // limitations under the License. // -#import "DWSearchViewController.h" +#import "DWNavigationChildViewController.h" +#import "DWDashPayProtocol.h" +#import "DWDashPayReadyProtocol.h" #import "DWPayModelProtocol.h" #import "DWTransactionListDataProviderProtocol.h" NS_ASSUME_NONNULL_BEGIN -@interface DWUserSearchViewController : DWSearchViewController +@interface DWRootContactsViewController : DWNavigationChildViewController - (instancetype)initWithPayModel:(id)payModel - dataProvider:(id)dataProvider NS_DESIGNATED_INITIALIZER; + dataProvider:(id)dataProvider + dashPayModel:(id)dashPayModel + dashPayReady:(id)dashPayReady NS_DESIGNATED_INITIALIZER; - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; - (nullable instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.m new file mode 100644 index 000000000..c0ed8f383 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.m @@ -0,0 +1,122 @@ +// +// 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 "DWRootContactsViewController.h" + +#import "DWContactsPlaceholderViewController.h" +#import "DWContactsViewController.h" +#import "DWDashPayModel.h" +#import "DWGlobalOptions.h" +#import "DWUIKit.h" + +@interface DWRootContactsViewController () + +@property (readonly, nonatomic, strong) id payModel; +@property (readonly, nonatomic, strong) id dataProvider; +@property (readonly, nonatomic, strong) id dashPayModel; +@property (readonly, nonatomic, strong) id dashPayReady; +@property (nullable, nonatomic, weak) UIViewController *currentController; + +@end + +@implementation DWRootContactsViewController + +- (instancetype)initWithPayModel:(id)payModel + dataProvider:(id)dataProvider + dashPayModel:(id)dashPayModel + dashPayReady:(id)dashPayReady { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _payModel = payModel; + _dataProvider = dataProvider; + _dashPayModel = dashPayModel; + _dashPayReady = dashPayReady; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = NSLocalizedString(@"Contacts", nil); + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + // Model: + + [self update]; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(update) + name:DWDashPayRegistrationStatusUpdatedNotification + object:nil]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self updateIfNeeded]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (void)update { + if ([self contactsAvailable]) { + DWContactsViewController *contactsController = + [[DWContactsViewController alloc] initWithPayModel:self.payModel + dataProvider:self.dataProvider]; + [self dw_embedChild:contactsController]; + self.navigationItem.rightBarButtonItem = contactsController.navigationItem.rightBarButtonItem; + self.currentController = contactsController; + } + else { + DWContactsPlaceholderViewController *placeholderController = + [[DWContactsPlaceholderViewController alloc] initWithDashPayModel:self.dashPayModel + dashPayReady:self.dashPayReady]; + [self dw_embedChild:placeholderController]; + self.currentController = placeholderController; + } +} + +- (void)updateIfNeeded { + BOOL updateNeeded = NO; + BOOL contactsAvailable = [self contactsAvailable]; + if (self.currentController != nil) { + if (contactsAvailable) { + updateNeeded = ![self.currentController isKindOfClass:DWContactsViewController.class]; + } + else { + updateNeeded = ![self.currentController isKindOfClass:DWContactsPlaceholderViewController.class]; + } + } + else { + updateNeeded = YES; + } + + if (updateNeeded) { + [self update]; + } +} + +- (BOOL)contactsAvailable { + return self.dashPayModel.username != nil; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.h deleted file mode 100644 index 0c80ed122..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.h +++ /dev/null @@ -1,43 +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 - -@class DWSearchStateViewController; - -@protocol DWSearchStateViewControllerDelegate - -- (void)searchStateViewController:(DWSearchStateViewController *)controller buttonAction:(UIButton *)sender; - -@end - -@interface DWSearchStateViewController : UIViewController - -@property (nullable, nonatomic, weak) id delegate; - -- (void)setPlaceholderGlobalState; -- (void)setPlaceholderLocalState; -- (void)setSearchingStateWithQuery:(NSString *)query; -- (void)setNoResultsGlobalStateWithQuery:(NSString *)query; -- (void)setNoResultsLocalStateWithQuery:(NSString *)query; -- (void)setErrorStateWithError:(NSError *)error; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.m deleted file mode 100644 index 5067fe5c1..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.m +++ /dev/null @@ -1,388 +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 "DWSearchStateViewController.h" - -#import "DWActionButton.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSUInteger, DWUserSearchState) { - DWUserSearchState_PlaceholderGlobal, - DWUserSearchState_PlaceholderLocal, - DWUserSearchState_Searching, - DWUserSearchState_NoResultsGlobal, - DWUserSearchState_NoResultsLocal, - DWUserSearchState_Error, -}; - - -@interface DWSearchStateViewController () - -@property (null_resettable, nonatomic, strong) UIImageView *iconImageView; -@property (null_resettable, nonatomic, strong) UILabel *descriptionLabel; -@property (null_resettable, nonatomic, strong) UIButton *actionButton; - -@property (nonatomic, assign) DWUserSearchState state; -@property (nullable, copy, nonatomic) NSString *searchQuery; -@property (nullable, nonatomic, strong) NSError *error; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWSearchStateViewController - -- (void)setPlaceholderGlobalState { - self.searchQuery = nil; - self.error = nil; - self.state = DWUserSearchState_PlaceholderGlobal; - - [self reloadData]; -} - -- (void)setPlaceholderLocalState { - self.searchQuery = nil; - self.error = nil; - self.state = DWUserSearchState_PlaceholderLocal; - - [self reloadData]; -} - -- (void)setSearchingStateWithQuery:(NSString *)query { - self.searchQuery = query; - self.error = nil; - self.state = DWUserSearchState_Searching; - - [self reloadData]; -} - -- (void)setNoResultsGlobalStateWithQuery:(NSString *)query { - self.searchQuery = query; - self.error = nil; - self.state = DWUserSearchState_NoResultsGlobal; - - [self reloadData]; -} - -- (void)setNoResultsLocalStateWithQuery:(NSString *)query { - self.searchQuery = query; - self.error = nil; - self.state = DWUserSearchState_NoResultsLocal; - - [self reloadData]; -} - -- (void)setErrorStateWithError:(NSError *)error { - self.searchQuery = nil; - self.error = error; - self.state = DWUserSearchState_Error; - - [self reloadData]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - NSArray *views = @[ self.iconImageView, self.descriptionLabel, self.actionButton ]; - UIStackView *verticalStackView = [[UIStackView alloc] initWithArrangedSubviews:views]; - verticalStackView.translatesAutoresizingMaskIntoConstraints = NO; - verticalStackView.axis = UILayoutConstraintAxisVertical; - verticalStackView.alignment = UIStackViewAlignmentCenter; - verticalStackView.spacing = 24.0; - - UIStackView *horizontalStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ verticalStackView ]]; - horizontalStackView.translatesAutoresizingMaskIntoConstraints = NO; - horizontalStackView.axis = UILayoutConstraintAxisHorizontal; - horizontalStackView.alignment = UIStackViewAlignmentCenter; - [self.view addSubview:horizontalStackView]; - - UILayoutGuide *guide = self.view.layoutMarginsGuide; - - [NSLayoutConstraint activateConstraints:@[ - [horizontalStackView.topAnchor constraintEqualToAnchor:self.view.topAnchor], - [horizontalStackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], - [guide.trailingAnchor constraintEqualToAnchor:horizontalStackView.trailingAnchor], - [self.view.bottomAnchor constraintEqualToAnchor:horizontalStackView.bottomAnchor], - [self.actionButton.heightAnchor constraintEqualToConstant:44.0], - ]]; - - [self reloadData]; -} - -- (UIImageView *)iconImageView { - if (_iconImageView == nil) { - UIImageView *imageView = [[UIImageView alloc] init]; - imageView.translatesAutoresizingMaskIntoConstraints = NO; - imageView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - _iconImageView = imageView; - } - return _iconImageView; -} - -- (UILabel *)descriptionLabel { - if (_descriptionLabel == nil) { - UILabel *label = [[UILabel alloc] init]; - label.translatesAutoresizingMaskIntoConstraints = NO; - label.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - label.numberOfLines = 0; - label.lineBreakMode = NSLineBreakByWordWrapping; - label.textAlignment = NSTextAlignmentCenter; - label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; - label.adjustsFontForContentSizeCategory = YES; - label.textColor = [UIColor dw_darkTitleColor]; - [label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - _descriptionLabel = label; - } - return _descriptionLabel; -} - -- (UIButton *)actionButton { - if (_actionButton == nil) { - DWActionButton *button = [[DWActionButton alloc] initWithFrame:CGRectZero]; - button.translatesAutoresizingMaskIntoConstraints = NO; - button.small = YES; - button.inverted = YES; - button.usedOnDarkBackground = NO; - [button addTarget:self action:@selector(actionButtonAction:) forControlEvents:UIControlEventTouchUpInside]; - _actionButton = button; - } - return _actionButton; -} - -- (void)reloadData { - switch (self.state) { - case DWUserSearchState_PlaceholderGlobal: { - [self configurePlaceholderState]; - - break; - } - case DWUserSearchState_PlaceholderLocal: { - [self configurePlaceholderState]; - [self configureActionButtonForSearchUsers]; - - break; - } - case DWUserSearchState_Searching: { - [self configureSearchingAnimationState]; - - break; - } - case DWUserSearchState_NoResultsGlobal: { - [self configureNoResultsGlobalState]; - - break; - } - case DWUserSearchState_NoResultsLocal: { - [self configureNoResultsLocalState]; - [self configureActionButtonForSearchUsers]; - - break; - } - case DWUserSearchState_Error: { - [self configureSearchErrorState]; - - break; - } - } -} - -- (void)actionButtonAction:(UIButton *)sender { - [self.delegate searchStateViewController:self buttonAction:sender]; -} - -- (void)configurePlaceholderState { - self.actionButton.hidden = YES; - - [self.iconImageView stopAnimating]; - self.iconImageView.animationImages = nil; - self.iconImageView.image = [UIImage imageNamed:@"dp_user_search_placeholder"]; - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - [result beginEditing]; - - NSAttributedString *title = - [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Add a New Contact", nil) - attributes:@{NSFontAttributeName : self.boldFont}]; - [result appendAttributedString:title]; - - [result appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; - - NSAttributedString *subtitle = - [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Find a user on the Dash Network", nil) - attributes:@{NSFontAttributeName : self.regularFont}]; - [result appendAttributedString:subtitle]; - - [result endEditing]; - - self.descriptionLabel.attributedText = result; -} - -- (void)configureSearchingAnimationState { - NSParameterAssert(self.searchQuery); - - self.actionButton.hidden = YES; - - if (self.iconImageView.animationImages == nil) { - NSArray *frames = @[ - [UIImage imageNamed:@"dp_user_search_anim_1"], - [UIImage imageNamed:@"dp_user_search_anim_2"], - [UIImage imageNamed:@"dp_user_search_anim_3"], - [UIImage imageNamed:@"dp_user_search_anim_4"], - ]; - self.iconImageView.animationImages = frames; - self.iconImageView.animationDuration = 0.65; - self.iconImageView.animationRepeatCount = 0; - [self.iconImageView startAnimating]; - } - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - [result beginEditing]; - - NSString *format = NSLocalizedString(@"Searching for username %@ on the Dash Network", nil); - NSString *query = [NSString stringWithFormat:@"\"%@\"", self.searchQuery ?: @""]; - NSString *text = [NSString stringWithFormat:format, query]; - - NSAttributedString *attributed = - [[NSAttributedString alloc] initWithString:text - attributes:@{NSFontAttributeName : self.regularFont}]; - [result appendAttributedString:attributed]; - - NSRange queryRange = [text rangeOfString:query]; - if (queryRange.location != NSNotFound) { - [result removeAttribute:NSFontAttributeName range:queryRange]; - [result setAttributes:@{NSFontAttributeName : self.boldFont} range:queryRange]; - } - - [result endEditing]; - - self.descriptionLabel.attributedText = result; -} - -- (void)configureNoResultsGlobalState { - NSParameterAssert(self.searchQuery); - - self.actionButton.hidden = YES; - - [self.iconImageView stopAnimating]; - self.iconImageView.animationImages = nil; - self.iconImageView.image = [UIImage imageNamed:@"dp_user_search_warning"]; - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - [result beginEditing]; - - NSString *format = NSLocalizedString(@"There are no users that match with the name %@", nil); - NSString *query = [NSString stringWithFormat:@"\"%@\"", self.searchQuery ?: @""]; - NSString *text = [NSString stringWithFormat:format, query]; - - NSAttributedString *attributed = - [[NSAttributedString alloc] initWithString:text - attributes:@{NSFontAttributeName : self.regularFont}]; - [result appendAttributedString:attributed]; - - NSRange queryRange = [text rangeOfString:query]; - if (queryRange.location != NSNotFound) { - [result removeAttribute:NSFontAttributeName range:queryRange]; - [result setAttributes:@{NSFontAttributeName : self.boldFont} range:queryRange]; - } - - [result endEditing]; - - self.descriptionLabel.attributedText = result; -} - -- (void)configureNoResultsLocalState { - NSParameterAssert(self.searchQuery); - - self.actionButton.hidden = YES; - - [self.iconImageView stopAnimating]; - self.iconImageView.animationImages = nil; - self.iconImageView.image = [UIImage imageNamed:@"dp_user_search_warning"]; - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - [result beginEditing]; - - NSString *format = NSLocalizedString(@"There are no users that match with the name %@ in your contacts", nil); - NSString *query = [NSString stringWithFormat:@"\"%@\"", self.searchQuery ?: @""]; - NSString *text = [NSString stringWithFormat:format, query]; - - NSAttributedString *attributed = - [[NSAttributedString alloc] initWithString:text - attributes:@{NSFontAttributeName : self.regularFont}]; - [result appendAttributedString:attributed]; - - NSRange queryRange = [text rangeOfString:query]; - if (queryRange.location != NSNotFound) { - [result removeAttribute:NSFontAttributeName range:queryRange]; - [result setAttributes:@{NSFontAttributeName : self.boldFont} range:queryRange]; - } - - [result endEditing]; - - self.descriptionLabel.attributedText = result; -} - -- (void)configureSearchErrorState { - self.actionButton.hidden = YES; - - [self.iconImageView stopAnimating]; - self.iconImageView.animationImages = nil; - self.iconImageView.image = [UIImage imageNamed:@"dp_user_search_warning"]; - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - [result beginEditing]; - - NSAttributedString *title = - [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Error", nil) - attributes:@{NSFontAttributeName : self.boldFont}]; - [result appendAttributedString:title]; - - if (self.error) { - [result appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; - - NSAttributedString *subtitle = - [[NSAttributedString alloc] initWithString:self.error.localizedDescription - attributes:@{NSFontAttributeName : self.regularFont}]; - [result appendAttributedString:subtitle]; - } - - [result endEditing]; - - self.descriptionLabel.attributedText = result; -} - -- (void)configureActionButtonForSearchUsers { - self.actionButton.hidden = NO; - self.actionButton.imageEdgeInsets = UIEdgeInsetsMake(0.0, -8.0, 0.0, 0.0); - [self.actionButton setImage:[UIImage imageNamed:@"dp_search_add_contact"] forState:UIControlStateNormal]; - [self.actionButton setTitle:NSLocalizedString(@"Search for a User on the Dash Network", nil) - forState:UIControlStateNormal]; -} - -- (UIFont *)regularFont { - return [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; -} - -- (UIFont *)boldFont { - return [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.h deleted file mode 100644 index 6d68ed081..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.h +++ /dev/null @@ -1,50 +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 - -#import "DWDPBasicUserItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@protocol DWUserDetailsCellDelegate; -@class DWUserSearchResultViewController; - -@protocol DWUserSearchResultViewControllerDelegate - -- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller - willDisplayItemAtIndex:(NSInteger)index; -- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller - didSelectItemAtIndex:(NSInteger)index - cell:(UICollectionViewCell *)cell; - -- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller - acceptContactRequest:(id)item; -- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller - declineContactRequest:(id)item; - -@end - -@interface DWUserSearchResultViewController : UIViewController - -@property (nullable, nonatomic, copy) NSString *searchQuery; -@property (nullable, nonatomic, copy) NSArray> *items; -@property (nullable, nonatomic, weak) id delegate; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.m deleted file mode 100644 index ab1913036..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.m +++ /dev/null @@ -1,118 +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 "DWUserSearchResultViewController.h" - -#import "DWUIKit.h" - -#import "DWDPBasicCell.h" -#import "DWDPNewIncomingRequestItem.h" -#import "DWListCollectionLayout.h" -#import "UICollectionView+DWDPItemDequeue.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUserSearchResultViewController () - -@property (null_resettable, nonatomic, strong) UICollectionView *collectionView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserSearchResultViewController - -- (void)setItems:(NSArray> *)items { - _items = [items copy]; - - [self.collectionView reloadData]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - [self.view addSubview:self.collectionView]; -} - -#pragma mark - UICollectionViewDataSource - -- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - return self.items.count; -} - -- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; - NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); - const CGFloat contentWidth = layout.contentWidth; - - id item = self.items[indexPath.row]; - - DWDPBasicCell *cell = [collectionView dw_dequeueReusableCellForItem:item atIndexPath:indexPath]; - cell.contentWidth = contentWidth; - cell.displayItemBackgroundView = YES; - cell.delegate = self; - [cell setItem:item highlightedText:self.searchQuery]; - - return cell; -} - -#pragma mark - UICollectionViewDelegate - -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { - [self.delegate userSearchResultViewController:self willDisplayItemAtIndex:indexPath.row]; -} - -- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - [self.collectionView deselectItemAtIndexPath:indexPath animated:YES]; - - UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; - [self.delegate userSearchResultViewController:self didSelectItemAtIndex:indexPath.row cell:cell]; -} - -#pragma mark - DWDPNewIncomingRequestItemDelegate - -- (void)acceptIncomingRequest:(id)item { - [self.delegate userSearchResultViewController:self acceptContactRequest:item]; -} - -- (void)declineIncomingRequest:(id)item { - [self.delegate userSearchResultViewController:self declineContactRequest:item]; -} - -#pragma mark - Private - -- (UICollectionView *)collectionView { - if (_collectionView == nil) { - DWListCollectionLayout *layout = [[DWListCollectionLayout alloc] init]; - - UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds - collectionViewLayout:layout]; - collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - collectionView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - collectionView.delegate = self; - collectionView.dataSource = self; - collectionView.alwaysBounceVertical = YES; - [collectionView dw_registerDPItemCells]; - - _collectionView = collectionView; - } - return _collectionView; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.m deleted file mode 100644 index 49cbe41af..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.m +++ /dev/null @@ -1,187 +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 "DWUserSearchViewController.h" - -#import "DWDashPayConstants.h" -#import "DWSearchStateViewController.h" -#import "DWUIKit.h" -#import "DWUserProfileViewController.h" -#import "DWUserSearchModel.h" -#import "DWUserSearchResultViewController.h" -#import "UIView+DWFindConstraints.h" -#import "UIViewController+DWEmbedding.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUserSearchViewController () - -@property (readonly, nonatomic, strong) id payModel; -@property (readonly, nonatomic, strong) id dataProvider; - -@property (null_resettable, nonatomic, strong) DWUserSearchModel *model; - -@property (null_resettable, nonatomic, strong) DWSearchStateViewController *stateController; -@property (null_resettable, nonatomic, strong) DWUserSearchResultViewController *resultsController; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserSearchViewController - -- (instancetype)initWithPayModel:(id)payModel - dataProvider:(id)dataProvider { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _payModel = payModel; - _dataProvider = dataProvider; - } - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.title = NSLocalizedString(@"Add a New Contact", nil); - - self.searchBar.placeholder = NSLocalizedString(@"Search for a username", nil); - - [self.stateController setPlaceholderGlobalState]; - [self dw_embedChild:self.stateController inContainer:self.contentView]; -} - -#pragma mark - UISearchBarDelegate - -- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { - [self.model searchWithQuery:self.searchBar.text]; - - if (self.model.trimmedQuery.length == 0) { - [self.stateController setPlaceholderGlobalState]; - } - - [self.resultsController dw_detachFromParent]; -} - -- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { - NSString *resultText = [searchBar.text stringByReplacingCharactersInRange:range withString:text]; - return resultText.length <= DW_MAX_USERNAME_LENGTH; -} - -#pragma mark - DWUserSearchModelDelegate - -- (void)userSearchModelDidStartSearch:(DWUserSearchModel *)model { - if (self.model.trimmedQuery.length == 0) { - [self.stateController setPlaceholderGlobalState]; - } - else { - [self.stateController setSearchingStateWithQuery:self.model.trimmedQuery]; - } -} - -- (void)userSearchModel:(DWUserSearchModel *)model completedWithItems:(NSArray> *)items; -{ - if (items.count > 0) { - self.resultsController.searchQuery = model.trimmedQuery; - self.resultsController.items = items; - [self dw_embedChild:self.resultsController inContainer:self.contentView]; - } - else { - [self.resultsController dw_detachFromParent]; - [self.stateController setNoResultsGlobalStateWithQuery:self.model.trimmedQuery]; - } -} - -- (void)userSearchModel:(DWUserSearchModel *)model completedWithError:(NSError *)error { - [self.resultsController dw_detachFromParent]; - [self.stateController setErrorStateWithError:error]; -} - -#pragma mark - DWUserSearchResultViewControllerDelegate - -- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller - willDisplayItemAtIndex:(NSInteger)index { - [self.model willDisplayItemAtIndex:index]; -} - -- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller - didSelectItemAtIndex:(NSInteger)index - cell:(UICollectionViewCell *)cell { - id item = [self.model itemAtIndex:index]; - if (!item) { - return; - } - - if (![self.model canOpenBlockchainIdentity:item.blockchainIdentity]) { - [cell dw_shakeView]; - return; - } - - DWUserProfileViewController *profileController = - [[DWUserProfileViewController alloc] initWithItem:item - payModel:self.payModel - dataProvider:self.dataProvider]; - [self.navigationController pushViewController:profileController animated:YES]; -} - -- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller - acceptContactRequest:(id)item { - [self.model acceptContactRequest:item]; -} - -- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller - declineContactRequest:(id)item { - [self.model declineContactRequest:item]; -} - -#pragma mark - Keyboard - -- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height - animationDuration:(NSTimeInterval)animationDuration - animationCurve:(UIViewAnimationCurve)animationCurve { - NSLayoutConstraint *constraint = [self.stateController.view dw_findConstraintWithAttribute:NSLayoutAttributeBottom]; - constraint.constant = height; - [self.view layoutIfNeeded]; -} - -#pragma mark - Private - -- (DWUserSearchModel *)model { - if (_model == nil) { - DWUserSearchModel *model = [[DWUserSearchModel alloc] init]; - model.delegate = self; - _model = model; - } - return _model; -} - -- (DWSearchStateViewController *)stateController { - if (_stateController == nil) { - _stateController = [[DWSearchStateViewController alloc] init]; - } - return _stateController; -} - -- (DWUserSearchResultViewController *)resultsController { - if (_resultsController == nil) { - _resultsController = [[DWUserSearchResultViewController alloc] init]; - _resultsController.delegate = self; - } - return _resultsController; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.h b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.h deleted file mode 100644 index e5d2e36c0..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.h +++ /dev/null @@ -1,52 +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 - -#import "DWDPBasicUserItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWUserSearchModel; -@class DSBlockchainIdentity; - -@protocol DWUserSearchModelDelegate - -- (void)userSearchModelDidStartSearch:(DWUserSearchModel *)model; -- (void)userSearchModel:(DWUserSearchModel *)model completedWithItems:(NSArray> *)items; -- (void)userSearchModel:(DWUserSearchModel *)model completedWithError:(NSError *)error; - -@end - -@interface DWUserSearchModel : NSObject - -@property (readonly, nonatomic, copy) NSString *trimmedQuery; -@property (nullable, nonatomic, weak) id delegate; - -- (void)searchWithQuery:(NSString *)searchQuery; -- (void)willDisplayItemAtIndex:(NSInteger)index; - -- (id)itemAtIndex:(NSInteger)index; - -- (BOOL)canOpenBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; - -- (void)acceptContactRequest:(id)item; -- (void)declineContactRequest:(id)item; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.m b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.m deleted file mode 100644 index bc47d1938..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.m +++ /dev/null @@ -1,202 +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 "DWUserSearchModel.h" - -#import "DWDPSearchItemsFactory.h" -#import "DWDashPayConstants.h" -#import "DWDashPayContactsActions.h" -#import "DWEnvironment.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUserSearchRequest : NSObject - -@property (readonly, nonatomic, copy) NSString *trimmedQuery; -@property (nonatomic, assign) uint32_t offset; -@property (nullable, nonatomic, copy) NSArray> *items; -@property (nonatomic, assign) BOOL requestInProgress; -@property (nonatomic, assign) BOOL hasNextPage; -@property (nullable, nonatomic, copy) NSData *lastItem; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserSearchRequest - -- (instancetype)initWithTrimmedQuery:(NSString *)trimmedQuery { - self = [super init]; - if (self) { - _trimmedQuery = [trimmedQuery copy]; - _offset = 0; - } - return self; -} - -@end - -#pragma mark - Model - -NS_ASSUME_NONNULL_BEGIN - -static uint32_t const LIMIT = 100; -static NSTimeInterval SEARCH_DEBOUNCE_DELAY = 0.4; - -@interface DWUserSearchModel () - -@property (nullable, nonatomic, strong) DWUserSearchRequest *searchRequest; -@property (nullable, nonatomic, strong) id request; -@property (readonly, nonatomic, strong) DWDPSearchItemsFactory *itemsFactory; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserSearchModel - -- (instancetype)init { - self = [super init]; - if (self) { - _itemsFactory = [[DWDPSearchItemsFactory alloc] init]; - } - return self; -} - -- (NSString *)trimmedQuery { - return self.searchRequest.trimmedQuery ?: @""; -} - -- (void)searchWithQuery:(NSString *)searchQuery { - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(performInitialSearch) object:nil]; - - [self.request cancel]; - self.request = nil; - - self.searchRequest = nil; - - NSCharacterSet *whitespaces = [NSCharacterSet whitespaceCharacterSet]; - NSString *trimmedQuery = [searchQuery stringByTrimmingCharactersInSet:whitespaces] ?: @""; - if ([self.searchRequest.trimmedQuery isEqualToString:trimmedQuery]) { - return; - } - if (trimmedQuery.length < DW_MIN_USERNAME_LENGTH) { - return; - } - - self.searchRequest = [[DWUserSearchRequest alloc] initWithTrimmedQuery:trimmedQuery]; - - [self performSelector:@selector(performInitialSearch) withObject:nil afterDelay:SEARCH_DEBOUNCE_DELAY]; -} - -- (void)willDisplayItemAtIndex:(NSInteger)index { - const BOOL shouldRequestNextPage = self.searchRequest.items.count >= LIMIT && index >= self.searchRequest.items.count - LIMIT / 4; - if (shouldRequestNextPage && self.searchRequest.hasNextPage && !self.searchRequest.requestInProgress) { - self.searchRequest.offset += LIMIT; - [self performSearchAndNotify:NO]; - } -} - -- (id)itemAtIndex:(NSInteger)index { - if (index < 0 || self.searchRequest.items.count < index) { - NSAssert(NO, @"No blockchain identity for invalid index %ld", index); - return nil; - } - - return self.searchRequest.items[index]; -} - -- (BOOL)canOpenBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - return !uint256_eq(myBlockchainIdentity.uniqueID, blockchainIdentity.uniqueID); -} - -- (void)acceptContactRequest:(id)item { - __weak typeof(self) weakSelf = self; - [DWDashPayContactsActions - acceptContactRequest:item - completion:^(BOOL success, NSArray *_Nonnull errors) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - // TODO: DP update state more gently - [strongSelf performSearchAndNotify:YES]; - }]; -} - -- (void)declineContactRequest:(id)item { - [DWDashPayContactsActions declineContactRequest:item completion:nil]; -} - -#pragma mark Private - -- (void)performInitialSearch { - [self performSearchAndNotify:YES]; -} - -- (void)performSearchAndNotify:(BOOL)notify { - if (notify) { - [self.delegate userSearchModelDidStartSearch:self]; - } - - if (self.searchRequest) { - [self performSearchWithQuery:self.searchRequest.trimmedQuery offset:self.searchRequest.offset]; - } -} - -- (void)performSearchWithQuery:(NSString *)query offset:(uint32_t)offset { - self.searchRequest.requestInProgress = YES; - - DSIdentitiesManager *manager = [DWEnvironment sharedInstance].currentChainManager.identitiesManager; - __weak typeof(self) weakSelf = self; - self.request = [manager searchIdentitiesByNamePrefix:query - inDomain:@"dash" - startAfter:self.searchRequest.lastItem - limit:LIMIT - withCompletion:^(BOOL success, NSArray *_Nullable blockchainIdentities, NSArray *_Nonnull errors) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - // search query was changed before results arrive, ignore results - if (!strongSelf.searchRequest || ![strongSelf.searchRequest.trimmedQuery isEqualToString:query]) { - return; - } - strongSelf.searchRequest.requestInProgress = NO; - if (success) { - NSMutableArray> *items = strongSelf.searchRequest.items ? [strongSelf.searchRequest.items mutableCopy] : [NSMutableArray array]; - for (DSBlockchainIdentity *blockchainIdentity in blockchainIdentities) { - id item = [strongSelf.itemsFactory itemForBlockchainIdentity:blockchainIdentity]; - [items addObject:item]; - } - strongSelf.searchRequest.hasNextPage = blockchainIdentities.count >= LIMIT; - strongSelf.searchRequest.items = items; - strongSelf.searchRequest.lastItem = [[[items lastObject] blockchainIdentity] uniqueIDData]; - [strongSelf.delegate userSearchModel:strongSelf completedWithItems:items]; - } - else { - strongSelf.searchRequest.hasNextPage = NO; - [strongSelf.delegate userSearchModel:strongSelf completedWithError:errors.firstObject]; - } - }]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel+DWProtected.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel+DWProtected.h deleted file mode 100644 index 9e4974fce..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel+DWProtected.h +++ /dev/null @@ -1,45 +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 - -#import "DWBaseContactsModel.h" - -#import "DWFetchedResultsDataSource.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWDPContactsItemsFactory; - -@interface DWBaseContactsModel () - -@property (readonly, nonatomic, strong) DWFetchedResultsDataSource *requestsDataSource; -@property (readonly, nonatomic, strong) DWFetchedResultsDataSource *contactsDataSource; - -@property (nullable, nonatomic, strong) id allDataSource; -@property (nullable, nonatomic, strong) id searchDataSource; - -@property (readonly, nonatomic, strong) DWDPContactsItemsFactory *itemsFactory; -@property (nullable, nonatomic, copy) NSString *trimmedQuery; - -- (void)rebuildFRCDataSources; - -- (BOOL)shouldFetchData; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.h deleted file mode 100644 index c88df22b0..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.h +++ /dev/null @@ -1,52 +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 -#import - -#import "DWContactsDataSource.h" -#import "DWContactsSortModeProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWBaseContactsModel; - -@protocol DWContactsModelDelegate - -- (void)contactsModelDidUpdate:(DWBaseContactsModel *)model; - -@end - -@interface DWBaseContactsModel : NSObject - -@property (readonly, nonatomic, assign) BOOL hasBlockchainIdentity; -@property (readonly, nonatomic, strong) id dataSource; -@property (nullable, nonatomic, weak) id delegate; - -@property (nonatomic, assign) DWContactsSortMode sortMode; - -- (void)start; -- (void)stop; - -- (void)acceptContactRequest:(id)item; -- (void)declineContactRequest:(id)item; - -- (void)searchWithQuery:(NSString *)searchQuery; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.m deleted file mode 100644 index deb861866..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.m +++ /dev/null @@ -1,147 +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 "DWBaseContactsModel+DWProtected.h" - -#import "DWContactsDataSourceObject.h" -#import "DWContactsSearchDataSourceObject.h" -#import "DWDPContactsItemsFactory.h" -#import "DWDashPayContactsActions.h" -#import "DWDashPayContactsUpdater.h" -#import "DWEnvironment.h" - -@implementation DWBaseContactsModel - -@synthesize sortMode; - -- (instancetype)init { - self = [super init]; - if (self) { - _itemsFactory = [[DWDPContactsItemsFactory alloc] init]; - } - return self; -} - -- (BOOL)hasBlockchainIdentity { - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - return myBlockchainIdentity != nil; -} - -- (id)dataSource { - return [self isSearching] ? self.searchDataSource : self.allDataSource; -} - -- (void)rebuildFRCDataSources { - // to be overriden - // create FRC-backed datasources here -} - -- (BOOL)shouldFetchData { - return YES; -} - -- (void)start { - if ([self shouldFetchData]) { - [[DWDashPayContactsUpdater sharedInstance] fetch]; - } - - if (!self.requestsDataSource) { - [self rebuildFRCDataSources]; - } - - [self.requestsDataSource start]; - [self.contactsDataSource start]; - - [self updateForced:YES]; -} - -- (void)stop { - [self.requestsDataSource stop]; - [self.contactsDataSource stop]; -} - -- (void)acceptContactRequest:(id)item { - [DWDashPayContactsActions acceptContactRequest:item completion:nil]; -} - -- (void)declineContactRequest:(id)item { - [DWDashPayContactsActions declineContactRequest:item completion:nil]; -} - -- (void)searchWithQuery:(NSString *)searchQuery { - NSCharacterSet *whitespaces = [NSCharacterSet whitespaceCharacterSet]; - NSString *trimmedQuery = [searchQuery stringByTrimmingCharactersInSet:whitespaces] ?: @""; - if ([self.trimmedQuery isEqualToString:trimmedQuery]) { - return; - } - - self.trimmedQuery = trimmedQuery; - - [self updateForced:NO]; -} - -#pragma mark - DWFetchedResultsDataSourceDelegate - -- (void)fetchedResultsDataSourceDidUpdate:(DWFetchedResultsDataSource *)fetchedResultsDataSource { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - [self updateForced:YES]; -} - -#pragma mark - NSFetchedResultsControllerDelegate - -- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - [self updateForced:YES]; -} - -#pragma mark - Private - -- (void)updateForced:(BOOL)forced { - NSFetchedResultsController *requestsFRC = self.requestsDataSource.fetchedResultsController; - requestsFRC.delegate = self; - NSFetchedResultsController *contactsFRC = self.contactsDataSource.fetchedResultsController; - contactsFRC.delegate = self; - - if (forced) { - self.allDataSource = [[DWContactsDataSourceObject alloc] initWithRequestsFRC:requestsFRC - contactsFRC:contactsFRC - itemsFactory:self.itemsFactory - sortMode:self.sortMode]; - } - - if (self.isSearching) { - self.searchDataSource = [[DWContactsSearchDataSourceObject alloc] initWithContactRequestsFRC:requestsFRC - contactsFRC:contactsFRC - itemsFactory:self.itemsFactory - trimmedQuery:self.trimmedQuery]; - } - else { - self.searchDataSource = nil; - } - - NSParameterAssert(self.delegate); - [self.delegate contactsModelDidUpdate:self]; -} - -- (BOOL)isSearching { - return self.trimmedQuery.length > 0; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.m deleted file mode 100644 index b3dde1111..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.m +++ /dev/null @@ -1,61 +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 "DWContactsModel.h" - -#import "DWBaseContactsModel+DWProtected.h" -#import "DWContactsDataSourceObject.h" -#import "DWContactsFetchedDataSource.h" -#import "DWEnvironment.h" -#import "DWIncomingFetchedDataSource.h" -#import "DWRequestsModel.h" - -@implementation DWContactsModel - -@synthesize requestsDataSource = _requestsDataSource; -@synthesize contactsDataSource = _contactsDataSource; - -- (instancetype)init { - self = [super init]; - if (self) { - [self rebuildFRCDataSources]; - } - return self; -} - -- (DWRequestsModel *)contactRequestsModel { - return [[DWRequestsModel alloc] initWithRequestsDataSource:self.requestsDataSource]; -} - -- (void)rebuildFRCDataSources { - DSBlockchainIdentity *blockchainIdentity = [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; - if (!blockchainIdentity) { - return; - } - - NSManagedObjectContext *context = [NSManagedObjectContext viewContext]; - - _requestsDataSource = [[DWIncomingFetchedDataSource alloc] initWithBlockchainIdentity:blockchainIdentity inContext:context]; - _requestsDataSource.shouldSubscribeToNotifications = YES; - _requestsDataSource.delegate = self; - - _contactsDataSource = [[DWContactsFetchedDataSource alloc] initWithBlockchainIdentity:blockchainIdentity inContext:context]; - _contactsDataSource.shouldSubscribeToNotifications = YES; - _contactsDataSource.delegate = self; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsSortModeProtocol.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsSortModeProtocol.h deleted file mode 100644 index 8f0d6a1e0..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsSortModeProtocol.h +++ /dev/null @@ -1,32 +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 - -typedef NS_ENUM(NSUInteger, DWContactsSortMode) { - DWContactsSortMode_ByUsername, -}; - -@protocol DWContactsSortModeProtocol - -@property (readonly, nonatomic, assign) DWContactsSortMode sortMode; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSource.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSource.h deleted file mode 100644 index 2ad16b271..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSource.h +++ /dev/null @@ -1,41 +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 - -#import "DWContactsSortModeProtocol.h" -#import "DWDPBasicUserItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@protocol DWContactsDataSource - -@property (readonly, nonatomic, assign, getter=isEmpty) BOOL empty; - -@property (readonly, nonatomic, assign, getter=isSearching) BOOL searching; -@property (readonly, nullable, nonatomic, copy) NSString *trimmedQuery; - -/// First section -@property (readonly, nonatomic, assign) NSUInteger requestsCount; -/// Second section -@property (readonly, nonatomic, assign) NSUInteger contactsCount; - -- (id)itemAtIndexPath:(NSIndexPath *)indexPath; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.h deleted file mode 100644 index c5fd9beb2..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.h +++ /dev/null @@ -1,37 +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 "DWContactsDataSource.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWDPContactsItemsFactory; -@class NSFetchedResultsController; - -@interface DWContactsDataSourceObject : NSObject - -- (instancetype)initWithRequestsFRC:(nullable NSFetchedResultsController *)contactRequestsFRC - contactsFRC:(nullable NSFetchedResultsController *)contactsFRC - itemsFactory:(DWDPContactsItemsFactory *)itemsFactory - sortMode:(DWContactsSortMode)sortMode; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.m deleted file mode 100644 index ab2ec215c..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.m +++ /dev/null @@ -1,91 +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 "DWContactsDataSourceObject.h" - -#import "DWDPContactsItemsFactory.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWContactsDataSourceObject () - -@property (nullable, readonly, nonatomic, strong) NSFetchedResultsController *requestsFRC; -@property (nullable, readonly, nonatomic, strong) NSFetchedResultsController *contactsFRC; -@property (readonly, nonatomic, strong) DWDPContactsItemsFactory *itemsFactory; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWContactsDataSourceObject - -@synthesize sortMode = _sortMode; - -- (instancetype)initWithRequestsFRC:(NSFetchedResultsController *)requestsFRC - contactsFRC:(NSFetchedResultsController *)contactsFRC - itemsFactory:(DWDPContactsItemsFactory *)itemsFactory - sortMode:(DWContactsSortMode)sortMode { - self = [super init]; - if (self) { - _requestsFRC = requestsFRC; - _contactsFRC = contactsFRC; - _itemsFactory = itemsFactory; - _sortMode = sortMode; - } - return self; -} - -- (BOOL)isEmpty { - if (self.requestsFRC == nil && self.contactsFRC == nil) { - return YES; - } - - const NSInteger count = self.requestsCount + self.contactsCount; - return count == 0; -} - -- (BOOL)isSearching { - return NO; -} - -- (NSString *)trimmedQuery { - return nil; -} - -- (NSUInteger)requestsCount { - return self.requestsFRC.sections.firstObject.numberOfObjects; -} - -- (NSUInteger)contactsCount { - return self.contactsFRC.sections.firstObject.numberOfObjects; -} - -- (id)itemAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 0) { - NSManagedObject *entity = [self.requestsFRC objectAtIndexPath:indexPath]; - id item = [self.itemsFactory itemForEntity:entity]; - return item; - } - else { - NSIndexPath *transformedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:0]; - NSManagedObject *entity = [self.contactsFRC objectAtIndexPath:transformedIndexPath]; - id item = [self.itemsFactory itemForEntity:entity]; - return item; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.h deleted file mode 100644 index 84a1903ae..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.h +++ /dev/null @@ -1,39 +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 - -#import "DWContactsDataSource.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWDPContactsItemsFactory; -@class NSFetchedResultsController; - -@interface DWContactsSearchDataSourceObject : NSObject - -- (instancetype)initWithContactRequestsFRC:(NSFetchedResultsController *)contactRequestsFRC - contactsFRC:(NSFetchedResultsController *)contactsFRC - itemsFactory:(DWDPContactsItemsFactory *)itemsFactory - trimmedQuery:(NSString *)trimmedQuery; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.m deleted file mode 100644 index e0d6ff756..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.m +++ /dev/null @@ -1,105 +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 "DWContactsSearchDataSourceObject.h" - -#import "DWContactsDataSource.h" -#import "DWDPContactsItemsFactory.h" -#import "NSPredicate+DWFullTextSearch.h" - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface DWContactsSearchDataSourceObject () - -@property (nullable, nonatomic, copy) NSArray> *filteredContactRequest; -@property (nullable, nonatomic, copy) NSArray> *filteredContacts; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWContactsSearchDataSourceObject - -@synthesize trimmedQuery = _trimmedQuery; - -- (instancetype)initWithContactRequestsFRC:(NSFetchedResultsController *)contactRequestsFRC - contactsFRC:(NSFetchedResultsController *)contactsFRC - itemsFactory:(DWDPContactsItemsFactory *)itemsFactory - trimmedQuery:(NSString *)trimmedQuery { - self = [super init]; - if (self) { - NSArray> *contactRequests = [self.class itemsWithFactory:itemsFactory frc:contactRequestsFRC]; - NSArray> *contacts = [self.class itemsWithFactory:itemsFactory frc:contactsFRC]; - _filteredContactRequest = [self.class filterItems:contactRequests trimmedQuery:trimmedQuery]; - _filteredContacts = [self.class filterItems:contacts trimmedQuery:trimmedQuery]; - _trimmedQuery = [trimmedQuery copy]; - } - return self; -} - -- (BOOL)isEmpty { - const NSInteger count = self.requestsCount + self.contactsCount; - return count == 0; -} - -- (BOOL)isSearching { - return YES; -} - -- (DWContactsSortMode)sortMode { - return DWContactsSortMode_ByUsername; -} - -- (NSUInteger)requestsCount { - return self.filteredContactRequest.count; -} - -- (NSUInteger)contactsCount { - return self.filteredContacts.count; -} - -- (id)itemAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 0) { - return self.filteredContactRequest[indexPath.row]; - } - else { - return self.filteredContacts[indexPath.row]; - } -} - -#pragma mark - Private - -+ (NSArray> *)filterItems:(NSArray> *)items trimmedQuery:(NSString *)trimmedQuery { - id item = nil; - NSArray *searchKeyPaths = @[ DW_KEYPATH(item, username), DW_KEYPATH(item, displayName) ]; - NSPredicate *predicate = [NSPredicate dw_searchPredicateForTrimmedQuery:trimmedQuery - searchKeyPaths:searchKeyPaths]; - return [items filteredArrayUsingPredicate:predicate]; -} - -+ (NSArray> *)itemsWithFactory:(DWDPContactsItemsFactory *)factory frc:(NSFetchedResultsController *)frc { - NSMutableArray> *items = [NSMutableArray array]; - for (NSManagedObject *entity in frc.fetchedObjects) { - id item = [factory itemForEntity:entity]; - [items addObject:item]; - } - return [items copy]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.m deleted file mode 100644 index 06c579d56..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.m +++ /dev/null @@ -1,49 +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 "DWContactsFetchedDataSource.h" - -#import "DWEnvironment.h" - -@implementation DWContactsFetchedDataSource - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - inContext:(NSManagedObjectContext *)context { - self = [super initWithContext:context]; - if (self) { - _blockchainIdentity = blockchainIdentity; - } - return self; -} - -- (NSString *)entityName { - return NSStringFromClass(DSDashpayUserEntity.class); -} - -- (NSPredicate *)predicate { - return [NSPredicate - predicateWithFormat: - @"ANY friends == %@", - [self.blockchainIdentity matchingDashpayUserInContext:self.context]]; -} - -- (NSArray *)sortDescriptors { - NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"associatedBlockchainIdentity.dashpayUsername.stringValue" ascending:YES]; - return @[ sortDescriptor ]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.h deleted file mode 100644 index 9317192f0..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.h +++ /dev/null @@ -1,55 +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 -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DWFetchedResultsDataSource; - -@protocol DWFetchedResultsDataSourceDelegate - -- (void)fetchedResultsDataSourceDidUpdate:(DWFetchedResultsDataSource *)fetchedResultsDataSource; - -@end - -@interface DWFetchedResultsDataSource : NSObject - -@property (nonatomic, assign) BOOL shouldSubscribeToNotifications; - -@property (readonly, nonatomic, strong) NSManagedObjectContext *context; -@property (readonly, nonatomic, copy) NSString *entityName; -@property (readonly, nonatomic, strong) NSPredicate *predicate; -@property (nullable, readonly, nonatomic, strong) NSPredicate *invertedPredicate; -@property (nullable, readonly, nonatomic, copy) NSArray *sortDescriptors; - -@property (null_resettable, nonatomic, strong) NSFetchedResultsController *fetchedResultsController; - -@property (nullable, nonatomic, strong) id delegate; - -- (void)start; -- (void)stop; - -- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.m deleted file mode 100644 index 3749e557c..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.m +++ /dev/null @@ -1,194 +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 "DWFetchedResultsDataSource.h" - -#import -#import - -static NSUInteger const FETCH_BATCH_SIZE = 20; - -NS_ASSUME_NONNULL_BEGIN - -@interface DWFetchedResultsDataSource () - -@property (nonatomic, assign) BOOL subscribedToNotifications; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWFetchedResultsDataSource - -- (instancetype)initWithContext:(NSManagedObjectContext *)context { - self = [super init]; - if (self) { - _context = context; - } - return self; -} - -- (NSString *)entityName { - NSAssert(NO, @"Must be overriden"); - return nil; -} - -- (NSPredicate *)predicate { - NSAssert(NO, @"Must be overriden"); - return [NSPredicate predicateWithValue:YES]; -} - -- (NSPredicate *)invertedPredicate { - return nil; -} - -- (NSArray *)sortDescriptors { - return nil; -} - -- (void)start { - NSParameterAssert(self.predicate); - NSParameterAssert(self.sortDescriptors); - // invertedPredicate is not mandatory - - if (self.shouldSubscribeToNotifications && !self.subscribedToNotifications) { - self.subscribedToNotifications = YES; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(backgroundManagedObjectContextDidSaveNotification:) - name:NSManagedObjectContextDidSaveNotification - object:self.context]; - } - - [self fetchedResultsController]; -} - -- (void)stop { - self.fetchedResultsController = nil; - - if (self.shouldSubscribeToNotifications && self.subscribedToNotifications) { - self.subscribedToNotifications = NO; - - [[NSNotificationCenter defaultCenter] removeObserver:self - name:NSManagedObjectContextDidSaveNotification - object:self.context]; - } -} - -- (NSFetchedResultsController *)fetchedResultsController { - if (_fetchedResultsController != nil) { - return _fetchedResultsController; - } - - DDLogVerbose(@"DWDP: Constructing FRC for %@", self.entityName); - - NSManagedObjectContext *context = self.context; - - NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; - fetchRequest.entity = [NSEntityDescription entityForName:self.entityName inManagedObjectContext:context]; - fetchRequest.fetchBatchSize = FETCH_BATCH_SIZE; - fetchRequest.sortDescriptors = self.sortDescriptors; - fetchRequest.predicate = self.predicate; - - NSFetchedResultsController *fetchedResultsController = - [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest - managedObjectContext:context - sectionNameKeyPath:nil - cacheName:nil]; - _fetchedResultsController = fetchedResultsController; - NSError *error = nil; - if (![fetchedResultsController performFetch:&error]) { - // Replace this implementation with code to handle the error appropriately. - // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - DSLog(@"Unresolved error %@, %@", error, [error userInfo]); - abort(); - } - - return _fetchedResultsController; -} - -#pragma mark - Private - -- (NSPredicate *)classPredicate { - return [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", NSClassFromString(self.entityName)]; -} - -- (NSPredicate *)predicateInContext { - return [self.predicate predicateInContext:self.context]; -} - -- (NSPredicate *)invertedPredicateInContext { - return [self.invertedPredicate predicateInContext:self.context]; -} - -- (NSPredicate *)fullPredicateInContext { - return [NSCompoundPredicate andPredicateWithSubpredicates:@[ [self classPredicate], [self predicateInContext] ]]; -} - -- (NSPredicate *)fullInvertedPredicateInContext { - return [NSCompoundPredicate andPredicateWithSubpredicates:@[ [self classPredicate], [self invertedPredicateInContext] ]]; -} - -- (void)backgroundManagedObjectContextDidSaveNotification:(NSNotification *)notification { - BOOL (^objectsHaveChanged)(NSSet *) = ^BOOL(NSSet *objects) { - NSSet *foundObjects = [objects filteredSetUsingPredicate:[self fullPredicateInContext]]; - if (foundObjects.count) { - return YES; - } - return NO; - }; - - BOOL (^objectsHaveChangedInverted)(NSSet *) = ^BOOL(NSSet *objects) { - if (!self.invertedPredicate) { - return NO; - } - NSSet *foundObjects = [objects filteredSetUsingPredicate:[self fullInvertedPredicateInContext]]; - if (foundObjects.count) { - return YES; - } - return NO; - }; - - - NSSet *insertedObjects = notification.userInfo[NSInsertedObjectsKey]; - NSSet *updatedObjects = notification.userInfo[NSUpdatedObjectsKey]; - NSSet *deletedObjects = notification.userInfo[NSDeletedObjectsKey]; - BOOL inserted = NO; - BOOL updated = NO; - BOOL deleted = NO; - BOOL insertedInverted = NO; - BOOL deletedInverted = NO; - if ((inserted = objectsHaveChanged(insertedObjects)) || - (updated = objectsHaveChanged(updatedObjects)) || - (deleted = objectsHaveChanged(deletedObjects)) || - (insertedInverted = objectsHaveChangedInverted(insertedObjects)) || - (deletedInverted = objectsHaveChangedInverted(deletedObjects))) { - if (inserted || updated || deleted) { - insertedInverted = objectsHaveChangedInverted(insertedObjects); - deletedInverted = objectsHaveChangedInverted(deletedObjects); - } - [self.context mergeChangesFromContextDidSaveNotification:notification]; - if (insertedInverted || deletedInverted) { - self.fetchedResultsController = nil; - dispatch_async(dispatch_get_main_queue(), ^{ - [self.delegate fetchedResultsDataSourceDidUpdate:self]; - }); - } - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.m deleted file mode 100644 index 5f2002c39..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.m +++ /dev/null @@ -1,58 +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 "DWIncomingFetchedDataSource.h" - -#import "DWEnvironment.h" - -@implementation DWIncomingFetchedDataSource - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - inContext:(NSManagedObjectContext *)context { - self = [super initWithContext:context]; - if (self) { - _blockchainIdentity = blockchainIdentity; - } - return self; -} - -- (NSString *)entityName { - return NSStringFromClass(DSFriendRequestEntity.class); -} - -- (NSPredicate *)predicate { - return [NSPredicate - predicateWithFormat: - @"destinationContact == %@ && (SUBQUERY(destinationContact.outgoingRequests, $friendRequest, $friendRequest.destinationContact == SELF.sourceContact).@count == 0)", - [self.blockchainIdentity matchingDashpayUserInContext:self.context]]; -} - -- (NSPredicate *)invertedPredicate { - return [NSPredicate - predicateWithFormat: - @"sourceContact == %@ && (SUBQUERY(sourceContact.incomingRequests, $friendRequest, $friendRequest.sourceContact == SELF.destinationContact).@count > 0)", - [self.blockchainIdentity matchingDashpayUserInContext:self.context]]; -} - -- (NSArray *)sortDescriptors { - NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] - initWithKey:@"sourceContact.associatedBlockchainIdentity.dashpayUsername.stringValue" - ascending:YES]; - return @[ sortDescriptor ]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWInvitationSuggestionView.h b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWInvitationSuggestionView.h new file mode 100644 index 000000000..00418c99d --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWInvitationSuggestionView.h @@ -0,0 +1,28 @@ +// +// 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 + +@interface DWInvitationSuggestionView : UIView + +@property (readonly, nonatomic, strong) UIButton *inviteButton; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWInvitationSuggestionView.m b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWInvitationSuggestionView.m new file mode 100644 index 000000000..0d8263a52 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWInvitationSuggestionView.m @@ -0,0 +1,70 @@ +// +// 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 "DWInvitationSuggestionView.h" + +#import "DWActionButton.h" +#import "DWUIKit.h" + +@implementation DWInvitationSuggestionView + +@synthesize inviteButton = _inviteButton; + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UILabel *orLabel = [[UILabel alloc] init]; + orLabel.translatesAutoresizingMaskIntoConstraints = NO; + orLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; + orLabel.textColor = [UIColor dw_tertiaryTextColor]; + orLabel.textAlignment = NSTextAlignmentCenter; + orLabel.text = NSLocalizedString(@"or", nil); + orLabel.adjustsFontForContentSizeCategory = YES; + + UIImageView *inviteImageView = [[UIImageView alloc] init]; + inviteImageView.translatesAutoresizingMaskIntoConstraints = NO; + inviteImageView.image = [UIImage imageNamed:@"menu_invite"]; + inviteImageView.contentMode = UIViewContentModeCenter; + + DWActionButton *inviteButton = [[DWActionButton alloc] init]; + inviteButton.translatesAutoresizingMaskIntoConstraints = NO; + inviteButton.inverted = YES; + [inviteButton setTitle:NSLocalizedString(@"Invite Someone to join the Dash Network", nil) forState:UIControlStateNormal]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + orLabel, inviteImageView, inviteButton + ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.spacing = 4; + [self addSubview:stackView]; + + [NSLayoutConstraint activateConstraints:@[ + [stackView.topAnchor constraintEqualToAnchor:self.topAnchor], + [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor], + + [inviteButton.heightAnchor constraintEqualToConstant:44], + ]]; + + _inviteButton = inviteButton; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.h new file mode 100644 index 000000000..cacba97ff --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.h @@ -0,0 +1,29 @@ +// +// 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 + +@interface DWNoContactsViewController : UIViewController + +@property (readonly, nonatomic, strong) UIButton *addButton; +@property (readonly, nonatomic, strong) UIButton *inviteButton; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.m new file mode 100644 index 000000000..fabcd61ac --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.m @@ -0,0 +1,100 @@ +// +// 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 "DWNoContactsViewController.h" + +#import "DWActionButton.h" +#import "DWGlobalOptions.h" +#import "DWInvitationSuggestionView.h" +#import "DWUIKit.h" + +@interface DWNoContactsViewController () + +@property (null_resettable, strong, nonatomic) DWInvitationSuggestionView *invitationView; + +@end + +@implementation DWNoContactsViewController + +@synthesize addButton = _addButton; + +- (UIButton *)inviteButton { + return self.invitationView.inviteButton; +} + +- (DWInvitationSuggestionView *)invitationView { + if (!_invitationView) { + _invitationView = [[DWInvitationSuggestionView alloc] init]; + _invitationView.translatesAutoresizingMaskIntoConstraints = NO; + _invitationView.alpha = [DWGlobalOptions sharedInstance].dpInvitationFlowEnabled ? 1.0 : 0.0; + } + return _invitationView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UIImageView *imageView = [[UIImageView alloc] init]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.image = [UIImage imageNamed:@"no_contacts_placeholder"]; + imageView.contentMode = UIViewContentModeCenter; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + titleLabel.textColor = [UIColor dw_secondaryTextColor]; + titleLabel.numberOfLines = 0; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.text = NSLocalizedString(@"You do not have any contacts at the moment", nil); + titleLabel.adjustsFontForContentSizeCategory = YES; + + DWActionButton *addButton = [[DWActionButton alloc] init]; + addButton.translatesAutoresizingMaskIntoConstraints = NO; + [addButton setTitle:NSLocalizedString(@"Add a New Contact", nil) forState:UIControlStateNormal]; + + UIStackView *centerStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + imageView, titleLabel, addButton + ]]; + centerStackView.translatesAutoresizingMaskIntoConstraints = NO; + centerStackView.axis = UILayoutConstraintAxisVertical; + centerStackView.spacing = 24.0; + centerStackView.alignment = UIStackViewAlignmentCenter; + [self.view addSubview:centerStackView]; + + [self.view addSubview:self.invitationView]; + + UILayoutGuide *guide = self.view.layoutMarginsGuide; + [NSLayoutConstraint activateConstraints:@[ + [centerStackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:centerStackView.trailingAnchor], + [centerStackView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], + + [self.invitationView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:self.invitationView.trailingAnchor], + [guide.bottomAnchor constraintEqualToAnchor:self.invitationView.bottomAnchor], + + [addButton.heightAnchor constraintEqualToConstant:50], + [addButton.widthAnchor constraintEqualToAnchor:centerStackView.widthAnchor + multiplier:0.8], + ]]; + + _addButton = addButton; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.m deleted file mode 100644 index bdefc39ef..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.m +++ /dev/null @@ -1,70 +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 "DWRequestsViewController.h" - -#import "DWBaseContactsViewController+DWProtected.h" -#import "DWRequestsContentViewController.h" -#import "DWRequestsModel.h" - -@implementation DWRequestsViewController - -@synthesize model = _model; -@synthesize stateController = _stateController; -@synthesize contentController = _contentController; - -- (instancetype)initWithModel:(DWRequestsModel *)model - payModel:(id)payModel - dataProvider:(id)dataProvider { - self = [super initWithPayModel:payModel dataProvider:dataProvider]; - if (self) { - _model = model; - _model.delegate = self; - } - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.title = NSLocalizedString(@"Contact Requests", nil); - self.searchBar.placeholder = NSLocalizedString(@"Search for a contact request", nil); -} - -#pragma mark - Private - -- (DWSearchStateViewController *)stateController { - if (_stateController == nil) { - _stateController = [[DWSearchStateViewController alloc] init]; - _stateController.delegate = self; - } - return _stateController; -} - -- (DWBaseContactsContentViewController *)contentController { - if (_contentController == nil) { - DWRequestsContentViewController *controller = - [[DWRequestsContentViewController alloc] initWithPayModel:self.payModel - dataProvider:self.dataProvider]; - controller.itemsDelegate = self; - controller.dataSource = self.model.dataSource; - _contentController = controller; - } - return _contentController; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.h b/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.h deleted file mode 100644 index bb2677325..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.h +++ /dev/null @@ -1,33 +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 "DWBaseContactsModel.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWFetchedResultsDataSource; - -@interface DWRequestsModel : DWBaseContactsModel - -- (instancetype)initWithRequestsDataSource:(DWFetchedResultsDataSource *)requestsDataSource NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPBlockchainIdentityBackedItem.h b/DashWallet/Sources/UI/DashPay/Contacts/Views/BaseCollectionReusableView.h similarity index 81% rename from DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPBlockchainIdentityBackedItem.h rename to DashWallet/Sources/UI/DashPay/Contacts/Views/BaseCollectionReusableView.h index 7124e0d72..84621ae58 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPBlockchainIdentityBackedItem.h +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/BaseCollectionReusableView.h @@ -19,11 +19,10 @@ NS_ASSUME_NONNULL_BEGIN -@class DSBlockchainIdentity; +@interface BaseCollectionReusableView : UICollectionReusableView -@protocol DWDPBlockchainIdentityBackedItem - -@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; +@property (nonatomic, assign) BOOL isContentChanged; +@property (nonatomic, assign) CGSize cachedSize; @end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.h b/DashWallet/Sources/UI/DashPay/Contacts/Views/BaseCollectionReusableView.m similarity index 84% rename from DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.h rename to DashWallet/Sources/UI/DashPay/Contacts/Views/BaseCollectionReusableView.m index abb7f94fd..c52b9256b 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.h +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/BaseCollectionReusableView.m @@ -15,12 +15,8 @@ // limitations under the License. // -#import "DWDPBasicCell.h" +#import "BaseCollectionReusableView.h" -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPTxListCell : DWDPBasicCell +@implementation BaseCollectionReusableView @end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicItem.h b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchPlaceholderView.h similarity index 59% rename from DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicItem.h rename to DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchPlaceholderView.h index e5393a66b..663c016a6 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicItem.h +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchPlaceholderView.h @@ -15,19 +15,22 @@ // limitations under the License. // -#import +#import "BaseCollectionReusableView.h" NS_ASSUME_NONNULL_BEGIN -@protocol DWDPItemCellDelegate +@class DWContactsSearchPlaceholderView; + +@protocol DWContactsSearchPlaceholderViewDelegate + +- (void)contactsSearchPlaceholderView:(DWContactsSearchPlaceholderView *)view searchAction:(UIButton *)sender; + @end -@protocol DWDPBasicItem +@interface DWContactsSearchPlaceholderView : BaseCollectionReusableView -@property (readonly, nonatomic) NSString *username; -@property (nullable, readonly, nonatomic) NSString *displayName; -@property (nullable, readonly, nonatomic) NSAttributedString *title; -@property (nullable, readonly, nonatomic) NSString *subtitle; +@property (nullable, nonatomic, copy) NSString *searchQuery; +@property (nullable, nonatomic, weak) id delegate; @end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchPlaceholderView.m b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchPlaceholderView.m new file mode 100644 index 000000000..48b3b500b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchPlaceholderView.m @@ -0,0 +1,127 @@ +// +// 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 "DWContactsSearchPlaceholderView.h" + +#import "DWActionButton.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWContactsSearchPlaceholderView () + +@property (nonatomic, strong) UILabel *descriptionLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWContactsSearchPlaceholderView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UILabel *label = [[UILabel alloc] init]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + label.numberOfLines = 0; + label.lineBreakMode = NSLineBreakByWordWrapping; + label.textAlignment = NSTextAlignmentCenter; + label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + label.adjustsFontForContentSizeCategory = YES; + label.textColor = [UIColor dw_darkTitleColor]; + [label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + _descriptionLabel = label; + + DWActionButton *button = [[DWActionButton alloc] initWithFrame:CGRectZero]; + button.translatesAutoresizingMaskIntoConstraints = NO; + button.small = YES; + button.inverted = YES; + button.usedOnDarkBackground = NO; + button.imageEdgeInsets = UIEdgeInsetsMake(0.0, -8.0, 0.0, 0.0); + [button setImage:[UIImage imageNamed:@"dp_search_add_contact"] forState:UIControlStateNormal]; + [button setTitle:NSLocalizedString(@"Search for a User on the Dash Network", nil) + forState:UIControlStateNormal]; + [button addTarget:self action:@selector(actionButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + NSArray *views = @[ label, button ]; + UIStackView *verticalStackView = [[UIStackView alloc] initWithArrangedSubviews:views]; + verticalStackView.translatesAutoresizingMaskIntoConstraints = NO; + verticalStackView.axis = UILayoutConstraintAxisVertical; + verticalStackView.alignment = UIStackViewAlignmentCenter; + verticalStackView.spacing = 24.0; + [self addSubview:verticalStackView]; + + UILayoutGuide *guide = self.layoutMarginsGuide; + [NSLayoutConstraint activateConstraints:@[ + [verticalStackView.topAnchor constraintEqualToAnchor:self.topAnchor + constant:32.0], + [verticalStackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:verticalStackView.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:verticalStackView.bottomAnchor + constant:16.0], + [button.heightAnchor constraintEqualToConstant:44.0], + ]]; + } + return self; +} + +- (void)setSearchQuery:(NSString *)searchQuery { + if (_searchQuery == nil || ![_searchQuery isEqualToString:searchQuery]) { + self.isContentChanged = YES; + } + + _searchQuery = searchQuery; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + + NSString *format = NSLocalizedString(@"There are no users that match with the name %@ in your contacts", nil); + NSString *query = [NSString stringWithFormat:@"\"%@\"", searchQuery ?: @""]; + NSString *text = [NSString stringWithFormat:format, query]; + + NSAttributedString *attributed = + [[NSAttributedString alloc] initWithString:text + attributes:@{NSFontAttributeName : self.regularFont}]; + [result appendAttributedString:attributed]; + + NSRange queryRange = [text rangeOfString:query]; + if (queryRange.location != NSNotFound) { + [result removeAttribute:NSFontAttributeName range:queryRange]; + [result setAttributes:@{NSFontAttributeName : self.boldFont} range:queryRange]; + } + + [result endEditing]; + + self.descriptionLabel.attributedText = result; +} + +- (void)actionButtonAction:(UIButton *)sender { + [self.delegate contactsSearchPlaceholderView:self searchAction:sender]; +} + +- (UIFont *)regularFont { + return [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; +} + +- (UIFont *)boldFont { + return [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPEstablishedContactItem.h b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchFailedHeaderView.h similarity index 86% rename from DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPEstablishedContactItem.h rename to DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchFailedHeaderView.h index 8634e2482..9b302e1b2 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPEstablishedContactItem.h +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchFailedHeaderView.h @@ -15,11 +15,11 @@ // limitations under the License. // -#import "DWDPBasicUserItem.h" +#import "BaseCollectionReusableView.h" NS_ASSUME_NONNULL_BEGIN -@protocol DWDPEstablishedContactItem +@interface DWGlobalMatchFailedHeaderView : BaseCollectionReusableView @end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchFailedHeaderView.m b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchFailedHeaderView.m new file mode 100644 index 000000000..023bc82cc --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchFailedHeaderView.m @@ -0,0 +1,48 @@ +// +// 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 "DWGlobalMatchFailedHeaderView.h" + +#import "DWNetworkUnavailableView.h" +#import "DWUIKit.h" + +@implementation DWGlobalMatchFailedHeaderView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + DWNetworkUnavailableView *view = [[DWNetworkUnavailableView alloc] initWithFrame:CGRectZero]; + view.translatesAutoresizingMaskIntoConstraints = NO; + view.error = NSLocalizedString(@"Unable to provide suggestions", nil); + [self addSubview:view]; + + UILayoutGuide *guide = self.layoutMarginsGuide; + [NSLayoutConstraint activateConstraints:@[ + [view.topAnchor constraintEqualToAnchor:self.topAnchor + constant:32.0], + [view.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:view.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor + constant:32.0], + ]]; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.h b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchHeaderView.h similarity index 80% rename from DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.h rename to DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchHeaderView.h index b8c6d9bd6..1cea93c05 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.h +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchHeaderView.h @@ -15,15 +15,13 @@ // limitations under the License. // -#import "DWBaseContactsModel.h" +#import "BaseCollectionReusableView.h" NS_ASSUME_NONNULL_BEGIN -@class DWRequestsModel; +@interface DWGlobalMatchHeaderView : BaseCollectionReusableView -@interface DWContactsModel : DWBaseContactsModel - -- (DWRequestsModel *)contactRequestsModel; +@property (nullable, nonatomic, copy) NSString *searchQuery; @end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchHeaderView.m b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchHeaderView.m new file mode 100644 index 000000000..b17f76add --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWGlobalMatchHeaderView.m @@ -0,0 +1,110 @@ +// +// 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 "DWGlobalMatchHeaderView.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWGlobalMatchHeaderView () + +@property (nonatomic, strong) UILabel *descriptionLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWGlobalMatchHeaderView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + UILabel *label = [[UILabel alloc] init]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.backgroundColor = [UIColor dw_backgroundColor]; + label.numberOfLines = 0; + label.lineBreakMode = NSLineBreakByWordWrapping; + label.textAlignment = NSTextAlignmentCenter; + label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + label.adjustsFontForContentSizeCategory = YES; + label.textColor = [UIColor dw_darkTitleColor]; + [label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [self addSubview:label]; + _descriptionLabel = label; + + UILayoutGuide *guide = self.layoutMarginsGuide; + [NSLayoutConstraint activateConstraints:@[ + [label.topAnchor constraintEqualToAnchor:self.topAnchor + constant:32.0], + [label.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:label.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:label.bottomAnchor + constant:32.0], + ]]; + } + return self; +} + +- (void)setSearchQuery:(NSString *)searchQuery { + if (_searchQuery == nil || ![_searchQuery isEqualToString:searchQuery]) { + self.isContentChanged = YES; + } + + _searchQuery = searchQuery; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + + NSAttributedString *title = [[NSAttributedString alloc] + initWithString:NSLocalizedString(@"More Suggestions", nil) + attributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleTitle3]}]; + [result appendAttributedString:title]; + [result appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n"]]; + + NSString *format = NSLocalizedString(@"Users that matches %@ who are currently not in your contacts", nil); + NSString *query = [NSString stringWithFormat:@"\"%@\"", searchQuery ?: @""]; + NSString *text = [NSString stringWithFormat:format, query]; + + NSMutableAttributedString *attributed = + [[NSMutableAttributedString alloc] initWithString:text + attributes:@{NSFontAttributeName : self.regularFont}]; + + NSRange queryRange = [text rangeOfString:query]; + if (queryRange.location != NSNotFound) { + [attributed removeAttribute:NSFontAttributeName range:queryRange]; + [attributed setAttributes:@{NSFontAttributeName : self.boldFont} range:queryRange]; + } + + [result appendAttributedString:attributed]; + + [result endEditing]; + + self.descriptionLabel.attributedText = result; +} + +- (UIFont *)regularFont { + return [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; +} + +- (UIFont *)boldFont { + return [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.m b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.m deleted file mode 100644 index c3c69e2bb..000000000 --- a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.m +++ /dev/null @@ -1,72 +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 "DWTitleActionHeaderView.h" - -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWTitleActionHeaderView () - -@property (weak, nonatomic) IBOutlet UIView *contentView; - -@end - -@implementation DWTitleActionHeaderView - -- (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.actionButton.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; -} - -- (IBAction)buttonAction:(UIButton *)sender { - [self.delegate titleActionHeaderView:self buttonAction:sender]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/DWDashPayConstants.m b/DashWallet/Sources/UI/DashPay/DWDashPayConstants.m deleted file mode 100644 index aa44ff27d..000000000 --- a/DashWallet/Sources/UI/DashPay/DWDashPayConstants.m +++ /dev/null @@ -1,25 +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 "DWDashPayConstants.h" - -#import - -uint64_t DWDP_MIN_BALANCE_TO_CREATE_USERNAME = (DUFFS / 100); // 0.01 Dash - -NSInteger DW_MIN_USERNAME_LENGTH = 3; -NSInteger DW_MAX_USERNAME_LENGTH = 24; diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.h b/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.h similarity index 68% rename from DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.h rename to DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.h index 23e819157..6fb0c9902 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.h +++ b/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.h @@ -15,22 +15,24 @@ // limitations under the License. // -#import "DWBaseContactsViewController.h" +#import NS_ASSUME_NONNULL_BEGIN -@class DWRequestsModel; +typedef NS_ENUM(NSUInteger, DWErrorDescriptionType) { + DWErrorDescriptionType_Profile, + DWErrorDescriptionType_AcceptContactRequest, + DWErrorDescriptionType_SendContactRequest, +}; -@interface DWRequestsViewController : DWBaseContactsViewController +@interface DWNetworkErrorViewController : UIViewController -- (instancetype)initWithModel:(DWRequestsModel *)model - payModel:(id)payModel - dataProvider:(id)dataProvider; +- (instancetype)initWithType:(DWErrorDescriptionType)type; -- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; -- (instancetype)initWithCoder:(nullable NSCoder *)coder NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; +- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; @end diff --git a/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.m b/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.m new file mode 100644 index 000000000..b7b859021 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.m @@ -0,0 +1,107 @@ +// +// 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 "DWNetworkErrorViewController.h" + +#import "DWModalPopupTransition.h" +#import "DWNetworkUnavailableView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWNetworkErrorViewController () + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; +@property (nonatomic, assign) DWErrorDescriptionType type; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWNetworkErrorViewController + +- (instancetype)initWithType:(DWErrorDescriptionType)type { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _type = type; + + _modalTransition = [[DWModalPopupTransition alloc] initWithInteractiveTransitionAllowed:NO]; + + self.transitioningDelegate = self.modalTransition; + self.modalPresentationStyle = UIModalPresentationCustom; + } + return self; +} + +- (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]; + + DWNetworkUnavailableView *errorView = [[DWNetworkUnavailableView alloc] initWithFrame:CGRectZero]; + errorView.translatesAutoresizingMaskIntoConstraints = NO; + switch (self.type) { + case DWErrorDescriptionType_Profile: + errorView.error = NSLocalizedString(@"Unable to fetch contact details", nil); + break; + case DWErrorDescriptionType_AcceptContactRequest: + errorView.error = NSLocalizedString(@"Unable to accept contact request", nil); + break; + case DWErrorDescriptionType_SendContactRequest: + errorView.error = NSLocalizedString(@"Unable to send contact request", nil); + break; + } + [contentView addSubview:errorView]; + + UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeSystem]; + closeButton.translatesAutoresizingMaskIntoConstraints = NO; + [closeButton setTitle:NSLocalizedString(@"Close", nil) forState:UIControlStateNormal]; + [closeButton addTarget:self action:@selector(closeButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + [contentView addSubview:closeButton]; + + + [NSLayoutConstraint activateConstraints:@[ + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + [contentView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], + + [errorView.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:32.0], + [errorView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [errorView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + + [closeButton.topAnchor constraintEqualToAnchor:errorView.bottomAnchor + constant:32.0], + [closeButton.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:closeButton.bottomAnchor + constant:16.0], + [closeButton.heightAnchor constraintGreaterThanOrEqualToConstant:44.0], + ]]; +} + +- (void)closeButtonAction:(UIButton *)sender { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Global/DSBlockchainIdentity+DWDisplayName.h b/DashWallet/Sources/UI/DashPay/Global/DSBlockchainIdentity+DWDisplayName.h new file mode 100644 index 000000000..5eb66ecc2 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/DSBlockchainIdentity+DWDisplayName.h @@ -0,0 +1,28 @@ +// +// 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 DSBlockchainIdentity (DWDisplayName) + +- (NSString *)dw_displayNameOrUsername; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule.m b/DashWallet/Sources/UI/DashPay/Global/DSBlockchainIdentity+DWDisplayName.m similarity index 69% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule.m rename to DashWallet/Sources/UI/DashPay/Global/DSBlockchainIdentity+DWDisplayName.m index f7871f29f..41851b340 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule.m +++ b/DashWallet/Sources/UI/DashPay/Global/DSBlockchainIdentity+DWDisplayName.m @@ -15,20 +15,16 @@ // limitations under the License. // -#import "DWUsernameValidationRule+Protected.h" +#import "DSBlockchainIdentity+DWDisplayName.h" -@implementation DWUsernameValidationRule +@implementation DSBlockchainIdentity (DWDisplayName) -- (instancetype)init { - self = [super init]; - if (self) { - [self validateText:nil]; +- (NSString *)dw_displayNameOrUsername { + NSString *displayName = self.displayName; + if (displayName.length == 0) { + return self.currentDashpayUsername; } - return self; -} - -- (void)validateText:(NSString *_Nullable)text { - NSAssert(NO, @"To be overriden"); + return displayName; } @end diff --git a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.h b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.h deleted file mode 100644 index 426941a09..000000000 --- a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.h +++ /dev/null @@ -1,34 +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 "DWDPBasicUserItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDashPayContactsActions : NSObject - -+ (void)acceptContactRequest:(id)item - completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; -+ (void)declineContactRequest:(id)item - completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.m b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.m deleted file mode 100644 index 4796c60e9..000000000 --- a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.m +++ /dev/null @@ -1,96 +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 "DWDashPayContactsActions.h" - -#import "DWDPBlockchainIdentityBackedItem.h" -#import "DWDPFriendRequestBackedItem.h" -#import "DWDPNewIncomingRequestItem.h" -#import "DWDashPayContactsUpdater.h" -#import "DWEnvironment.h" -#import "DWNotificationsProvider.h" - -@implementation DWDashPayContactsActions - -+ (void)acceptContactRequest:(id)item - completion:(void (^)(BOOL success, NSArray *errors))completion { - NSAssert([item conformsToProtocol:@protocol(DWDPNewIncomingRequestItem)], @"Incompatible item"); - - const BOOL isBlockchainIdentityBacked = [item conformsToProtocol:@protocol(DWDPBlockchainIdentityBackedItem)]; - const BOOL isFriendRequestBacked = [item conformsToProtocol:@protocol(DWDPFriendRequestBackedItem)]; - NSAssert(isBlockchainIdentityBacked || isFriendRequestBacked, @"Invalid item to accept contact request"); - - id newRequestItem = (id)item; - newRequestItem.requestState = DWDPNewIncomingRequestItemState_Processing; - - void (^resultCompletion)(BOOL success, NSArray *errors) = ^(BOOL success, NSArray *errors) { - newRequestItem.requestState = success ? DWDPNewIncomingRequestItemState_Accepted : DWDPNewIncomingRequestItemState_Failed; - - // TODO: DP temp workaround to update and force reload contact list - // This will trigger DWNotificationsProvider to reset - [[DWDashPayContactsUpdater sharedInstance] fetch]; - - DSLog(@"DWDP: accept contact request %@: %@", success ? @"Succeeded" : @"Failed", errors); - - if (completion) { - completion(success, errors); - } - }; - - // Accepting request from a DSFriendRequestEntity doesn't require searching for associated blockchain identity. - // Since all DWDPBasicUserItem has associated BI, check if it's a DSFriendRequestEntity first. - if (isFriendRequestBacked && [(id)item friendRequestEntity] != nil) { - id backedItem = (id)item; - [self acceptContactRequestFromFriendRequest:backedItem.friendRequestEntity completion:resultCompletion]; - } - else if (isBlockchainIdentityBacked && [(id)item blockchainIdentity] != nil) { - id backedItem = (id)item; - [self acceptContactRequestFromBlockchainIdentity:backedItem.blockchainIdentity completion:resultCompletion]; - } -} - -+ (void)declineContactRequest:(id)item - completion:(void (^)(BOOL success, NSArray *errors))completion { - // TODO: DP dummy method - - NSAssert([item conformsToProtocol:@protocol(DWDPNewIncomingRequestItem)], @"Incompatible item"); - - id newRequestItem = (id)item; - newRequestItem.requestState = DWDPNewIncomingRequestItemState_Processing; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - newRequestItem.requestState = DWDPNewIncomingRequestItemState_Failed; - }); -} - -#pragma mark - Private - -+ (void)acceptContactRequestFromBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - completion:(void (^)(BOOL success, NSArray *errors))completion { - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - [myBlockchainIdentity acceptFriendRequestFromBlockchainIdentity:blockchainIdentity completion:completion]; -} - -+ (void)acceptContactRequestFromFriendRequest:(DSFriendRequestEntity *)friendRequest - completion:(void (^)(BOOL success, NSArray *errors))completion { - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - [myBlockchainIdentity acceptFriendRequest:friendRequest completion:completion]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.h b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.h deleted file mode 100644 index f3438e2b0..000000000 --- a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.h +++ /dev/null @@ -1,43 +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 - -extern NSNotificationName const DWDashPayContactsDidUpdateNotification; - -@interface DWDashPayContactsUpdater : NSObject - -/// Subscribe to update contacts every 30 seconds -- (void)beginUpdating; -/// Unsubscribe from updating contacts -- (void)endUpdating; - -/// Initiate update immediately -- (void)fetch; -/// Initiate update immediately -- (void)fetchWithCompletion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; - -+ (instancetype)sharedInstance; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.m b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.m deleted file mode 100644 index e22759c52..000000000 --- a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.m +++ /dev/null @@ -1,138 +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 "DWDashPayContactsUpdater.h" - -#import "DWEnvironment.h" - -NS_ASSUME_NONNULL_BEGIN - -NSNotificationName const DWDashPayContactsDidUpdateNotification = @"org.dash.wallet.dp.contacts-did-update"; - -static NSTimeInterval const UPDATE_INTERVAL = 30; - -@interface DWDashPayContactsUpdater () - -@property (nonatomic, assign, getter=isUpdating) BOOL updating; -@property (nonatomic, assign, getter=isFetching) BOOL fetching; - -@property (nullable, nonatomic, copy) void (^fetchCompletion)(BOOL success, NSArray *errors); - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDashPayContactsUpdater - -+ (instancetype)sharedInstance { - static DWDashPayContactsUpdater *_sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _sharedInstance = [[self alloc] init]; - }); - return _sharedInstance; -} - -- (void)beginUpdating { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - if (myBlockchainIdentity == nil) { - return; - } - - if (self.isUpdating) { - return; - } - self.updating = YES; - - [self fetch]; -} - -- (void)endUpdating { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - if (!self.isUpdating) { - return; - } - self.updating = NO; - - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fetchInternal) object:nil]; -} - -- (void)fetch { - [self fetchIntiatedInternally:NO completion:nil]; -} - -- (void)fetchWithCompletion:(void (^_Nullable)(BOOL success, NSArray *errors))completion { - [self fetchIntiatedInternally:NO completion:completion]; -} - -#pragma mark - Private - -- (void)fetchInternal { - [self fetchIntiatedInternally:YES completion:nil]; -} - -- (void)fetchIntiatedInternally:(BOOL)initiatedInternally completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - if (myBlockchainIdentity == nil) { - return; - } - - if (!initiatedInternally) { - self.fetchCompletion = completion; - } - - if (self.isFetching) { - return; - } - self.fetching = YES; - - // Cancel any scheduled calls if fetch is called manually - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fetchInternal) object:nil]; - - __weak typeof(self) weakSelf = self; - [myBlockchainIdentity fetchContactRequests:^(BOOL success, NSArray *_Nonnull errors) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - strongSelf.fetching = NO; - - DSLog(@"DWDP: Fetch contact requests %@: %@", - success ? @"Succeeded" : @"Failed", - errors.count == 0 ? @"" : errors); - - if (strongSelf.fetchCompletion) { - strongSelf.fetchCompletion(success, errors); - strongSelf.fetchCompletion = nil; - } - - [[NSNotificationCenter defaultCenter] postNotificationName:DWDashPayContactsDidUpdateNotification - object:nil]; - - [strongSelf performSelector:@selector(fetchInternal) withObject:nil afterDelay:UPDATE_INTERVAL]; - }]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.h b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.h deleted file mode 100644 index a7cd48aa1..000000000 --- a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.h +++ /dev/null @@ -1,37 +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 -#import - -#import "DWDPBasicUserItem.h" -#import "DWDPNotificationItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWNotificationsData : NSObject - -@property (readonly, nonatomic, assign) BOOL isEmpty; -@property (readonly, nonatomic, copy) NSArray> *unreadItems; -@property (readonly, nonatomic, copy) NSArray> *oldItems; - -- (instancetype)initWithUnreadItems:(NSArray> *)unreadItems - oldItems:(NSArray> *)oldItems NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.m b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.m deleted file mode 100644 index 70adf6395..000000000 --- a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.m +++ /dev/null @@ -1,45 +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 "DWNotificationsData.h" - -@implementation DWNotificationsData - -- (instancetype)initWithUnreadItems:(NSArray> *)unreadItems - oldItems:(NSArray> *)oldItems { - NSParameterAssert(unreadItems); - NSParameterAssert(oldItems); - self = [super init]; - if (self) { - _unreadItems = [unreadItems copy]; - _oldItems = [oldItems copy]; - _isEmpty = _unreadItems.count == 0 && _oldItems.count == 0; - } - return self; -} - -- (instancetype)init { - return [self initWithUnreadItems:@[] oldItems:@[]]; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(nullable NSZone *)zone { - return [[self.class alloc] initWithUnreadItems:self.unreadItems oldItems:self.oldItems]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.h b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.h deleted file mode 100644 index 87bb1e446..000000000 --- a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.h +++ /dev/null @@ -1,35 +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 "DWFetchedResultsDataSource.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSBlockchainIdentity; - -@interface DWNotificationsFetchedDataSource : DWFetchedResultsDataSource - -@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - inContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.m b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.m deleted file mode 100644 index bd8913e2e..000000000 --- a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.m +++ /dev/null @@ -1,48 +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 "DWNotificationsFetchedDataSource.h" - -#import "DWEnvironment.h" - -@implementation DWNotificationsFetchedDataSource - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - inContext:(NSManagedObjectContext *)context { - self = [super initWithContext:context]; - if (self) { - _blockchainIdentity = blockchainIdentity; - } - return self; -} - -- (NSString *)entityName { - return NSStringFromClass(DSFriendRequestEntity.class); -} - -- (NSPredicate *)predicate { - DSDashpayUserEntity *dashPayUser = [self.blockchainIdentity matchingDashpayUserInContext:self.context]; - return [NSPredicate predicateWithFormat:@"destinationContact == %@ || sourceContact == %@", dashPayUser, dashPayUser]; -} - -- (NSArray *)sortDescriptors { - // reversed order, from old to new - NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:YES]; - return @[ sortDescriptor ]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.m b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.m deleted file mode 100644 index e594d9316..000000000 --- a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.m +++ /dev/null @@ -1,209 +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 "DWNotificationsProvider.h" - -#import "DWDashPayContactsUpdater.h" -#import "DWEnvironment.h" -#import "DWGlobalOptions.h" -#import "DWNotificationsData.h" -#import "DWNotificationsFetchedDataSource.h" - -#import "DWDPAcceptedRequestNotificationObject.h" -#import "DWDPEstablishedContactNotificationObject.h" -#import "DWDPNewIncomingRequestNotificationObject.h" -#import "DWDPOutgoingRequestNotificationObject.h" - -NS_ASSUME_NONNULL_BEGIN - -NSNotificationName const DWNotificationsProviderWillUpdateNotification = @"org.dash.wallet.dp.notifications-will-update"; -NSNotificationName const DWNotificationsProviderDidUpdateNotification = @"org.dash.wallet.dp.notifications-did-update"; - -@interface DWNotificationsProvider () - -@property (nonatomic, strong) DWFetchedResultsDataSource *fetchedDataSource; -@property (nonatomic, copy) DWNotificationsData *data; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWNotificationsProvider - -+ (instancetype)sharedInstance { - static DWNotificationsProvider *_sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _sharedInstance = [[self alloc] init]; - }); - return _sharedInstance; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _data = [[DWNotificationsData alloc] init]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(didUpdateContacts) - name:DWDashPayContactsDidUpdateNotification - object:nil]; - - // Defer initial reset of notifications because it would lead to a deadlock - // (since DWNotificationsProvider is a singleton and reset would produce a notification) - dispatch_async(dispatch_get_main_queue(), ^{ - [self forceUpdate]; - }); - } - return self; -} - -- (void)forceUpdate { - DSBlockchainIdentity *blockchainIdentity = [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; - if (!blockchainIdentity) { - return; - } - - NSManagedObjectContext *context = [NSManagedObjectContext viewContext]; - - _fetchedDataSource = [[DWNotificationsFetchedDataSource alloc] initWithBlockchainIdentity:blockchainIdentity inContext:context]; - _fetchedDataSource.shouldSubscribeToNotifications = YES; - _fetchedDataSource.delegate = self; - [_fetchedDataSource start]; - _fetchedDataSource.fetchedResultsController.delegate = self; - - [self reload]; -} - -#pragma mark - Private - -- (void)setData:(DWNotificationsData *)data { - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter postNotificationName:DWNotificationsProviderWillUpdateNotification object:self]; - _data = data; - [notificationCenter postNotificationName:DWNotificationsProviderDidUpdateNotification object:self]; -} - -- (void)reload { - // fetched objects come in a reversed order (from old to new) - NSArray *fetchedObjects = self.fetchedDataSource.fetchedResultsController.fetchedObjects; - - NSMutableDictionary *> *connections = - [NSMutableDictionary dictionary]; - for (DSFriendRequestEntity *request in fetchedObjects) { - NSManagedObjectID *sourceID = request.sourceContact.objectID; - NSMutableSet *sourceConnections = connections[sourceID]; - if (sourceConnections == nil) { - sourceConnections = [NSMutableSet set]; - connections[sourceID] = sourceConnections; - } - [sourceConnections addObject:request.destinationContact.objectID]; - } - - DSBlockchainIdentity *blockchainIdentity = [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; - NSManagedObjectID *userID = blockchainIdentity.matchingDashpayUserInViewContext.objectID; - - DWGlobalOptions *options = [DWGlobalOptions sharedInstance]; - const NSTimeInterval mostRecentViewedTimestamp = [options.mostRecentViewedNotificationDate timeIntervalSince1970]; - - NSMutableArray> *newItems = [NSMutableArray array]; - NSMutableArray> *oldItems = [NSMutableArray array]; - - NSMutableSet *processed = [NSMutableSet set]; - - for (DSFriendRequestEntity *request in fetchedObjects) { - NSManagedObjectID *sourceID = request.sourceContact.objectID; - NSManagedObjectID *destinationID = request.destinationContact.objectID; - NSSet *destinationConnections = connections[destinationID]; - const BOOL isFriendship = [destinationConnections containsObject:sourceID]; - const BOOL isNew = request.timestamp > mostRecentViewedTimestamp; - NSMutableArray> *items = isNew ? newItems : oldItems; - - if ([sourceID isEqual:userID]) { // outoging - const BOOL isInitiatedByMe = ![processed containsObject:destinationID]; - [processed addObject:destinationID]; - - if (isFriendship) { - DSBlockchainIdentity *blockchainIdentity = [request.destinationContact.associatedBlockchainIdentity blockchainIdentity]; - DWDPOutgoingRequestNotificationObject *object = - [[DWDPOutgoingRequestNotificationObject alloc] initWithFriendRequestEntity:request - blockchainIdentity:blockchainIdentity - isInitiatedByMe:isInitiatedByMe]; - // all outgoing events should be in the Earlier section - [oldItems addObject:object]; - } - - // outgoing requests with no response (pending) are not shown in notifications - } - else { // incoming - const BOOL isInitiatedByThem = ![processed containsObject:sourceID]; - [processed addObject:sourceID]; - - DSBlockchainIdentity *blockchainIdentity = [request.sourceContact.associatedBlockchainIdentity blockchainIdentity]; - if (isFriendship) { - DWDPAcceptedRequestNotificationObject *object = - [[DWDPAcceptedRequestNotificationObject alloc] initWithFriendRequestEntity:request - blockchainIdentity:blockchainIdentity - isInitiatedByThem:isInitiatedByThem]; - // Don't add notifications about MY responses to the New section - if (isInitiatedByThem) { - [oldItems addObject:object]; - } - else { - [items addObject:object]; - } - } - else { - DWDPNewIncomingRequestNotificationObject *object = - [[DWDPNewIncomingRequestNotificationObject alloc] initWithFriendRequestEntity:request - blockchainIdentity:blockchainIdentity]; - [items addObject:object]; - } - } - } - - self.data = [[DWNotificationsData alloc] initWithUnreadItems:[newItems reverseObjectEnumerator].allObjects - oldItems:[oldItems reverseObjectEnumerator].allObjects]; -} - -#pragma mark - DWFetchedResultsDataSourceDelegate - -- (void)fetchedResultsDataSourceDidUpdate:(DWFetchedResultsDataSource *)fetchedResultsDataSource { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - // internal FRC might be niled out - self.fetchedDataSource.fetchedResultsController.delegate = self; - - [self reload]; -} - -- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - DSLog(@"DWDP: Notification provider's FRC did update"); - - [self reload]; -} - -#pragma mark - Notifications - -- (void)didUpdateContacts { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - [self forceUpdate]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.h b/DashWallet/Sources/UI/DashPay/Global/UIImageView+DWDPAvatar.h similarity index 82% rename from DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.h rename to DashWallet/Sources/UI/DashPay/Global/UIImageView+DWDPAvatar.h index 5f88df09d..9b92b9d5b 100644 --- a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.h +++ b/DashWallet/Sources/UI/DashPay/Global/UIImageView+DWDPAvatar.h @@ -19,9 +19,9 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWNoNotificationsCell : UICollectionViewCell +@interface UIImageView (DWDPAvatar) -@property (nonatomic, assign) CGFloat contentWidth; +- (void)dw_setAvatarWithURLString:(NSString *)urlString completion:(void (^)(UIImage *_Nullable image))completion; @end diff --git a/DashWallet/Sources/UI/DashPay/Global/UIImageView+DWDPAvatar.m b/DashWallet/Sources/UI/DashPay/Global/UIImageView+DWDPAvatar.m new file mode 100644 index 000000000..943f88165 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/UIImageView+DWDPAvatar.m @@ -0,0 +1,136 @@ +// +// 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 "UIImageView+DWDPAvatar.h" + +#import "DWDPAvatarView.h" +#import +#import + +@implementation UIImageView (DWDPAvatar) + +- (void)dw_setAvatarWithURLString:(NSString *)urlString completion:(void (^)(UIImage *_Nullable image))completion { + if (urlString.length == 0) { + if (completion) { + completion(nil); + } + return; + } + + + NSURL *url = [NSURL URLWithString:urlString]; + NSURL *originalURL = [url copy]; + if (url == nil) { + if (completion) { + completion(nil); + } + return; + } + + // has crop params + CGRect cropRectOfInterest = CGRectNull; + if (urlString.length > 0 && [urlString rangeOfString:DPCropParameterName].location != NSNotFound) { + NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + NSMutableArray *queryItems = [components.queryItems mutableCopy]; + NSURLQueryItem *cropItem = nil; + for (NSURLQueryItem *item in components.queryItems) { + if ([item.name isEqualToString:DPCropParameterName]) { + cropItem = item; + break; + } + } + + if (cropItem != nil) { + [queryItems removeObject:cropItem]; + + NSArray *params = [cropItem.value componentsSeparatedByString:@","]; + if (params.count != 4) { + params = [cropItem.value componentsSeparatedByString:@"%2C"]; + } + + if (params.count == 4) { + CGFloat x = [params[0] doubleValue]; + CGFloat y = [params[1] doubleValue]; + CGFloat w = [params[2] doubleValue] - x; + CGFloat h = [params[3] doubleValue] - y; + cropRectOfInterest = CGRectMake(x, y, w, h); + } + + components.queryItems = queryItems.count > 0 ? queryItems : nil; + url = components.URL; + } + } + + __weak typeof(self) weakSelf = self; + [self sd_setImageWithURL:url + completed:^(UIImage *_Nullable image, NSError *_Nullable error, SDImageCacheType cacheType, NSURL *_Nullable imageURL) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (image == nil || CGRectIsNull(cropRectOfInterest)) { + if (completion) { + completion(image); + } + + return; + } + + dispatch_async([strongSelf.class processingQueue], ^{ + NSString *croppedKey = originalURL.absoluteString; + + UIImage *croppedImage = [SDImageCache.sharedImageCache imageFromCacheForKey:croppedKey]; + if (croppedImage != nil) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) { + completion(croppedImage); + } + }); + + return; + } + + CGSize imageSize = image.size; + CGRect cropRect = CGRectMake(cropRectOfInterest.origin.x * imageSize.width, + cropRectOfInterest.origin.y * imageSize.height, + cropRectOfInterest.size.width * imageSize.width, + cropRectOfInterest.size.height * imageSize.height); + croppedImage = [image croppedImageWithFrame:cropRect angle:0 circularClip:NO]; + if (croppedImage) { + [SDImageCache.sharedImageCache storeImage:croppedImage forKey:croppedKey completion:nil]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) { + completion(croppedImage); + } + }); + }); + }]; +} + ++ (dispatch_queue_t)processingQueue { + static dispatch_queue_t _sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _sharedInstance = dispatch_queue_create("dw.dp.avatar-processing-queue", DISPATCH_QUEUE_SERIAL); + }); + return _sharedInstance; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.h b/DashWallet/Sources/UI/DashPay/Invites/Additions/DPAlertViewController+DWInvite.h similarity index 75% rename from DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.h rename to DashWallet/Sources/UI/DashPay/Invites/Additions/DPAlertViewController+DWInvite.h index 574b9d54f..4d77efffd 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.h +++ b/DashWallet/Sources/UI/DashPay/Invites/Additions/DPAlertViewController+DWInvite.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,13 +15,13 @@ // limitations under the License. // -#import "DWDPGenericItemView.h" +#import "DPAlertViewController.h" NS_ASSUME_NONNULL_BEGIN -@interface DWDPTxItemView : DWDPGenericItemView +@interface DPAlertViewController (DWInvite) -@property (readonly, nonatomic, strong) UILabel *amountLabel; ++ (DPAlertViewController *)insufficientFundsForInvitationAlert; @end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Additions/DPAlertViewController+DWInvite.m b/DashWallet/Sources/UI/DashPay/Invites/Additions/DPAlertViewController+DWInvite.m new file mode 100644 index 000000000..492c571b1 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Additions/DPAlertViewController+DWInvite.m @@ -0,0 +1,42 @@ +// +// 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 "DPAlertViewController+DWInvite.h" + +#import "DWDashPayConstants.h" +#import + +@implementation DPAlertViewController (DWInvite) + ++ (DPAlertViewController *)insufficientFundsForInvitationAlert { + DSPriceManager *priceManager = [DSPriceManager sharedInstance]; + NSString *amount = [[[priceManager stringForDashAmount:DWDP_MIN_BALANCE_TO_CREATE_INVITE] + stringByReplacingOccurrencesOfString:DASH + withString:@""] + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + NSString *desc = [NSString stringWithFormat: + NSLocalizedString(@"You need at least %@ Dash to create an invitation", nil), amount]; + DPAlertViewController *controller = + [[DPAlertViewController alloc] + initWithIcon:[UIImage imageNamed:@"insufficientFunds_icon"] + title:NSLocalizedString(@"Insufficient Wallet Balance", nil) + description:desc]; + return controller; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.h b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.h similarity index 88% rename from DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.h rename to DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.h index 121e321f0..081020e0a 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.h +++ b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.h @@ -21,12 +21,10 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWConfirmUsernameContentView : UIView +@interface DWConfirmInvitationContentView : UIView @property (readonly, nonatomic, weak) DWCheckbox *confirmationCheckbox; -@property (nullable, nonatomic, copy) NSString *username; - @end NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.m b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.m similarity index 77% rename from DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.m rename to DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.m index 454138df6..89c11f516 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.m +++ b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.m @@ -15,19 +15,19 @@ // limitations under the License. // -#import "DWConfirmUsernameContentView.h" +#import "DWConfirmInvitationContentView.h" #import "DWCheckbox.h" #import "DWDashPayConstants.h" #import "DWUIKit.h" -#import "dashwallet-Swift.h" +#import "NSAttributedString+DWBuilder.h" #import NS_ASSUME_NONNULL_BEGIN static CGSize const DashSymbolMainSize = {35.0, 27.0}; -@interface DWConfirmUsernameContentView () +@interface DWConfirmInvitationContentView () @property (strong, nonatomic) IBOutlet UIView *contentView; @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @@ -40,7 +40,7 @@ @interface DWConfirmUsernameContentView () NS_ASSUME_NONNULL_END -@implementation DWConfirmUsernameContentView +@implementation DWConfirmInvitationContentView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; @@ -72,13 +72,13 @@ - (void)commonInit { self.backgroundColor = [UIColor dw_backgroundColor]; - self.titleLabel.text = NSLocalizedString(@"Upgrade Fee", nil); + self.titleLabel.text = NSLocalizedString(@"Invitation Fee", nil); // These two labels doesn't support Dynamic Type and have same hardcoded values as in DWAmountInputControl self.mainAmountLabel.font = [UIFont dw_regularFontOfSize:34.0]; self.supplementaryAmountLabel.font = [UIFont dw_regularFontOfSize:16.0]; - const uint64_t amount = DWDP_MIN_BALANCE_TO_CREATE_USERNAME; + const uint64_t amount = DWDP_MIN_BALANCE_TO_CREATE_INVITE; self.mainAmountLabel.attributedText = [self mainAmountAttributedStringForAmount:amount]; self.supplementaryAmountLabel.text = [self supplementaryAmountStringForAmount:amount]; @@ -90,16 +90,12 @@ - (void)commonInit { selector:@selector(contentSizeCategoryDidChangeNotification) name:UIContentSizeCategoryDidChangeNotification object:nil]; -} - -- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection { - [super traitCollectionDidChange:previousTraitCollection]; [self reloadAttributedData]; } -- (void)setUsername:(NSString *)username { - _username = username; +- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; [self reloadAttributedData]; } @@ -107,29 +103,15 @@ - (void)setUsername:(NSString *)username { #pragma mark - Private - (void)reloadAttributedData { - if (self.username.length == 0) { - self.descriptionLabel.attributedText = nil; - return; - } - UIColor *color = [UIColor dw_secondaryTextColor]; - NSString *format = NSLocalizedString(@"You have chosen \"%@\" as your username. Your username cannot be changed once registered.", nil); - NSString *text = [NSString stringWithFormat:format, self.username]; + NSString *text = NSLocalizedString(@"Each invitation will be funded with this amount so that the receiver can quickly create their username on the Dash Network", nil); NSDictionary *attributes = @{ NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote], NSForegroundColorAttributeName : color, }; - NSRange usernameRange = [text rangeOfString:self.username]; - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:text attributes:attributes]; - [result beginEditing]; - [result removeAttribute:NSFontAttributeName range:usernameRange]; - [result addAttribute:NSFontAttributeName value:[UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline] range:usernameRange]; - [result endEditing]; - - self.descriptionLabel.attributedText = result; + self.descriptionLabel.attributedText = [[NSAttributedString alloc] initWithString:text attributes:attributes]; } #pragma mark - Notifications @@ -147,7 +129,8 @@ - (NSAttributedString *)mainAmountAttributedStringForAmount:(uint64_t)amount { } - (NSString *)supplementaryAmountStringForAmount:(uint64_t)amount { - NSString *supplementaryAmount = [CurrencyExchangerObjcWrapper localCurrencyStringForDashAmount:amount]; + DSPriceManager *priceManager = [DSPriceManager sharedInstance]; + NSString *supplementaryAmount = [priceManager localCurrencyStringForDashAmount:amount]; return supplementaryAmount; } diff --git a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.xib b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.xib similarity index 95% rename from DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.xib rename to DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.xib index 057a09586..cb1abd864 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.xib +++ b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.xib @@ -1,15 +1,16 @@ - + - + + - + @@ -67,9 +68,10 @@ - + + @@ -90,7 +92,6 @@ - @@ -101,5 +102,8 @@ + + + diff --git a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/DWConfirmUsernameViewController.h b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationViewController.h similarity index 58% rename from DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/DWConfirmUsernameViewController.h rename to DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationViewController.h index 2a1dd2fb4..be4f75204 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/DWConfirmUsernameViewController.h +++ b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationViewController.h @@ -19,21 +19,20 @@ NS_ASSUME_NONNULL_BEGIN -@class DWConfirmUsernameViewController; +@class DWConfirmInvitationViewController; +@class DSBlockchainInvitation; -@protocol DWConfirmUsernameViewControllerDelegate +@protocol DWConfirmInvitationViewControllerDelegate -- (void)confirmUsernameViewControllerDidConfirm:(DWConfirmUsernameViewController *)controller; +- (void)confirmInvitationViewController:(DWConfirmInvitationViewController *)controller + didConfirmWithInvitation:(DSBlockchainInvitation *)invitation + link:(NSString *)link; @end -@interface DWConfirmUsernameViewController : DWBaseModalViewController +@interface DWConfirmInvitationViewController : DWBaseModalViewController -@property (readonly, nonatomic, copy) NSString *username; - -@property (nullable, nonatomic, weak) id delegate; - -- (instancetype)initWithUsername:(NSString *)username; +@property (nullable, nonatomic, weak) id delegate; @end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationViewController.m b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationViewController.m new file mode 100644 index 000000000..e83cbe54a --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationViewController.m @@ -0,0 +1,126 @@ +// +// 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 "DWConfirmInvitationViewController.h" + +#import "DWConfirmInvitationContentView.h" +#import "DWDashPayConstants.h" +#import "DWEnvironment.h" +#import "UIViewController+DWDisplayError.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWConfirmInvitationViewController () + +@property (nonatomic, strong) DWConfirmInvitationContentView *confirmView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWConfirmInvitationViewController + ++ (BOOL)isActionButtonInNavigationBar { + return NO; +} + +- (NSString *)actionButtonTitle { + return NSLocalizedString(@"Confirm", nil); +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self setModalTitle:NSLocalizedString(@"Confirm", nil)]; + + self.actionButton.enabled = NO; + + self.confirmView = [[DWConfirmInvitationContentView alloc] initWithFrame:CGRectZero]; + [self.confirmView.confirmationCheckbox addTarget:self + action:@selector(confirmationCheckboxAction:) + forControlEvents:UIControlEventValueChanged]; + + [self setupModalContentView:self.confirmView]; +} + +- (void)actionButtonAction:(id)sender { + self.actionButton.enabled = NO; + + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSAccount *account = [DWEnvironment sharedInstance].currentAccount; + DSBlockchainInvitation *invitation = [wallet createBlockchainInvitation]; + invitation.name = [NSString stringWithFormat:NSLocalizedString(@"Invitation %ld", @"Invitation #3"), wallet.blockchainInvitations.count + 1]; + + + DSBlockchainIdentityRegistrationStep steps = DSBlockchainIdentityRegistrationStep_L1Steps; + [invitation generateBlockchainInvitationsExtendedPublicKeysWithPrompt:NSLocalizedString(@"Create invitation", nil) + completion:^(BOOL registered) { + [invitation.identity createFundingPrivateKeyForInvitationWithPrompt:NSLocalizedString(@"Create invitation", nil) + completion:^(BOOL success, BOOL cancelled) { + if (success && !cancelled) { + [invitation.identity + registerOnNetwork:steps + withFundingAccount:account + forTopupAmount:DWDP_MIN_BALANCE_TO_CREATE_INVITE + stepCompletion:^(DSBlockchainIdentityRegistrationStep stepCompleted) { + } + completion:^(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *_Nonnull error) { + if (error) { + [self dw_displayErrorModally:error]; + } + else { + [self generateLinkForInvitationAndFinish:invitation]; + } + }]; + } + }]; + }]; +} + +- (void)generateLinkForInvitationAndFinish:(DSBlockchainInvitation *)invitation { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + + __weak typeof(self) weakSelf = self; + [invitation + createInvitationFullLinkFromIdentity:myBlockchainIdentity + completion:^(BOOL cancelled, NSString *_Nonnull invitationFullLink) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + // skip? + if (cancelled == NO && invitationFullLink == nil) { + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + strongSelf.actionButton.enabled = cancelled; + + if (!cancelled) { + [strongSelf.delegate confirmInvitationViewController:strongSelf didConfirmWithInvitation:invitation link:invitationFullLink]; + } + }); + }]; +} + +- (void)confirmationCheckboxAction:(DWCheckbox *)sender { + self.actionButton.enabled = sender.isOn; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFirstStepViewController.h b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFirstStepViewController.h new file mode 100644 index 000000000..d33206640 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFirstStepViewController.h @@ -0,0 +1,36 @@ +// +// 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 DWSendInviteFirstStepViewController; + +@protocol DWSendInviteFirstStepViewControllerDelegate + +- (void)sendInviteFirstStepViewControllerNewInviteAction:(DWSendInviteFirstStepViewController *)controller; + +@end + +@interface DWSendInviteFirstStepViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFirstStepViewController.m b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFirstStepViewController.m new file mode 100644 index 000000000..187f2e6df --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFirstStepViewController.m @@ -0,0 +1,100 @@ +// +// 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 "DWSendInviteFirstStepViewController.h" + +#import "DWActionButton.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWSendInviteFirstStepViewController () + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWSendInviteFirstStepViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = NSLocalizedString(@"Invite", nil); + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UIImageView *imageView = [[UIImageView alloc] init]; + imageView.image = [UIImage imageNamed:@"invite_logo"]; + imageView.contentMode = UIViewContentModeCenter; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleTitle3]; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.numberOfLines = 0; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.text = NSLocalizedString(@"Invite your friends & family", nil); + titleLabel.adjustsFontForContentSizeCategory = YES; + + UILabel *descLabel = [[UILabel alloc] init]; + descLabel.translatesAutoresizingMaskIntoConstraints = NO; + descLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + descLabel.textColor = [UIColor dw_darkTitleColor]; + descLabel.numberOfLines = 0; + descLabel.textAlignment = NSTextAlignmentCenter; + descLabel.text = NSLocalizedString(@"Let your friends and family to join the Dash Network. Invite them to the world of social banking.", nil); + descLabel.adjustsFontForContentSizeCategory = YES; + + UIStackView *centerStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + imageView, titleLabel, descLabel + ]]; + centerStackView.translatesAutoresizingMaskIntoConstraints = NO; + centerStackView.axis = UILayoutConstraintAxisVertical; + centerStackView.spacing = 12.0; + centerStackView.alignment = UIStackViewAlignmentCenter; + [centerStackView setCustomSpacing:40 afterView:imageView]; + [self.view addSubview:centerStackView]; + + DWActionButton *inviteButton = [[DWActionButton alloc] init]; + inviteButton.translatesAutoresizingMaskIntoConstraints = NO; + [inviteButton setTitle:NSLocalizedString(@"Create a new Invitation", nil) forState:UIControlStateNormal]; + [inviteButton addTarget:self + action:@selector(inviteButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:inviteButton]; + + UILayoutGuide *guide = self.view.layoutMarginsGuide; + NSLayoutConstraint *centerConstraint = [centerStackView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor]; + centerConstraint.priority = UILayoutPriorityRequired - 1; + [NSLayoutConstraint activateConstraints:@[ + [centerStackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:centerStackView.trailingAnchor], + centerConstraint, + + [inviteButton.topAnchor constraintGreaterThanOrEqualToAnchor:centerStackView.bottomAnchor], + + [guide.bottomAnchor constraintEqualToAnchor:inviteButton.bottomAnchor], + [inviteButton.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:inviteButton.trailingAnchor], + [inviteButton.heightAnchor constraintEqualToConstant:50], + ]]; +} + +- (void)inviteButtonAction { + [self.delegate sendInviteFirstStepViewControllerNewInviteAction:self]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.h b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.h new file mode 100644 index 000000000..19c08a205 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.h @@ -0,0 +1,36 @@ +// +// 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 DWSendInviteFlowController; + +@protocol DWSendInviteFlowControllerDelegate + +- (void)sendInviteFlowControllerDidFinish:(DWSendInviteFlowController *)controller; + +@end + +@interface DWSendInviteFlowController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.m b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.m new file mode 100644 index 000000000..409fcdb0b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.m @@ -0,0 +1,110 @@ +// +// 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 "DWSendInviteFlowController.h" + +#import "DPAlertViewController+DWInvite.h" +#import "DWConfirmInvitationViewController.h" +#import "DWFullScreenModalControllerViewController.h" +#import "DWNavigationController.h" +#import "DWSendInviteFirstStepViewController.h" + +#import "DWUIKit.h" +#import "dashwallet-Swift.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWSendInviteFlowController () < + DWSendInviteFirstStepViewControllerDelegate, + DWConfirmInvitationViewControllerDelegate, + DWFullScreenModalControllerViewControllerDelegate, + SuccessInvitationViewControllerDelegate> + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWSendInviteFlowController + +- (void)viewDidLoad { + [super viewDidLoad]; + + DWSendInviteFirstStepViewController *controller = [[DWSendInviteFirstStepViewController alloc] init]; + controller.delegate = self; + controller.navigationItem.leftBarButtonItem = [self cancelBarButton]; + DWNavigationController *navigation = [[DWNavigationController alloc] initWithRootViewController:controller]; + [self dw_embedChild:navigation]; +} + +- (UIBarButtonItem *)cancelBarButton { + return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelButtonAction)]; +} + +- (void)cancelButtonAction { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)showSuccessInvitation:(DSBlockchainInvitation *)invitation fullLink:(NSString *)fullLink { + SuccessInvitationViewController *invitationController = + [[SuccessInvitationViewController alloc] initWith:invitation fullLink:fullLink index:0]; + invitationController.delegate = self; + DWFullScreenModalControllerViewController *modal = + [[DWFullScreenModalControllerViewController alloc] initWithController:invitationController]; + modal.delegate = self; + modal.title = NSLocalizedString(@"Invitation", nil); + modal.modalPresentationStyle = UIModalPresentationFullScreen; + [self presentViewController:modal animated:YES completion:nil]; +} + +#pragma mark - DWSendInviteFirstStepViewControllerDelegate + +- (void)sendInviteFirstStepViewControllerNewInviteAction:(DWSendInviteFirstStepViewController *)controller { + DWConfirmInvitationViewController *confirmationController = [[DWConfirmInvitationViewController alloc] init]; + confirmationController.delegate = self; + [self presentViewController:confirmationController animated:YES completion:nil]; +} + +#pragma mark - DWConfirmInvitationViewControllerDelegate + +- (void)confirmInvitationViewController:(DWConfirmInvitationViewController *)controller + didConfirmWithInvitation:(DSBlockchainInvitation *)invitation + link:(NSString *)link { + [controller dismissViewControllerAnimated:YES + completion:^{ + [self showSuccessInvitation:invitation fullLink:link]; + }]; +} + +#pragma mark - DWFullScreenModalControllerViewControllerDelegate + +- (void)fullScreenModalControllerViewControllerDidCancel:(DWFullScreenModalControllerViewController *)controller { + [controller dismissViewControllerAnimated:YES + completion:^{ + [self.delegate sendInviteFlowControllerDidFinish:self]; + }]; +} + +#pragma mark - DWSuccessInvitationViewControllerDelegate + +- (void)successInvitationViewControllerDidSelectLaterWithController:(SuccessInvitationViewController *)controller { + [controller dismissViewControllerAnimated:YES + completion:^{ + [self.delegate sendInviteFlowControllerDidFinish:self]; + }]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/DWInvitationHistoryViewController.h b/DashWallet/Sources/UI/DashPay/Invites/History/DWInvitationHistoryViewController.h new file mode 100644 index 000000000..5f534094f --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/DWInvitationHistoryViewController.h @@ -0,0 +1,26 @@ +// +// 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 + +@interface DWInvitationHistoryViewController : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/DWInvitationHistoryViewController.m b/DashWallet/Sources/UI/DashPay/Invites/History/DWInvitationHistoryViewController.m new file mode 100644 index 000000000..d315c6aa9 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/DWInvitationHistoryViewController.m @@ -0,0 +1,193 @@ +// +// 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 "DWInvitationHistoryViewController.h" + +#import "DWEnvironment.h" +#import "DWHistoryFilterViewController.h" +#import "DWHistoryHeaderView.h" +#import "DWInvitationHistoryModel.h" +#import "DWInvitationTableViewCell.h" +#import "DWSendInviteFlowController.h" +#import "dashwallet-Swift.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWInvitationHistoryViewController () + +@property (nonatomic, strong) DWInvitationHistoryModel *model; +@property (null_resettable, nonatomic, strong) UITableView *tableView; + +@end + +@interface DWNonFloatingTableView : UITableView + +@end + +@implementation DWNonFloatingTableView + +- (BOOL)allowsHeaderViewsToFloat { + return NO; +} + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWInvitationHistoryViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = NSLocalizedString(@"Invite", nil); + + self.model = [[DWInvitationHistoryModel alloc] init]; + self.model.delegate = self; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + [self.view addSubview:self.tableView]; + [NSLayoutConstraint dw_activate:@[ + [self.tableView pinEdges:self.view], + ]]; +} + +- (void)viewWillAppear:(BOOL)animated{ + [super viewWillAppear:animated]; + + [_tableView reloadData]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (void)createInvitationAction:(UIControl *)sender { + DWSendInviteFlowController *controller = [[DWSendInviteFlowController alloc] init]; + controller.delegate = self; + [self presentViewController:controller animated:YES completion:nil]; +} + +- (void)optionsButtonAction:(UIControl *)sender { + DWHistoryFilterViewController *controller = [[DWHistoryFilterViewController alloc] init]; + controller.delegate = self; + [self presentViewController:controller animated:YES completion:nil]; +} + +- (UITableView *)tableView { + if (_tableView == nil) { + UITableView *tableView = [[DWNonFloatingTableView alloc] initWithFrame:[UIScreen mainScreen].bounds + style:UITableViewStylePlain]; + tableView.translatesAutoresizingMaskIntoConstraints = NO; + tableView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + tableView.delegate = self; + tableView.dataSource = self; + tableView.rowHeight = UITableViewAutomaticDimension; + tableView.estimatedRowHeight = 74.0; + tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + tableView.sectionHeaderHeight = UITableViewAutomaticDimension; + tableView.estimatedSectionHeaderHeight = 100.0; + [tableView registerClass:DWInvitationTableViewCell.class + forCellReuseIdentifier:DWInvitationTableViewCell.dw_reuseIdentifier]; + _tableView = tableView; + } + return _tableView; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.model.items.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + DWInvitationTableViewCell *cell = (DWInvitationTableViewCell *) + [tableView dequeueReusableCellWithIdentifier:DWInvitationTableViewCell.dw_reuseIdentifier + forIndexPath:indexPath]; + id item = self.model.items[indexPath.row]; + cell.item = item; + return cell; +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + DWHistoryHeaderView *header = [[DWHistoryHeaderView alloc] init]; + [header.createButton addTarget:self action:@selector(createInvitationAction:) forControlEvents:UIControlEventTouchUpInside]; + [header.optionsButton addTarget:self action:@selector(optionsButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + return header; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + + id item = self.model.items[indexPath.row]; + NSUInteger index = self.model.items.count - indexPath.row; + + __weak typeof(self) weakSelf = self; + [item.blockchainInvitation + createInvitationFullLinkFromIdentity:myBlockchainIdentity + completion:^(BOOL cancelled, NSString *_Nonnull invitationFullLink) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + // skip? + if (cancelled == NO && invitationFullLink == nil) { + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + BaseInvitationViewController *controller = + [[BaseInvitationViewController alloc] initWith:item.blockchainInvitation + fullLink:invitationFullLink + index:index]; + controller.title = NSLocalizedString(@"Invite", nil); + controller.hidesBottomBarWhenPushed = YES; + controller.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + [self.navigationController pushViewController:controller animated:YES]; + }); + }]; +} + +#pragma mark - DWSendInviteFlowControllerDelegate + +- (void)sendInviteFlowControllerDidFinish:(DWSendInviteFlowController *)controller { + [controller dismissViewControllerAnimated:YES completion:nil]; + [self.model reload]; +} + +#pragma mark - DWInvitationHistoryModelDelegate + +- (void)invitationHistoryModelDidUpdate:(DWInvitationHistoryModel *)model { + [self.tableView reloadData]; +} + +#pragma mark - DWHistoryFilterViewControllerDelegate + +- (void)historyFilterViewController:(DWHistoryFilterViewController *)controller + didSelectFilter:(DWInvitationHistoryFilter)filter { + [controller dismissViewControllerAnimated:YES completion:nil]; + self.model.filter = filter; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.h b/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterContentView.h similarity index 60% rename from DashWallet/Sources/UI/DashPay/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.h rename to DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterContentView.h index 490104b0d..9b50c5b9e 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.h +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterContentView.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -17,18 +17,21 @@ #import +#import "DWInvitationHistoryFilter.h" + NS_ASSUME_NONNULL_BEGIN -@protocol DWRegistrationCompletedViewControllerDelegate +@class DWHistoryFilterContentView; + +@protocol DWHistoryFilterContentViewDelegate -- (void)registrationCompletedViewControllerAction:(UIViewController *)controller; +- (void)historyFilterView:(DWHistoryFilterContentView *)view didSelectFilter:(DWInvitationHistoryFilter)filter; @end -@interface DWRegistrationCompletedViewController : UIViewController +@interface DWHistoryFilterContentView : UIView -@property (nullable, nonatomic, weak) id delegate; -@property (nonatomic, copy) NSString *username; +@property (nullable, nonatomic, weak) id delegate; @end diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterContentView.m b/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterContentView.m new file mode 100644 index 000000000..834689266 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterContentView.m @@ -0,0 +1,94 @@ +// +// 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 "DWHistoryFilterContentView.h" + +#import "DWPressableButton.h" +#import "DWUIKit.h" + +@implementation DWHistoryFilterContentView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + self.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner; + self.layer.cornerRadius = 8.0; + self.layer.masksToBounds = YES; + + UIButton *allButton = [self.class button]; + allButton.tag = DWInvitationHistoryFilter_All; + [allButton setTitle:@"All" forState:UIControlStateNormal]; + [allButton addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside]; + + UIButton *pendingButton = [self.class button]; + pendingButton.tag = DWInvitationHistoryFilter_Pending; + [pendingButton setTitle:NSLocalizedString(@"Pending", nil) forState:UIControlStateNormal]; + [pendingButton addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside]; + + UIButton *claimedButton = [self.class button]; + claimedButton.tag = DWInvitationHistoryFilter_Claimed; + [claimedButton setTitle:NSLocalizedString(@"Claimed", nil) forState:UIControlStateNormal]; + [claimedButton addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside]; + + UIView *separator1 = [[UIView alloc] init]; + separator1.translatesAutoresizingMaskIntoConstraints = NO; + separator1.backgroundColor = [UIColor dw_separatorLineColor]; + + UIView *separator2 = [[UIView alloc] init]; + separator2.translatesAutoresizingMaskIntoConstraints = NO; + separator2.backgroundColor = [UIColor dw_separatorLineColor]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ allButton, separator1, pendingButton, separator2, claimedButton ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisVertical; + [self addSubview:stackView]; + + const CGFloat padding = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [stackView.topAnchor constraintEqualToAnchor:self.topAnchor], + [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor + constant:padding], + [self.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor], + + [claimedButton.heightAnchor constraintGreaterThanOrEqualToConstant:80], + [allButton.heightAnchor constraintGreaterThanOrEqualToConstant:80], + [pendingButton.heightAnchor constraintGreaterThanOrEqualToConstant:80], + + [separator1.heightAnchor constraintEqualToConstant:1], + [separator2.heightAnchor constraintEqualToConstant:1], + ]]; + } + return self; +} + +- (void)buttonAction:(UIButton *)sender { + [self.delegate historyFilterView:self didSelectFilter:sender.tag]; +} + ++ (UIButton *)button { + DWPressableButton *button = [[DWPressableButton alloc] initWithFrame:CGRectZero]; + button.translatesAutoresizingMaskIntoConstraints = NO; + button.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; + button.adjustsImageWhenHighlighted = NO; + [button setTitleColor:[UIColor dw_darkTitleColor] forState:UIControlStateNormal]; + return button; +} +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterViewController.h b/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterViewController.h new file mode 100644 index 000000000..377cfea45 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterViewController.h @@ -0,0 +1,39 @@ +// +// 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 "DWInvitationHistoryFilter.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWHistoryFilterViewController; + +@protocol DWHistoryFilterViewControllerDelegate + +- (void)historyFilterViewController:(DWHistoryFilterViewController *)controller + didSelectFilter:(DWInvitationHistoryFilter)filter; + +@end + +@interface DWHistoryFilterViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterViewController.m b/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterViewController.m new file mode 100644 index 000000000..f9c63a6a6 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Filter/DWHistoryFilterViewController.m @@ -0,0 +1,91 @@ +// +// 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 "DWHistoryFilterViewController.h" + +#import "DWHistoryFilterContentView.h" +#import "DWModalPopupTransition.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWHistoryFilterViewController () + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWHistoryFilterViewController + +- (instancetype)init { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _modalTransition = [[DWModalPopupTransition alloc] init]; + _modalTransition.appearanceStyle = DWModalPopupAppearanceStyle_Fullscreen; + + self.transitioningDelegate = self.modalTransition; + self.modalPresentationStyle = UIModalPresentationCustom; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognizerAction)]; + [self.view addGestureRecognizer:tapRecognizer]; + + DWHistoryFilterContentView *contentView = [[DWHistoryFilterContentView alloc] initWithFrame:CGRectZero]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.delegate = self; + [self.view addSubview:contentView]; + + UIView *overscroll = [[UIView alloc] init]; + overscroll.translatesAutoresizingMaskIntoConstraints = NO; + overscroll.backgroundColor = [UIColor dw_backgroundColor]; + [self.view addSubview:overscroll]; + + [NSLayoutConstraint activateConstraints:@[ + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + + [overscroll.topAnchor constraintEqualToAnchor:contentView.bottomAnchor + constant:-10], + [overscroll.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:overscroll.trailingAnchor], + [overscroll.heightAnchor constraintEqualToConstant:500], + ]]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (void)tapRecognizerAction { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - DWHistoryFilterContentViewDelegate + +- (void)historyFilterView:(DWHistoryFilterContentView *)view didSelectFilter:(DWInvitationHistoryFilter)filter { + [self.delegate historyFilterViewController:self didSelectFilter:filter]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryFilter.h b/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryFilter.h new file mode 100644 index 000000000..f0b37553c --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryFilter.h @@ -0,0 +1,27 @@ +// +// 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. +// + +#ifndef DWInvitationHistoryFilter_h +#define DWInvitationHistoryFilter_h + +typedef NS_ENUM(NSUInteger, DWInvitationHistoryFilter) { + DWInvitationHistoryFilter_All, + DWInvitationHistoryFilter_Pending, + DWInvitationHistoryFilter_Claimed, +}; + +#endif /* DWInvitationHistoryFilter_h */ diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryModel.h b/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryModel.h new file mode 100644 index 000000000..04b09ae70 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryModel.h @@ -0,0 +1,56 @@ +// +// 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 "DWInvitationHistoryFilter.h" +#import "DWInvitationItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSBlockchainInvitation; + +@protocol DWInvitationHistoryItem + +@property (readonly, nonatomic, strong) DSBlockchainInvitation *blockchainInvitation; +@property (readonly, nonatomic, strong) NSString *tag; + +@end + +// + +@class DWInvitationHistoryModel; + +@protocol DWInvitationHistoryModelDelegate + +- (void)invitationHistoryModelDidUpdate:(DWInvitationHistoryModel *)model; + +@end + +// + +@interface DWInvitationHistoryModel : NSObject + +@property (nonatomic, assign) DWInvitationHistoryFilter filter; +@property (readonly, nonatomic, copy) NSArray> *items; +@property (nullable, nonatomic, weak) id delegate; + +- (void)reload; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryModel.m b/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryModel.m new file mode 100644 index 000000000..319f5528f --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationHistoryModel.m @@ -0,0 +1,157 @@ +// +// 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 "DWInvitationHistoryModel.h" + +#import "DWDateFormatter.h" +#import "DWEnvironment.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Item Header + +@interface DWInvitationHistoryItemImpl : NSObject + +@property (readonly, nonatomic, assign) NSUInteger index; + +@end + +#pragma mark - Model Header + +@interface DWInvitationHistoryModel () + +@property (nonatomic, copy) NSArray> *items; + +@end + +NS_ASSUME_NONNULL_END + +#pragma mark - Item Impl + +@implementation DWInvitationHistoryItemImpl + +@synthesize blockchainInvitation = _blockchainInvitation; +@synthesize tag = _tag; + +- (instancetype)initWithInvitation:(DSBlockchainInvitation *)invitation index:(NSUInteger)index { + self = [super init]; + if (self) { + _blockchainInvitation = invitation; + _index = index; + } + return self; +} + +- (NSString *)tag { + return _blockchainInvitation.tag; +} + +- (BOOL)isRegistered { + return self.blockchainInvitation.identity.isRegistered; +} + +- (NSString *)title { + NSString *name = _blockchainInvitation.name; + NSString *tag = [self.tag isEqualToString:@""] ? nil : self.tag; + + return (tag ? tag : (name ? name : [NSString stringWithFormat:NSLocalizedString(@"Invitation %ld", @"Invitation #3"), self.index])); +} + +- (NSString *)subtitle { + DSTransaction *transaction = self.blockchainInvitation.identity.registrationCreditFundingTransaction; + DSChain *chain = [DWEnvironment sharedInstance].currentChain; + NSTimeInterval now = [chain timestampForBlockHeight:TX_UNCONFIRMED]; + NSTimeInterval txTime = (transaction.timestamp > 1) ? transaction.timestamp : now; + NSDate *txDate = [NSDate dateWithTimeIntervalSince1970:txTime]; + return [[DWDateFormatter sharedInstance] shortStringFromDate:txDate]; +} + +@end + +#pragma mark - Model Impl + +@implementation DWInvitationHistoryModel + +- (instancetype)init { + self = [super init]; + if (self) { + _items = @[]; + _filter = DWInvitationHistoryFilter_All; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(blockchainIdentityDidUpdateNotification) + name:DSBlockchainIdentityDidUpdateNotification + object:nil]; + + [self reload]; + } + return self; +} + +- (void)setFilter:(DWInvitationHistoryFilter)filter { + _filter = filter; + + [self reload]; +} + +- (void)reload { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + NSArray *descriptors = @[ + [NSSortDescriptor sortDescriptorWithKey:@"identity.registrationCreditFundingTransaction.blockHeight" + ascending:NO], + [NSSortDescriptor sortDescriptorWithKey:@"identity.registrationCreditFundingTransaction.timestamp" + ascending:NO], + ]; + NSArray *invitations = [wallet.blockchainInvitations.allValues + sortedArrayUsingDescriptors:descriptors]; + NSUInteger index = invitations.count; + NSMutableArray *mutableItems = [NSMutableArray arrayWithCapacity:invitations.count]; + for (DSBlockchainInvitation *invitation in invitations) { + BOOL shouldInclude = NO; + switch (self.filter) { + case DWInvitationHistoryFilter_All: + shouldInclude = YES; + break; + case DWInvitationHistoryFilter_Pending: + shouldInclude = invitation.identity.registrationStatus == DSBlockchainIdentityRegistrationStatus_Unknown || + invitation.identity.registrationStatus == DSBlockchainIdentityRegistrationStatus_NotRegistered; + break; + case DWInvitationHistoryFilter_Claimed: + shouldInclude = invitation.identity.registrationStatus == DSBlockchainIdentityRegistrationStatus_Registering || + invitation.identity.registrationStatus == DSBlockchainIdentityRegistrationStatus_Registered; + break; + } + + if (shouldInclude) { + DWInvitationHistoryItemImpl *item = + [[DWInvitationHistoryItemImpl alloc] initWithInvitation:invitation + index:index]; + [mutableItems addObject:item]; + + index -= 1; + } + } + self.items = mutableItems; + + [self.delegate invitationHistoryModelDidUpdate:self]; +} + +- (void)blockchainIdentityDidUpdateNotification { + [self reload]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/DWDashPayConstants.h b/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationItem.h similarity index 77% rename from DashWallet/Sources/UI/DashPay/DWDashPayConstants.h rename to DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationItem.h index 99b2015d9..cacf06687 100644 --- a/DashWallet/Sources/UI/DashPay/DWDashPayConstants.h +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Models/DWInvitationItem.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -19,8 +19,12 @@ NS_ASSUME_NONNULL_BEGIN -extern uint64_t DWDP_MIN_BALANCE_TO_CREATE_USERNAME; -extern NSInteger DW_MIN_USERNAME_LENGTH; -extern NSInteger DW_MAX_USERNAME_LENGTH; +@protocol DWInvitationItem + +- (BOOL)isRegistered; +- (NSString *)title; +- (NSString *)subtitle; + +@end NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.h b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.h new file mode 100644 index 000000000..95299d5f4 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.h @@ -0,0 +1,26 @@ +// +// 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 "DWBasePressableControl.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWCreateInvitationButton : DWBasePressableControl + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.m b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.m new file mode 100644 index 000000000..1c2493422 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.m @@ -0,0 +1,98 @@ +// +// 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 "DWCreateInvitationButton.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWCreateInvitationButton () +@end + +NS_ASSUME_NONNULL_END + +@implementation DWCreateInvitationButton + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = [UIColor dw_backgroundColor]; + contentView.layer.cornerRadius = 8; + contentView.layer.masksToBounds = YES; + contentView.userInteractionEnabled = NO; + [self addSubview:contentView]; + + UIImageView *icon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_create_invitation"]]; + icon.translatesAutoresizingMaskIntoConstraints = NO; + icon.userInteractionEnabled = NO; + [contentView addSubview:icon]; + + UILabel *title = [[UILabel alloc] init]; + title.translatesAutoresizingMaskIntoConstraints = NO; + title.textColor = [UIColor dw_darkTitleColor]; + title.numberOfLines = 0; + title.adjustsFontForContentSizeCategory = YES; + title.userInteractionEnabled = NO; + [contentView addSubview:title]; + + NSMutableAttributedString *text = [[NSMutableAttributedString alloc] init]; + [text appendAttributedString: + [[NSAttributedString alloc] initWithString: + NSLocalizedString(@"Create a new invitation", nil) + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote], + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + }]]; + [text appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; + [text appendAttributedString: + [[NSAttributedString alloc] initWithString: + NSLocalizedString(@"Invite your friends and family to join the Dash Network.", nil) + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleCaption1], + NSForegroundColorAttributeName : [UIColor dw_tertiaryTextColor], + }]]; + title.attributedText = text; + + [title setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [contentView setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + + CGFloat const padding = 20; + [NSLayoutConstraint dw_activate:@[ + [contentView pinEdges:self], + + [icon.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:padding], + [icon.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor], + [icon pinSize:CGSizeMake(37, 37)], + + [title.leadingAnchor constraintEqualToAnchor:icon.trailingAnchor + constant:padding], + [title.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:padding], + [contentView.trailingAnchor constraintEqualToAnchor:title.trailingAnchor + constant:padding], + [contentView.bottomAnchor constraintEqualToAnchor:title.bottomAnchor + constant:padding], + ]]; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWHistoryHeaderView.h b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWHistoryHeaderView.h new file mode 100644 index 000000000..d5164b004 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWHistoryHeaderView.h @@ -0,0 +1,29 @@ +// +// 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 + +@interface DWHistoryHeaderView : UIView + +@property (readonly, nonatomic, strong) UIControl *createButton; +@property (readonly, nonatomic, strong) UIButton *optionsButton; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWHistoryHeaderView.m b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWHistoryHeaderView.m new file mode 100644 index 000000000..a84779e69 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWHistoryHeaderView.m @@ -0,0 +1,84 @@ +// +// 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 "DWHistoryHeaderView.h" + +#import "DWCreateInvitationButton.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWHistoryHeaderView () + +@property (readonly, nonatomic, strong) DWCreateInvitationButton *createInvitationButton; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWHistoryHeaderView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + DWCreateInvitationButton *createButton = [[DWCreateInvitationButton alloc] init]; + createButton.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:createButton]; + _createButton = createButton; + + UILabel *header = [[UILabel alloc] init]; + header.translatesAutoresizingMaskIntoConstraints = NO; + header.numberOfLines = 0; + header.adjustsFontForContentSizeCategory = YES; + header.textColor = [UIColor dw_darkTitleColor]; + header.text = NSLocalizedString(@"Invitations History", nil); + header.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + [self addSubview:header]; + + UIButton *optionsButton = [UIButton buttonWithType:UIButtonTypeSystem]; + optionsButton.translatesAutoresizingMaskIntoConstraints = NO; + [optionsButton setImage:[UIImage imageNamed:@"icon_options"] forState:UIControlStateNormal]; + optionsButton.tintColor = [UIColor dw_dashBlueColor]; + [self addSubview:optionsButton]; + _optionsButton = optionsButton; + + CGFloat const padding = 16; + UIEdgeInsets const insets = UIEdgeInsetsMake(padding, padding, padding, padding); + [NSLayoutConstraint dw_activate:@[ + [createButton pinEdges:self + insets:insets + except:DWAnchorBottom], + + [header.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [header.topAnchor constraintEqualToAnchor:createButton.bottomAnchor + constant:20], + [self.bottomAnchor constraintEqualToAnchor:header.bottomAnchor + constant:20], + + [optionsButton.leadingAnchor constraintEqualToAnchor:header.trailingAnchor + constant:20], + [self.trailingAnchor constraintEqualToAnchor:optionsButton.trailingAnchor + constant:5], + [optionsButton.centerYAnchor constraintEqualToAnchor:header.centerYAnchor], + [optionsButton pinSize:CGSizeMake(44, 44)], + ]]; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWInvitationTableViewCell.h b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWInvitationTableViewCell.h new file mode 100644 index 000000000..80243673e --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWInvitationTableViewCell.h @@ -0,0 +1,30 @@ +// +// 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 "DWInvitationItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWInvitationTableViewCell : UITableViewCell + +@property (nullable, nonatomic, strong) id item; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWInvitationTableViewCell.m b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWInvitationTableViewCell.m new file mode 100644 index 000000000..30c3d2293 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWInvitationTableViewCell.m @@ -0,0 +1,126 @@ +// +// 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 "DWInvitationTableViewCell.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWInvitationTableViewCell () + +@property (readonly, nonatomic, strong) UIImageView *iconImageView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) UILabel *subtitleLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWInvitationTableViewCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + self.selectionStyle = UITableViewCellSelectionStyleNone; + + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.contentView.backgroundColor = self.backgroundColor; + + UIImageView *icon = [[UIImageView alloc] init]; + icon.translatesAutoresizingMaskIntoConstraints = NO; + icon.userInteractionEnabled = NO; + icon.contentMode = UIViewContentModeScaleAspectFit; + [self.contentView addSubview:icon]; + _iconImageView = icon; + + UILabel *title = [[UILabel alloc] init]; + title.translatesAutoresizingMaskIntoConstraints = NO; + title.textColor = [UIColor dw_darkTitleColor]; + title.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + title.numberOfLines = 0; + title.adjustsFontForContentSizeCategory = YES; + title.userInteractionEnabled = NO; + [self.contentView addSubview:title]; + _titleLabel = title; + + UILabel *subtitle = [[UILabel alloc] init]; + subtitle.translatesAutoresizingMaskIntoConstraints = NO; + subtitle.textColor = [UIColor dw_tertiaryTextColor]; + subtitle.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCaption1]; + subtitle.numberOfLines = 0; + subtitle.adjustsFontForContentSizeCategory = YES; + subtitle.userInteractionEnabled = NO; + [self.contentView addSubview:subtitle]; + _subtitleLabel = subtitle; + + UIImageView *chevron = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_disclosure_indicator"]]; + chevron.translatesAutoresizingMaskIntoConstraints = NO; + [self.contentView addSubview:chevron]; + + [title setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [subtitle setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + + CGFloat const padding = 20; + UIEdgeInsets const insets = UIEdgeInsetsMake(padding, padding, padding, padding); + [NSLayoutConstraint dw_activate:@[ + [icon.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor + constant:16 + padding], + [icon.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor], + [icon pinSize:CGSizeMake(37, 37)], + + [title.leadingAnchor constraintEqualToAnchor:icon.trailingAnchor + constant:padding], + [title.topAnchor constraintEqualToAnchor:self.contentView.topAnchor + constant:padding], + + [subtitle.topAnchor constraintEqualToAnchor:title.bottomAnchor], + [subtitle.leadingAnchor constraintEqualToAnchor:icon.trailingAnchor + constant:padding], + [self.contentView.bottomAnchor constraintEqualToAnchor:subtitle.bottomAnchor + constant:padding], + + [chevron.leadingAnchor constraintEqualToAnchor:title.trailingAnchor + constant:padding], + [chevron.leadingAnchor constraintEqualToAnchor:subtitle.trailingAnchor + constant:padding], + [self.contentView.trailingAnchor constraintEqualToAnchor:chevron.trailingAnchor + constant:padding], + [chevron pinSize:CGSizeMake(10, 19)], + [chevron.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor], + + ]]; + } + return self; +} + +- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { + [super setHighlighted:highlighted animated:animated]; + + [self.contentView dw_pressedAnimation:DWPressedAnimationStrength_Light pressed:highlighted]; +} + +- (void)setItem:(id)item { + _item = item; + + UIImage *icon = [UIImage imageNamed:item.isRegistered ? @"icon_invitation_read" : @"icon_invitation_unread"]; + self.iconImageView.image = icon; + self.titleLabel.text = item.title; + self.subtitleLabel.text = item.subtitle; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/BaseInvitationViewController.swift b/DashWallet/Sources/UI/DashPay/Invites/Invitation/BaseInvitationViewController.swift new file mode 100644 index 000000000..a315998d0 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/BaseInvitationViewController.swift @@ -0,0 +1,256 @@ +// +// Created by Pavel Tikhonenko +// Copyright © 2022 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class InvitationSourceItem: NSObject, UIActivityItemSource +{ + let url: URL + + init(with url: URL) { + self.url = url + super.init() + } + + func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { + return NSLocalizedString("DashPay Invitation", comment: "") + } + + func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { + return self.url + } +} + + +@objc class BaseInvitationViewController: UIViewController { + internal var topView: BaseInvitationTopView! + internal var actionsView: DWInvitationActionsView! + internal var invitationView: UIView! + internal var buttonsView: UIStackView! + internal var sendButton: DWActionButton! + private var bottomConstraint: NSLayoutConstraint! + + internal let invitation: DSBlockchainInvitation + internal let fullLink: String + internal var invitationURL: URL! + + internal var index: Int + override var preferredStatusBarStyle: UIStatusBarStyle { + .lightContent + } + + @objc public init(with invitation: DSBlockchainInvitation, fullLink: String, index: Int = 0) { + self.invitation = invitation + self.fullLink = fullLink + self.index = index + + super.init(nibName: nil, bundle: nil) + + let wallet = DWEnvironment.sharedInstance().currentWallet + let myBlockchainIdentity: DSBlockchainIdentity = wallet.defaultBlockchainIdentity! + + DWInvitationLinkBuilder.dynamicLink(from: fullLink, myBlockchainIdentity: myBlockchainIdentity, completion: { [weak self] url in + self?.invitationURL = url ?? URL(string: fullLink) + }) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //MARK: Actions + + @objc func sendButtonAction() { + let invitationURL = self.invitationURL! + + let imageSize: CGSize = CGSize(width: 320, height: 440) + let messageView = DWInvitationMessageView(frame: CGRect(x: 0, y: -1000, width: imageSize.width, height: imageSize.height)) + self.view.window?.addSubview(messageView) + + let renderer = UIGraphicsImageRenderer(size: imageSize) + let image = renderer.image { ctx in + messageView.drawHierarchy(in: messageView.bounds, afterScreenUpdates: true) + } + messageView.removeFromSuperview() + + let shareItem = InvitationSourceItem(with: invitationURL) + + let sharingController = UIActivityViewController(activityItems: [ shareItem, image ], applicationActivities: nil) + self.present(sharingController, animated: true) + } + + @objc func previewButtonAction() { + let previewController = DWInvitationPreviewViewController() + self.present(previewController, animated: true) + } + + @objc func profileAction() { + let item = DWDPUserObject(blockchainIdentity: invitation.identity) + let payModel = DWPayModel() + let dataProvider = DWTransactionListDataProviderStub() + + let profileController = DWUserProfileViewController(item: item, payModel: payModel, dataProvider: dataProvider, shouldSkipUpdating: true, shownAfterPayment: false) + self.navigationController?.pushViewController(profileController, animated: true) + } + + //MARK: Hierarchy + + internal func configureInvitationView() { + invitationView = UIView() + invitationView.translatesAutoresizingMaskIntoConstraints = false + + configureTopView() + + if invitation.identity.isRegistered { + topView.previewButton.isHidden = true + }else{ + configureActionsView() + } + } + + internal func configureTopView() { + self.topView = InvitationTopView(index: index) + topView.translatesAutoresizingMaskIntoConstraints = false + topView.layer.cornerRadius = 8.0 + topView.layer.masksToBounds = true + topView.previewButton.addTarget(self, action: #selector(previewButtonAction), for: .touchUpInside) + invitationView.addSubview(topView) + + NSLayoutConstraint.activate([ + topView.heightAnchor.constraint(equalTo: topView.widthAnchor, multiplier: 0.88), + topView.topAnchor.constraint(equalTo: invitationView.topAnchor, constant: 23), + topView.leadingAnchor.constraint(equalTo: invitationView.leadingAnchor), + topView.trailingAnchor.constraint(equalTo: invitationView.trailingAnchor), + ]) + } + + internal func configureActionsView() { + actionsView = DWInvitationActionsView(frame: .zero) + actionsView.translatesAutoresizingMaskIntoConstraints = false + actionsView.delegate = self + invitationView.addSubview(actionsView) + + NSLayoutConstraint.activate([ + actionsView.topAnchor.constraint(equalTo: self.topView.bottomAnchor, constant: 20), + actionsView.leadingAnchor.constraint(equalTo: self.invitationView.leadingAnchor), + actionsView.trailingAnchor.constraint(equalTo: self.invitationView.trailingAnchor), + actionsView.bottomAnchor.constraint(equalTo: self.invitationView.bottomAnchor), + ]) + } + + internal func configureButtonsView() { + buttonsView = UIStackView() + buttonsView.axis = .vertical + buttonsView.spacing = 8 + buttonsView.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(buttonsView) + + if invitation.identity.isRegistered + { + let tap = UITapGestureRecognizer(target: self, action: #selector(profileAction)) + let view = InvitationBottomView(invitation: invitation) + view.addGestureRecognizer(tap) + buttonsView.addArrangedSubview(view) + }else{ + sendButton = DWActionButton() + sendButton.translatesAutoresizingMaskIntoConstraints = false + sendButton.setTitle(NSLocalizedString("Send again", comment: ""), for: .normal) + sendButton.addTarget(self, action: #selector(sendButtonAction), for: .touchUpInside) + buttonsView.addArrangedSubview(sendButton) + + NSLayoutConstraint.activate([ + sendButton.heightAnchor.constraint(equalToConstant: 50) + ]) + } + } + + private func configureBottomView() { + + } + + internal func configureHierarchy() { + let contentView = UIView() + contentView.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(contentView) + + configureButtonsView() + + self.bottomConstraint = self.view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: self.buttonsView.bottomAnchor, constant: 16) + + NSLayoutConstraint.activate([ + contentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + contentView.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor), + + self.buttonsView.topAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 10), + self.buttonsView.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor), + self.buttonsView.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor), + + bottomConstraint, + ]) + + let scrollingController = DWScrollingViewController() + scrollingController.keyboardNotificationsEnabled = false + self.dw_embedChild(scrollingController, inContainer: contentView) + + configureInvitationView() + + scrollingController.contentView.dw_embedSubview(self.invitationView) + scrollingController.scrollView.showsVerticalScrollIndicator = false + } + + override func viewDidLoad() { + super.viewDidLoad() + + configureHierarchy() + + let wallet = DWEnvironment.sharedInstance().currentWallet + let myBlockchainIdentity = wallet.defaultBlockchainIdentity! + topView.update(with: myBlockchainIdentity, invitation: invitation) + + self.actionsView?.tagTextField.text = invitation.tag; + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.ka_startObservingKeyboardNotifications() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + self.invitation.updateInWallet() + self.ka_stopObservingKeyboardNotifications() + } + + override func ka_keyboardShowOrHideAnimation(withHeight height: CGFloat, animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) { + self.bottomConstraint.constant = height > 0 ? height : 16 + self.view.layoutIfNeeded() + } +} + +extension BaseInvitationViewController: DWInvitationActionsViewDelegate { + func invitationActionsViewCopyButtonAction(_ view: DWInvitationActionsView) { + UIPasteboard.general.string = invitationURL.absoluteString + } + + func invitationActionsView(_ view: DWInvitationActionsView, didChangeTag tag: String) { + invitation.tag = tag + } +} diff --git a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.h b/DashWallet/Sources/UI/DashPay/Invites/Invitation/DWInvitationLinkBuilder.h similarity index 69% rename from DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.h rename to DashWallet/Sources/UI/DashPay/Invites/Invitation/DWInvitationLinkBuilder.h index c4a40fc93..f02d6f492 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.h +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/DWInvitationLinkBuilder.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -17,16 +17,15 @@ #import -#import "DWDPBasicUserItem.h" -#import "DWDPBlockchainIdentityBackedItem.h" - NS_ASSUME_NONNULL_BEGIN @class DSBlockchainIdentity; -@interface DWDPSearchItemsFactory : NSObject +@interface DWInvitationLinkBuilder : NSObject -- (id)itemForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; ++ (void)dynamicLinkFrom:(NSString *)linkString + myBlockchainIdentity:(DSBlockchainIdentity *)myBlockchainIdentity + completion:(void (^)(NSURL *_Nullable url))completion; @end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/DWInvitationLinkBuilder.m b/DashWallet/Sources/UI/DashPay/Invites/Invitation/DWInvitationLinkBuilder.m new file mode 100644 index 000000000..80a74dbc9 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/DWInvitationLinkBuilder.m @@ -0,0 +1,82 @@ +// +// 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 "DWInvitationLinkBuilder.h" + +#import "DWEnvironment.h" +#import + +#import "DSBlockchainIdentity+DWDisplayName.h" + +static NSString *const AndroidBundleID = @"org.dash.dashpay.testnet"; +// TODO: DP set app store id +static NSString *const iOSAppStoreID = @"1563288407"; + +@implementation DWInvitationLinkBuilder + ++ (void)dynamicLinkFrom:(NSString *)linkString + myBlockchainIdentity:(DSBlockchainIdentity *)myBlockchainIdentity + completion:(void (^)(NSURL *_Nullable url))completion { + NSString *encodedName = [[myBlockchainIdentity dw_displayNameOrUsername] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + + NSString *displayNameParam = @""; + if (myBlockchainIdentity.displayName.length != 0) { + displayNameParam = [NSString stringWithFormat:@"&display-name=%@", encodedName]; + } + + NSString *avatarParam = @""; + if (myBlockchainIdentity.avatarPath.length > 0) { + NSString *encodedAvatar = [myBlockchainIdentity.avatarPath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + avatarParam = [NSString stringWithFormat:@"&avatar-url=%@", encodedAvatar]; + } + + NSString *fullLink = [NSString stringWithFormat:@"%@%@%@", linkString, displayNameParam, avatarParam]; + + NSURL *link = [[NSURL alloc] initWithString:fullLink]; + NSString *dynamicLinksDomainURIPrefix = @"https://invitations.dashpay.io/link"; + FIRDynamicLinkComponents *linkBuilder = + [[FIRDynamicLinkComponents alloc] initWithLink:link + domainURIPrefix:dynamicLinksDomainURIPrefix]; + linkBuilder.iOSParameters = + [[FIRDynamicLinkIOSParameters alloc] initWithBundleID:[[NSBundle mainBundle] bundleIdentifier]]; + linkBuilder.iOSParameters.appStoreID = iOSAppStoreID; + linkBuilder.androidParameters = + [[FIRDynamicLinkAndroidParameters alloc] initWithPackageName:AndroidBundleID]; + + linkBuilder.socialMetaTagParameters = + [[FIRDynamicLinkSocialMetaTagParameters alloc] init]; + linkBuilder.socialMetaTagParameters.title = NSLocalizedString(@"Join Now", nil); + + NSString *urlFormat = + [NSString + stringWithFormat:@"https://invitations.dashpay.io/fun/invite-preview?display-name=%@%@", + encodedName, avatarParam]; + linkBuilder.socialMetaTagParameters.imageURL = [NSURL URLWithString:urlFormat]; + linkBuilder.socialMetaTagParameters.descriptionText = + [NSString stringWithFormat:NSLocalizedString(@"You have been invited by %@. Start using Dash cryptocurrency.", nil), + [myBlockchainIdentity dw_displayNameOrUsername]]; + + [linkBuilder shortenWithCompletion:^(NSURL *_Nullable shortURL, + NSArray *_Nullable warnings, + NSError *_Nullable error) { + if (completion) { + completion(shortURL); + } + }]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/SuccessInvitationViewController.swift b/DashWallet/Sources/UI/DashPay/Invites/Invitation/SuccessInvitationViewController.swift new file mode 100644 index 000000000..eace88c0a --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/SuccessInvitationViewController.swift @@ -0,0 +1,78 @@ +// +// Created by Pavel Tikhonenko +// Copyright © 2022 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +@objc protocol SuccessInvitationViewControllerDelegate: AnyObject { + +@objc func successInvitationViewControllerDidSelectLater(controller: SuccessInvitationViewController) + +} + +@objc class SuccessInvitationViewController: BaseInvitationViewController { + @objc weak var delegate: SuccessInvitationViewControllerDelegate? + + override var preferredStatusBarStyle: UIStatusBarStyle { + .default + } + + @objc func laterButtonAction() { + delegate?.successInvitationViewControllerDidSelectLater(controller: self) + } + + override func configureTopView() { + self.topView = SuccessInvitationTopView(frame: .zero) + topView.translatesAutoresizingMaskIntoConstraints = false + topView.layer.cornerRadius = 8.0 + topView.layer.masksToBounds = true + topView.previewButton.addTarget(self, action: #selector(previewButtonAction), for: .touchUpInside) + + invitationView.addSubview(topView) + + NSLayoutConstraint.activate([ + topView.heightAnchor.constraint(equalTo: topView.widthAnchor, multiplier: 0.88), + topView.topAnchor.constraint(equalTo: invitationView.topAnchor, constant: 23), + topView.leadingAnchor.constraint(equalTo: invitationView.leadingAnchor), + topView.trailingAnchor.constraint(equalTo: invitationView.trailingAnchor), + ]) + } + + override func configureButtonsView() { + super.configureButtonsView() + + sendButton.setTitle(NSLocalizedString("Send Invitation", comment: ""), for: .normal) + + let laterButton = DWActionButton() + laterButton.translatesAutoresizingMaskIntoConstraints = false + laterButton.inverted = true + laterButton.setTitle(NSLocalizedString("Maybe later", comment: ""), for: .normal) + laterButton.addTarget(self, action: #selector(laterButtonAction), for: .touchUpInside) + buttonsView.addArrangedSubview(laterButton) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.topView.viewWillAppear() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.topView.viewDidAppear() + } +} diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.h b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationActionsView.h similarity index 57% rename from DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.h rename to DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationActionsView.h index 65fc06a54..f0ae73801 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.h +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationActionsView.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2019 Dash Core Group. All rights reserved. +// 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. @@ -16,23 +16,23 @@ // #import +#import "DWTextField.h" NS_ASSUME_NONNULL_BEGIN -@class DWTitleActionHeaderView; +@class DWInvitationActionsView; -@protocol DWTitleActionHeaderViewDelegate +@protocol DWInvitationActionsViewDelegate -- (void)titleActionHeaderView:(DWTitleActionHeaderView *)view buttonAction:(UIView *)sender; +- (void)invitationActionsView:(DWInvitationActionsView *)view didChangeTag:(NSString *)tag; +- (void)invitationActionsViewCopyButtonAction:(DWInvitationActionsView *)view; @end -@interface DWTitleActionHeaderView : UICollectionReusableView +@interface DWInvitationActionsView : UIView -@property (strong, nonatomic) IBOutlet UILabel *titleLabel; -@property (strong, nonatomic) IBOutlet UIButton *actionButton; - -@property (nullable, nonatomic, weak) id delegate; +@property (nonatomic, strong) DWTextField *tagTextField; +@property (nullable, nonatomic, weak) id delegate; @end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationActionsView.m b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationActionsView.m new file mode 100644 index 000000000..43faecb7d --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationActionsView.m @@ -0,0 +1,117 @@ +// +// 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 "DWInvitationActionsView.h" + +#import "DWActionButton.h" +#import "DWTextField.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWInvitationActionsView () + +@end + +NS_ASSUME_NONNULL_END + +static NSTimeInterval DEBOUNCE_DELAY = 0.2; + +@implementation DWInvitationActionsView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UILabel *title = [[UILabel alloc] init]; + title.translatesAutoresizingMaskIntoConstraints = NO; + title.textColor = [UIColor dw_darkTitleColor]; + title.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + title.text = NSLocalizedString(@"Tag for your reference", nil); + title.adjustsFontForContentSizeCategory = YES; + title.numberOfLines = 0; + [self addSubview:title]; + + DWTextField *textField = [[DWTextField alloc] init]; + textField.translatesAutoresizingMaskIntoConstraints = NO; + textField.horizontalPadding = 16.0; + textField.verticalPadding = 8; + textField.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + textField.textColor = [UIColor dw_darkTitleColor]; + textField.returnKeyType = UIReturnKeyDone; + textField.backgroundColor = [UIColor dw_backgroundColor]; + textField.layer.cornerRadius = 8; + textField.layer.masksToBounds = YES; + textField.delegate = self; + textField.placeholder = NSLocalizedString(@"eg: Dad", @"Invitation tag placeholder"); + [textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + [self addSubview:textField]; + _tagTextField = textField; + + DWActionButton *copyButton = [[DWActionButton alloc] init]; + copyButton.translatesAutoresizingMaskIntoConstraints = NO; + copyButton.inverted = YES; + [copyButton addTarget:self action:@selector(copyButtonAction) forControlEvents:UIControlEventTouchUpInside]; + [copyButton setTitle:NSLocalizedString(@"Copy Invitation Link", nil) forState:UIControlStateNormal]; + [copyButton setImage:[[UIImage imageNamed:@"invitation_copy"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] + forState:UIControlStateNormal]; + [copyButton setInsetsForContentPadding:UIEdgeInsetsMake(0, 20, 0, 20) imageTitlePadding:10]; + [self addSubview:copyButton]; + + [NSLayoutConstraint activateConstraints:@[ + [title.topAnchor constraintEqualToAnchor:self.topAnchor], + [title.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:title.trailingAnchor], + + [textField.topAnchor constraintEqualToAnchor:title.bottomAnchor + constant:12], + [textField.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:textField.trailingAnchor], + [textField.heightAnchor constraintEqualToConstant:52], + + [copyButton.topAnchor constraintEqualToAnchor:textField.bottomAnchor + constant:8], + [copyButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:copyButton.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:copyButton.bottomAnchor], + [copyButton.heightAnchor constraintEqualToConstant:44], + ]]; + } + return self; +} + +- (void)copyButtonAction { + [self.delegate invitationActionsViewCopyButtonAction:self]; +} + +#pragma mark - UITextFieldDelegate + +- (void)textFieldDidChange:(UITextField *)textField { + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tagDidUpdate) object:nil]; + + [self performSelector:@selector(tagDidUpdate) withObject:nil afterDelay:DEBOUNCE_DELAY]; +} + +- (void)tagDidUpdate { + [self.delegate invitationActionsView:self didChangeTag:_tagTextField.text ?: @""]; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + return YES; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.h b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationMessageView.h similarity index 84% rename from DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.h rename to DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationMessageView.h index d6c92f344..a114f9946 100644 --- a/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.h +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationMessageView.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWNotificationsViewController : UIViewController +@interface DWInvitationMessageView : UIView @end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationMessageView.m b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationMessageView.m new file mode 100644 index 000000000..19bea42da --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWInvitationMessageView.m @@ -0,0 +1,98 @@ +// +// 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 "DWInvitationMessageView.h" + +#import "DSBlockchainIdentity+DWDisplayName.h" +#import "DWEnvironment.h" +#import "DWSuccessInvitationView.h" +#import "DWUIKit.h" + +@implementation DWInvitationMessageView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + DWSuccessInvitationView *iconView = [[DWSuccessInvitationView alloc] initWithFrame:CGRectZero]; + iconView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:iconView]; + + UILabel *title = [[UILabel alloc] init]; + title.translatesAutoresizingMaskIntoConstraints = NO; + title.textColor = [UIColor dw_dashBlueColor]; + title.text = NSLocalizedString(@"Join Now", nil); + title.font = [UIFont dw_fontForTextStyle:UIFontTextStyleTitle2]; + title.textAlignment = NSTextAlignmentCenter; + title.numberOfLines = 0; + [self addSubview:title]; + + UILabel *subtitle = [[UILabel alloc] init]; + subtitle.translatesAutoresizingMaskIntoConstraints = NO; + subtitle.textColor = [UIColor dw_darkTitleColor]; + subtitle.textAlignment = NSTextAlignmentCenter; + subtitle.numberOfLines = 0; + [self addSubview:subtitle]; + + [NSLayoutConstraint activateConstraints:@[ + [iconView.topAnchor constraintEqualToAnchor:self.topAnchor + constant:32.0], + [iconView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + + [title.topAnchor constraintEqualToAnchor:iconView.bottomAnchor + constant:28], + [title.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:16], + [self.trailingAnchor constraintEqualToAnchor:title.trailingAnchor + constant:16], + + [subtitle.topAnchor constraintEqualToAnchor:title.bottomAnchor + constant:16], + [subtitle.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:16], + [self.trailingAnchor constraintEqualToAnchor:subtitle.trailingAnchor + constant:16], + + [self.bottomAnchor constraintEqualToAnchor:subtitle.bottomAnchor + constant:32.0], + ]]; + + // Setup + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + iconView.blockchainIdentity = myBlockchainIdentity; + + NSMutableAttributedString *desc = [[NSMutableAttributedString alloc] init]; + [desc beginEditing]; + + NSString *name = [myBlockchainIdentity dw_displayNameOrUsername]; + NSString *text = [NSString stringWithFormat:NSLocalizedString(@"You have been invited by %@. Start using Dash cryptocurrency.", nil), name]; + + [desc appendAttributedString:[[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody]}]]; + NSRange range = [text rangeOfString:name]; + if (range.location != NSNotFound) { + [desc setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]} range:range]; + } + + [desc endEditing]; + subtitle.attributedText = desc; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.h b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.h new file mode 100644 index 000000000..d80949735 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.h @@ -0,0 +1,33 @@ +// +// 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 DSBlockchainIdentity; + +@interface DWSuccessInvitationView : UIView + +@property (nullable, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +- (void)prepareForAnimation; +- (void)showAnimated; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.m b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.m new file mode 100644 index 000000000..cef644418 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.m @@ -0,0 +1,131 @@ +// +// 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 "DWSuccessInvitationView.h" + +#import "DWDPAvatarView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWSuccessInvitationView () + +@property (readonly, nonatomic, strong) UIImageView *backImageView; +@property (readonly, nonatomic, strong) UIView *avatarContainer; +@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; +@property (readonly, nonatomic, strong) UIImageView *topImageView; +@property (readonly, nonatomic, strong) NSLayoutConstraint *avatarTopConstraint; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWSuccessInvitationView + +- (DSBlockchainIdentity *)blockchainIdentity { + return self.avatarView.blockchainIdentity; +} + +- (void)setBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self.avatarView.blockchainIdentity = blockchainIdentity; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UIImageView *backImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"invite_letterbox_back"]]; + backImageView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:backImageView]; + _backImageView = backImageView; + + UIView *avatarContainer = [[UIView alloc] init]; + avatarContainer.translatesAutoresizingMaskIntoConstraints = NO; + avatarContainer.backgroundColor = [UIColor dw_lightBlueColor]; + [self addSubview:avatarContainer]; + _avatarContainer = avatarContainer; + + DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:CGRectZero]; + avatarView.translatesAutoresizingMaskIntoConstraints = NO; + avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; + [avatarContainer addSubview:avatarView]; + _avatarView = avatarView; + + UIImageView *topImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"invite_letterbox_top"]]; + topImageView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:topImageView]; + _topImageView = topImageView; + + NSLayoutConstraint *avatarTopConstraint = [avatarContainer.topAnchor constraintEqualToAnchor:self.topAnchor constant:56]; + _avatarTopConstraint = avatarTopConstraint; + + [NSLayoutConstraint activateConstraints:@[ + [backImageView.topAnchor constraintEqualToAnchor:self.topAnchor], + [backImageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:backImageView.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:backImageView.bottomAnchor], + + [topImageView.topAnchor constraintEqualToAnchor:self.topAnchor + constant:89.0], + [topImageView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + + [avatarView.topAnchor constraintEqualToAnchor:avatarContainer.topAnchor + constant:8.0], + [avatarView.leadingAnchor constraintEqualToAnchor:avatarContainer.leadingAnchor + constant:8.0], + [avatarContainer.trailingAnchor constraintEqualToAnchor:avatarView.trailingAnchor + constant:8.0], + [avatarContainer.bottomAnchor constraintEqualToAnchor:avatarView.bottomAnchor + constant:8.0], + [avatarView.widthAnchor constraintEqualToConstant:64.0], + [avatarView.heightAnchor constraintEqualToConstant:64.0], + + avatarTopConstraint, + [avatarContainer.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + ]]; + } + return self; +} + +- (CGSize)intrinsicContentSize { + return CGSizeMake(152.0, 175.0); +} + +- (void)prepareForAnimation { + self.backImageView.alpha = 0; + self.topImageView.alpha = 0; + self.avatarContainer.alpha = 0; + self.avatarTopConstraint.constant = 0; + [self layoutIfNeeded]; +} + +- (void)showAnimated { + [UIView animateWithDuration:0.35 + animations:^{ + self.backImageView.alpha = 1.0; + self.topImageView.alpha = 1.0; + } + completion:^(BOOL finished) { + self.avatarTopConstraint.constant = 56.0; + [UIView animateWithDuration:0.35 + animations:^{ + [self layoutIfNeeded]; + self.avatarContainer.alpha = 1.0; + }]; + }]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/InvitationBottomView.swift b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/InvitationBottomView.swift new file mode 100644 index 000000000..aa3a2d50e --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/InvitationBottomView.swift @@ -0,0 +1,96 @@ +// +// Created by Pavel Tikhonenko +// Copyright © 2022 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class InvitationBottomView: UIView { + init(invitation: DSBlockchainInvitation) { + super.init(frame: .zero) + + let stackView = UIStackView() + stackView.isUserInteractionEnabled = false + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 5 + stackView.alignment = .center + addSubview(stackView) + + let usedByLabel = UILabel() + usedByLabel.translatesAutoresizingMaskIntoConstraints = false + usedByLabel.text = NSLocalizedString("Invitation used by", comment: "") + usedByLabel.font = UIFont.dw_font(forTextStyle: .footnote) + usedByLabel.textColor = UIColor.dw_secondaryText() + stackView.addArrangedSubview(usedByLabel) + + let containerView = UIView() + containerView.translatesAutoresizingMaskIntoConstraints = false + containerView.backgroundColor = UIColor.dw_dashBlue().withAlphaComponent(0.7) + containerView.layer.cornerRadius = 8 + stackView.addArrangedSubview(containerView) + + let userStackView = UIStackView() + userStackView.translatesAutoresizingMaskIntoConstraints = false + userStackView.axis = .horizontal + userStackView.spacing = 10 + containerView.addSubview(userStackView) + + let avatarView = DWDPAvatarView() + avatarView.isSmall = true + avatarView.backgroundMode = .random + avatarView.blockchainIdentity = invitation.identity + userStackView.addArrangedSubview(avatarView) + + let titleStackView = UIStackView() + titleStackView.translatesAutoresizingMaskIntoConstraints = false + titleStackView.axis = .vertical + titleStackView.spacing = 4 + userStackView.addArrangedSubview(titleStackView) + + let titleLable = UILabel() + titleLable.font = UIFont.dw_mediumFont(ofSize: 14) + titleLable.text = invitation.identity.displayName ?? invitation.identity.currentDashpayUsername + titleStackView.addArrangedSubview(titleLable) + + if let dn = invitation.identity.displayName, !dn.isEmpty { + let sublabel = UILabel() + sublabel.font = UIFont.dw_font(forTextStyle: .footnote) + sublabel.text = invitation.identity.currentDashpayUsername + titleStackView.addArrangedSubview(sublabel) + } + + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: self.topAnchor), + stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + + userStackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20), + userStackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20), + userStackView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), + + containerView.heightAnchor.constraint(equalToConstant: 64), + + avatarView.widthAnchor.constraint(equalToConstant: 36), + avatarView.heightAnchor.constraint(equalToConstant: 36), + + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/InvitationTopView.swift b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/InvitationTopView.swift new file mode 100644 index 000000000..78137e585 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/InvitationTopView.swift @@ -0,0 +1,141 @@ +// +// Created by Pavel Tikhonenko +// Copyright © 2022 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class InvitationTopView: BaseInvitationTopView { + private var iconView: UIImageView! + private var titleLabel: UILabel! + private var dateLabel: UILabel! + + private var index: Int = 0 + + init(index: Int) { + super.init(frame: .zero) + + self.index = index + + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.alignment = .center + stackView.spacing = 5 + self.addSubview(stackView) + + iconView = UIImageView(image: UIImage(named: "icon_invitation_unread_big")!) + iconView.translatesAutoresizingMaskIntoConstraints = false + iconView.contentMode = .center + stackView.addArrangedSubview(iconView) + + let title = UILabel() + title.translatesAutoresizingMaskIntoConstraints = false + title.textColor = UIColor.dw_darkTitle() + title.font = UIFont.dw_font(forTextStyle: .title2) + title.adjustsFontForContentSizeCategory = true + title.textAlignment = .center + title.numberOfLines = 0 + stackView.addArrangedSubview(title) + titleLabel = title + + dateLabel = UILabel() + dateLabel.translatesAutoresizingMaskIntoConstraints = false + dateLabel.textColor = UIColor.dw_secondaryText() + dateLabel.font = UIFont.dw_font(forTextStyle: .footnote) + dateLabel.adjustsFontForContentSizeCategory = true + dateLabel.textAlignment = .center + dateLabel.numberOfLines = 0 + stackView.addArrangedSubview(dateLabel) + + NSLayoutConstraint.activate([ + stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor), + stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0), + + iconView.heightAnchor.constraint(equalToConstant: 72), + iconView.widthAnchor.constraint(equalToConstant: 72), + + previewButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16), + self.trailingAnchor.constraint(equalTo: previewButton.trailingAnchor, constant: 16), + self.bottomAnchor.constraint(equalTo: previewButton.bottomAnchor, constant: 4), + previewButton.heightAnchor.constraint(equalToConstant: 44), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func update(with blockchainIdentity: DSBlockchainIdentity, invitation: DSBlockchainInvitation) { + if invitation.identity.isRegistered { + iconView.image = UIImage(named: "icon_invitation_read_big")! + } + + let tag = invitation.tag.isEmpty ? nil : invitation.tag + let defaultTitle = NSString.localizedStringWithFormat(NSLocalizedString("Invitation %ld", comment: "") as NSString, index) + let title = invitation.name ?? (tag ?? String(defaultTitle)) + titleLabel.text = title + + + let transaction: DSTransaction = invitation.identity.registrationCreditFundingTransaction! + let chain = DWEnvironment.sharedInstance().currentChain + + let now = chain.timestamp(forBlockHeight: UInt32(TX_UNCONFIRMED)) + let txTime = (transaction.timestamp > 1) ? transaction.timestamp : now + let txDate = Date(timeIntervalSince1970: txTime) + let dateString = DWDateFormatter.sharedInstance().shortString(from: txDate) + dateLabel.text = dateString + } +} + +class BaseInvitationTopView: UIView { + + var previewButton: DWActionButton = DWActionButton() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.backgroundColor = UIColor.dw_background() + + previewButton.translatesAutoresizingMaskIntoConstraints = false + previewButton.inverted = true + previewButton.setTitle(NSLocalizedString("Preview Invitation", comment: ""), for: .normal) + self.addSubview(previewButton) + + NSLayoutConstraint.activate([ + previewButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16), + self.trailingAnchor.constraint(equalTo: previewButton.trailingAnchor, constant: 16), + self.bottomAnchor.constraint(equalTo: previewButton.bottomAnchor, constant: 4), + previewButton.heightAnchor.constraint(equalToConstant: 44), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(with blockchainIdentity: DSBlockchainIdentity, invitation: DSBlockchainInvitation) { + + } + + func viewWillAppear() { + + } + + func viewDidAppear() { + + } + +} diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/SuccessInvitationTopView.swift b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/SuccessInvitationTopView.swift new file mode 100644 index 000000000..60d42d853 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/SuccessInvitationTopView.swift @@ -0,0 +1,69 @@ +// +// Created by Pavel Tikhonenko +// Copyright © 2022 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class SuccessInvitationTopView: BaseInvitationTopView { + let iconView: DWSuccessInvitationView = DWSuccessInvitationView(frame: .zero) + + override init(frame: CGRect) { + super.init(frame: frame) + + iconView.translatesAutoresizingMaskIntoConstraints = false + iconView.transform = CGAffineTransform(scaleX: 0.68, y: 0.68) + self.addSubview(iconView) + + let title = UILabel() + title.translatesAutoresizingMaskIntoConstraints = false + title.textColor = UIColor.dw_darkTitle() + title.font = UIFont.dw_font(forTextStyle: .title2) + title.adjustsFontForContentSizeCategory = true + title.text = NSLocalizedString("Invitation Created Successfully", comment: "") + title.textAlignment = .center + title.numberOfLines = 0 + self.addSubview(title) + + title.setContentCompressionResistancePriority(.required, for: .vertical) + + NSLayoutConstraint.activate([ + iconView.topAnchor.constraint(equalTo: self.topAnchor, constant: 32), + iconView.centerXAnchor.constraint(equalTo: self.centerXAnchor), + + title.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 20), + title.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16), + self.trailingAnchor.constraint(equalTo: title.trailingAnchor, constant: 16), + + previewButton.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 5), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func update(with blockchainIdentity: DSBlockchainIdentity, invitation: DSBlockchainInvitation) { + self.iconView.blockchainIdentity = blockchainIdentity; + } + + override func viewWillAppear() { + iconView.prepareForAnimation() + } + + override func viewDidAppear() { + iconView.showAnimated() + } +} diff --git a/DashWallet/Sources/UI/DashPay/Invites/Preview/DWInvitationPreviewViewController.h b/DashWallet/Sources/UI/DashPay/Invites/Preview/DWInvitationPreviewViewController.h new file mode 100644 index 000000000..42864c44a --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Preview/DWInvitationPreviewViewController.h @@ -0,0 +1,26 @@ +// +// 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 + +@interface DWInvitationPreviewViewController : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Invites/Preview/DWInvitationPreviewViewController.m b/DashWallet/Sources/UI/DashPay/Invites/Preview/DWInvitationPreviewViewController.m new file mode 100644 index 000000000..4dfd4ee08 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Invites/Preview/DWInvitationPreviewViewController.m @@ -0,0 +1,163 @@ +// +// 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 "DWInvitationPreviewViewController.h" + +#import "DSBlockchainIdentity+DWDisplayName.h" +#import "DWActionButton.h" +#import "DWEnvironment.h" +#import "DWModalPopupTransition.h" +#import "DWSuccessInvitationView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWInvitationPreviewViewController () + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; + +@property (readonly, nonatomic, strong) DWSuccessInvitationView *iconView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWInvitationPreviewViewController + +- (instancetype)init { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _modalTransition = [[DWModalPopupTransition alloc] initWithInteractiveTransitionAllowed:YES]; + + self.transitioningDelegate = self.modalTransition; + self.modalPresentationStyle = UIModalPresentationCustom; + } + return self; +} + +- (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]; + + + DWSuccessInvitationView *iconView = [[DWSuccessInvitationView alloc] initWithFrame:CGRectZero]; + iconView.translatesAutoresizingMaskIntoConstraints = NO; + [contentView addSubview:iconView]; + _iconView = iconView; + + UILabel *title = [[UILabel alloc] init]; + title.translatesAutoresizingMaskIntoConstraints = NO; + title.textColor = [UIColor dw_dashBlueColor]; + title.text = NSLocalizedString(@"Join Now", nil); + title.font = [UIFont dw_fontForTextStyle:UIFontTextStyleTitle2]; + title.textAlignment = NSTextAlignmentCenter; + title.numberOfLines = 0; + [contentView addSubview:title]; + + UILabel *subtitle = [[UILabel alloc] init]; + subtitle.translatesAutoresizingMaskIntoConstraints = NO; + subtitle.textColor = [UIColor dw_darkTitleColor]; + subtitle.textAlignment = NSTextAlignmentCenter; + subtitle.numberOfLines = 0; + [contentView addSubview:subtitle]; + + DWActionButton *okButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + okButton.translatesAutoresizingMaskIntoConstraints = NO; + okButton.usedOnDarkBackground = NO; + okButton.small = YES; + okButton.inverted = YES; + [okButton setTitle:NSLocalizedString(@"Close", nil) forState:UIControlStateNormal]; + [okButton addTarget:self action:@selector(closeButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + [contentView addSubview:okButton]; + + [NSLayoutConstraint activateConstraints:@[ + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + [contentView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], + + [iconView.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:32.0], + [iconView.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + + [title.topAnchor constraintEqualToAnchor:iconView.bottomAnchor + constant:28], + [title.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:16], + [contentView.trailingAnchor constraintEqualToAnchor:title.trailingAnchor + constant:16], + + [subtitle.topAnchor constraintEqualToAnchor:title.bottomAnchor + constant:16], + [subtitle.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:16], + [contentView.trailingAnchor constraintEqualToAnchor:subtitle.trailingAnchor + constant:16], + + [okButton.topAnchor constraintEqualToAnchor:subtitle.bottomAnchor + constant:32.0], + [okButton.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:okButton.bottomAnchor + constant:20.0], + [okButton.heightAnchor constraintGreaterThanOrEqualToConstant:40.0], + ]]; + + // Setup + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + iconView.blockchainIdentity = myBlockchainIdentity; + + NSMutableAttributedString *desc = [[NSMutableAttributedString alloc] init]; + [desc beginEditing]; + + NSString *name = [myBlockchainIdentity dw_displayNameOrUsername]; + NSString *text = [NSString stringWithFormat:NSLocalizedString(@"You have been invited by %@. Start using Dash cryptocurrency.", nil), name]; + + [desc appendAttributedString:[[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody]}]]; + NSRange range = [text rangeOfString:name]; + if (range.location != NSNotFound) { + [desc setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]} range:range]; + } + + [desc endEditing]; + subtitle.attributedText = desc; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self.iconView prepareForAnimation]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [self.iconView showAnimated]; +} + +- (void)closeButtonAction:(UIButton *)sender { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.m b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.m deleted file mode 100644 index 7bf21b4f0..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.m +++ /dev/null @@ -1,52 +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 "DWDPContactsItemsFactory.h" - -#import "DWDPContactObject.h" -#import "DWDPDashpayUserBackedItem.h" -#import "DWDPFriendRequestBackedItem.h" -#import "DWDPNewIncomingRequestObject.h" -#import "DWDPRespondedIncomingRequestObject.h" - -@implementation DWDPContactsItemsFactory - -- (id)itemForEntity:(NSManagedObject *)entity { - if ([entity isKindOfClass:DSFriendRequestEntity.class]) { - return [self itemForFriendRequestEntity:(DSFriendRequestEntity *)entity]; - } - if ([entity isKindOfClass:DSDashpayUserEntity.class]) { - return [self itemForDashpayUserEntity:(DSDashpayUserEntity *)entity]; - } - - NSAssert(NO, @"Unsupported entity type"); - return nil; -} - -#pragma mark - Private - -- (id)itemForFriendRequestEntity:(DSFriendRequestEntity *)entity { - // TODO: DP impl case `if entity.isIgnored` - DSBlockchainIdentity *blockchainIdentity = [entity.sourceContact.associatedBlockchainIdentity blockchainIdentity]; - return [[DWDPNewIncomingRequestObject alloc] initWithFriendRequestEntity:entity blockchainIdentity:blockchainIdentity]; -} - -- (id)itemForDashpayUserEntity:(DSDashpayUserEntity *)entity { - return [[DWDPContactObject alloc] initWithDashpayUserEntity:entity]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.m b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.m deleted file mode 100644 index 9ea47c77c..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.m +++ /dev/null @@ -1,47 +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 "DWDPSearchItemsFactory.h" - -#import "DWEnvironment.h" - -#import "DWDPEstablishedContactObject.h" -#import "DWDPNewIncomingRequestObject.h" -#import "DWDPPendingRequestObject.h" -#import "DWDPUserObject.h" - -@implementation DWDPSearchItemsFactory - -- (id)itemForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - DSBlockchainIdentityFriendshipStatus friendshipStatus = [myBlockchainIdentity friendshipStatusForRelationshipWithBlockchainIdentity:blockchainIdentity]; - - switch (friendshipStatus) { - case DSBlockchainIdentityFriendshipStatus_Unknown: - case DSBlockchainIdentityFriendshipStatus_None: - return [[DWDPUserObject alloc] initWithBlockchainIdentity:blockchainIdentity]; - case DSBlockchainIdentityFriendshipStatus_Outgoing: - return [[DWDPPendingRequestObject alloc] initWithBlockchainIdentity:blockchainIdentity]; - case DSBlockchainIdentityFriendshipStatus_Incoming: - return [[DWDPNewIncomingRequestObject alloc] initWithBlockchainIdentity:blockchainIdentity]; - case DSBlockchainIdentityFriendshipStatus_Friends: - return [[DWDPEstablishedContactObject alloc] initWithBlockchainIdentity:blockchainIdentity]; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.m deleted file mode 100644 index c8ba471b3..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.m +++ /dev/null @@ -1,71 +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 "DWDPContactObject.h" - -#import - -#import "DWEnvironment.h" -#import "UIFont+DWDPItem.h" - -@implementation DWDPContactObject - -@synthesize displayName = _displayName; - -- (instancetype)initWithDashpayUserEntity:(DSDashpayUserEntity *)userEntity { - self = [super init]; - if (self) { - _userEntity = userEntity; - _blockchainIdentity = [userEntity.associatedBlockchainIdentity blockchainIdentity]; - } - return self; -} - -- (NSString *)username { - if (_username == nil) { - _username = [self.userEntity.username copy]; - } - return _username; -} - -- (NSString *)displayName { - if (_displayName == nil) { - _displayName = [self.userEntity.displayName copy]; - } - return _displayName; -} - -- (NSAttributedString *)title { - NSDictionary *attributes = @{NSFontAttributeName : [UIFont dw_itemTitleFont]}; - return [[NSAttributedString alloc] initWithString:self.displayName ?: self.username attributes:attributes]; -} - -- (NSString *)subtitle { - return self.displayName ? self.username : nil; -} - -- (DSFriendRequestEntity *)friendRequestToPay { - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - NSPredicate *predicate = [NSPredicate predicateWithFormat: - @"destinationContact.associatedBlockchainIdentity.uniqueID == %@", - myBlockchainIdentity.uniqueIDData]; - DSFriendRequestEntity *friendRequest = [[self.userEntity.outgoingRequests filteredSetUsingPredicate:predicate] anyObject]; - return friendRequest; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.h deleted file mode 100644 index 76287025d..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.h +++ /dev/null @@ -1,32 +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 "DWDPContactObject.h" -#import "DWDPEstablishedContactItem.h" - -NS_ASSUME_NONNULL_BEGIN - -/// Established contact may come from search or notifications -@interface DWDPEstablishedContactObject : DWDPContactObject - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithDashpayUserEntity:(DSDashpayUserEntity *)userEntity NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.h deleted file mode 100644 index b14c0ed1f..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.h +++ /dev/null @@ -1,38 +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 "DWDPFriendRequestBackedItem.h" -#import "DWDPIncomingRequestItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSFriendRequestEntity; - -@interface DWDPIncomingRequestObject : NSObject - -@property (readonly, nullable, strong, nonatomic) DSFriendRequestEntity *friendRequestEntity; - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.m deleted file mode 100644 index afbc992a6..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.m +++ /dev/null @@ -1,74 +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 "DWDPIncomingRequestObject.h" - -#import - -#import "UIFont+DWDPItem.h" - -@implementation DWDPIncomingRequestObject - -@synthesize blockchainIdentity = _blockchainIdentity; -@synthesize username = _username; - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - self = [super init]; - if (self) { - _blockchainIdentity = blockchainIdentity; - _friendRequestEntity = friendRequestEntity; - } - return self; -} - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - self = [super init]; - if (self) { - _blockchainIdentity = blockchainIdentity; - _username = blockchainIdentity.currentDashpayUsername; - } - return self; -} - -- (NSString *)username { - if (_username == nil) { - // incoming request, use source - DSDashpayUserEntity *contact = self.friendRequestEntity.sourceContact; - _username = [contact.username copy]; - } - return _username; -} - -- (NSString *)displayName { - return nil; -} - -- (NSAttributedString *)title { - NSDictionary *attributes = @{NSFontAttributeName : [UIFont dw_itemTitleFont]}; - return [[NSAttributedString alloc] initWithString:self.displayName ?: self.username attributes:attributes]; -} - -- (NSString *)subtitle { - return self.displayName ? self.username : nil; -} - -- (DSFriendRequestEntity *)friendRequestToPay { - return self.friendRequestEntity; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.h deleted file mode 100644 index c34166359..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.h +++ /dev/null @@ -1,28 +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 "DWDPIncomingRequestObject.h" - -#import "DWDPNewIncomingRequestItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPNewIncomingRequestObject : DWDPIncomingRequestObject - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.h deleted file mode 100644 index 9f6490adb..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.h +++ /dev/null @@ -1,27 +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 "DWDPPendingRequestItem.h" -#import "DWDPUserObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPPendingRequestObject : DWDPUserObject - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.h deleted file mode 100644 index d19bc1b8e..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.h +++ /dev/null @@ -1,27 +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 "DWDPIncomingRequestObject.h" -#import "DWDPRespondedRequestItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPRespondedIncomingRequestObject : DWDPIncomingRequestObject - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.m deleted file mode 100644 index 04cc776a5..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.m +++ /dev/null @@ -1,24 +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 "DWDPRespondedIncomingRequestObject.h" - -#import - -@implementation DWDPRespondedIncomingRequestObject - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.h deleted file mode 100644 index f6d40b3f4..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.h +++ /dev/null @@ -1,36 +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 "DWDPTxItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSTransaction; -@protocol DWTransactionListDataProviderProtocol; - -@interface DWDPTxObject : NSObject - -- (instancetype)initWithTransaction:(DSTransaction *)tx - dataProvider:(id)dataProvider - username:(NSString *)username NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.m deleted file mode 100644 index 52013c4b5..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.m +++ /dev/null @@ -1,88 +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 "DWDPTxObject.h" - -#import "DWTransactionListDataProviderProtocol.h" -#import "UIColor+DWStyle.h" -#import "UIFont+DWDPItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPTxObject () - -@property (readonly, nonatomic, strong) id dataProvider; -@property (readonly, nonatomic, strong) id dataItem; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPTxObject - -@synthesize displayName = _displayName; -@synthesize subtitle = _subtitle; -@synthesize username = _username; -@synthesize transaction = _transaction; - -- (instancetype)initWithTransaction:(DSTransaction *)tx - dataProvider:(id)dataProvider - username:(NSString *)username { - self = [super init]; - if (self) { - _transaction = tx; - _dataProvider = dataProvider; - _username = username; - // TODO: DP provide Display Name - _subtitle = [dataProvider shortDateStringForTransaction:tx]; - _dataItem = [dataProvider transactionDataForTransaction:tx]; - } - return self; -} - -- (NSAttributedString *)title { - NSDictionary *attributes = @{NSFontAttributeName : [UIFont dw_itemTitleFont]}; - return [[NSAttributedString alloc] initWithString:self.dataItem.directionText attributes:attributes]; -} - -- (NSAttributedString *)amountString { - UIFont *titleFont = [UIFont dw_itemTitleFont]; - UIFont *subtitleFont = [UIFont dw_itemSubtitleFont]; - - NSAttributedString *dashAmountString = [self.dataProvider dashAmountStringFrom:self.dataItem font:titleFont]; - - NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; - style.maximumLineHeight = 4.0; - NSAttributedString *spacingString = [[NSAttributedString alloc] initWithString:@"\n\n" - attributes:@{NSParagraphStyleAttributeName : style}]; - - NSAttributedString *fiatString = [[NSAttributedString alloc] initWithString:self.dataItem.fiatAmount - attributes:@{ - NSFontAttributeName : subtitleFont, - NSForegroundColorAttributeName : [UIColor dw_tertiaryTextColor], - }]; - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - [result beginEditing]; - [result appendAttributedString:dashAmountString]; - [result appendAttributedString:spacingString]; - [result appendAttributedString:fiatString]; - [result endEditing]; - return [result copy]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.h deleted file mode 100644 index 8ebd89ea3..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.h +++ /dev/null @@ -1,39 +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 "DWDPBasicUserItem.h" -#import "DWDPFriendRequestBackedItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSFriendRequestEntity; - -/// User from search -@interface DWDPUserObject : NSObject - -@property (readonly, nullable, strong, nonatomic) DSFriendRequestEntity *friendRequestEntity; - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.m deleted file mode 100644 index 2761a5cc5..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.m +++ /dev/null @@ -1,81 +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 "DWDPUserObject.h" - -#import - -#import "DWEnvironment.h" -#import "UIFont+DWDPItem.h" - -@implementation DWDPUserObject - -@synthesize blockchainIdentity = _blockchainIdentity; -@synthesize username = _username; - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - self = [super init]; - if (self) { - _blockchainIdentity = blockchainIdentity; - _username = blockchainIdentity.currentDashpayUsername; - } - return self; -} - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - self = [super init]; - if (self) { - _blockchainIdentity = blockchainIdentity; - _friendRequestEntity = friendRequestEntity; - } - return self; -} - -- (NSString *)displayName { - return nil; -} - -- (NSString *)username { - if (_username == nil) { - // outgoing request, use destination - DSDashpayUserEntity *contact = self.friendRequestEntity.destinationContact; - _username = [contact.username copy]; - } - return _username; -} - -- (NSAttributedString *)title { - NSDictionary *attributes = @{NSFontAttributeName : [UIFont dw_itemTitleFont]}; - return [[NSAttributedString alloc] initWithString:self.displayName ?: self.username attributes:attributes]; -} - -- (NSString *)subtitle { - return self.displayName ? self.username : nil; -} - -- (DSFriendRequestEntity *)friendRequestToPay { - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - NSPredicate *predicate = [NSPredicate predicateWithFormat: - @"sourceContact.associatedBlockchainIdentity.uniqueID == %@", - uint256_data(myBlockchainIdentity.uniqueID)]; - DSFriendRequestEntity *friendRequest = [[self.blockchainIdentity.matchingDashpayUserInViewContext.incomingRequests filteredSetUsingPredicate:predicate] anyObject]; - return friendRequest; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.h deleted file mode 100644 index fadb3646d..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.h +++ /dev/null @@ -1,35 +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 "DWDPNotificationItem.h" -#import "DWDPRespondedIncomingRequestObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPAcceptedRequestNotificationObject : DWDPRespondedIncomingRequestObject - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - isInitiatedByThem:(BOOL)isInitiatedByThem NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.m deleted file mode 100644 index bfec582df..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.m +++ /dev/null @@ -1,78 +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 "DWDPAcceptedRequestNotificationObject.h" - -#import "DWDateFormatter.h" -#import "DWEnvironment.h" -#import "UIFont+DWDPItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPAcceptedRequestNotificationObject () - -@property (readonly, nonatomic, assign, getter=isInitiatedByThem) BOOL initiatedByThem; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPAcceptedRequestNotificationObject - -@synthesize title = _title; -@synthesize subtitle = _subtitle; -@synthesize date = _date; - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - isInitiatedByThem:(BOOL)isInitiatedByThem { - self = [super initWithFriendRequestEntity:friendRequestEntity blockchainIdentity:blockchainIdentity]; - if (self) { - _date = [NSDate dateWithTimeIntervalSince1970:friendRequestEntity.timestamp]; - _initiatedByThem = isInitiatedByThem; - } - return self; -} - -- (NSAttributedString *)title { - if (_title == nil) { - NSString *name = self.displayName ?: self.username; - NSString *format = self.isInitiatedByThem - ? NSLocalizedString(@"%@ has sent you a contact request", nil) - : NSLocalizedString(@"%@ has accepted your contact request", nil); - NSString *plainTitle = [NSString stringWithFormat:format, name]; - - NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:plainTitle attributes:@{NSFontAttributeName : [UIFont dw_itemSubtitleFont]}]; - - NSRange range = [plainTitle rangeOfString:name]; - if (range.location != NSNotFound) { - [title setAttributes:@{NSFontAttributeName : [UIFont dw_itemTitleFont]} range:range]; - } - - _title = [title copy]; - } - return _title; -} - -- (NSString *)subtitle { - if (_subtitle == nil) { - _subtitle = [[DWDateFormatter sharedInstance] shortStringFromDate:self.date]; - } - return _subtitle; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.h deleted file mode 100644 index 258f97b32..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.h +++ /dev/null @@ -1,36 +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 "DWDPEstablishedContactObject.h" -#import "DWDPNotificationItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSFriendRequestEntity; - -// TODO: DP consider removing this notification type - -@interface DWDPEstablishedContactNotificationObject : DWDPEstablishedContactObject - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.m deleted file mode 100644 index 5ec58b2f9..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.m +++ /dev/null @@ -1,80 +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 "DWDPEstablishedContactNotificationObject.h" - -#import - -#import "DWDateFormatter.h" -#import "UIFont+DWDPItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPEstablishedContactNotificationObject () - -@property (readonly, nonatomic, strong) DSFriendRequestEntity *friendRequestEntity; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPEstablishedContactNotificationObject - -@synthesize title = _title; -@synthesize subtitle = _subtitle; -@synthesize date = _date; - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - self = [super initWithBlockchainIdentity:blockchainIdentity]; - if (self) { - _friendRequestEntity = friendRequestEntity; - _date = [NSDate dateWithTimeIntervalSince1970:friendRequestEntity.timestamp]; - } - return self; -} - -- (NSAttributedString *)title { - if (_title == nil) { - NSString *name = self.displayName ?: self.username; - NSString *format = NSLocalizedString(@"%@ has sent you a contact request", nil); - NSString *plainTitle = [NSString stringWithFormat:format, name]; - - NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:plainTitle attributes:@{NSFontAttributeName : [UIFont dw_itemSubtitleFont]}]; - - NSRange range = [plainTitle rangeOfString:name]; - if (range.location != NSNotFound) { - [title setAttributes:@{NSFontAttributeName : [UIFont dw_itemTitleFont]} range:range]; - } - - _title = [title copy]; - } - return _title; -} - -- (NSString *)subtitle { - if (_subtitle == nil) { - _subtitle = [[DWDateFormatter sharedInstance] shortStringFromDate:self.date]; - } - return _subtitle; -} - -- (DSFriendRequestEntity *)friendRequestToPay { - return self.friendRequestEntity; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.h deleted file mode 100644 index a5afc1cba..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.h +++ /dev/null @@ -1,29 +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 "DWDPNewIncomingRequestObject.h" -#import "DWDPNotificationItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPNewIncomingRequestNotificationObject : DWDPNewIncomingRequestObject - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.m deleted file mode 100644 index 711988ae3..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.m +++ /dev/null @@ -1,45 +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 "DWDPNewIncomingRequestNotificationObject.h" - -#import - -#import "DWDateFormatter.h" - -@implementation DWDPNewIncomingRequestNotificationObject - -@synthesize subtitle = _subtitle; -@synthesize date = _date; - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - self = [super initWithFriendRequestEntity:friendRequestEntity blockchainIdentity:blockchainIdentity]; - if (self) { - _date = [NSDate dateWithTimeIntervalSince1970:friendRequestEntity.timestamp]; - } - return self; -} - -- (NSString *)subtitle { - if (_subtitle == nil) { - _subtitle = [[DWDateFormatter sharedInstance] shortStringFromDate:self.date]; - } - return _subtitle; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.h deleted file mode 100644 index fdfbacf9a..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.h +++ /dev/null @@ -1,35 +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 "DWDPNotificationItem.h" -#import "DWDPUserObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPOutgoingRequestNotificationObject : DWDPUserObject - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - isInitiatedByMe:(BOOL)isInitiatedByMe NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.m deleted file mode 100644 index 2dd23508d..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.m +++ /dev/null @@ -1,83 +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 "DWDPOutgoingRequestNotificationObject.h" - -#import "DWDateFormatter.h" -#import "DWEnvironment.h" -#import "UIFont+DWDPItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPOutgoingRequestNotificationObject () - -@property (readonly, nonatomic, assign, getter=isInitiatedByMe) BOOL initiatedByMe; - -@end - -NS_ASSUME_NONNULL_END - - -@implementation DWDPOutgoingRequestNotificationObject - -@synthesize title = _title; -@synthesize subtitle = _subtitle; -@synthesize date = _date; - -- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity - blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - isInitiatedByMe:(BOOL)isInitiatedByMe { - self = [super initWithFriendRequestEntity:friendRequestEntity blockchainIdentity:blockchainIdentity]; - if (self) { - _date = [NSDate dateWithTimeIntervalSince1970:friendRequestEntity.timestamp]; - _initiatedByMe = isInitiatedByMe; - } - return self; -} - -- (NSAttributedString *)title { - if (_title == nil) { - NSString *name = self.displayName ?: self.username; - NSString *format = self.isInitiatedByMe - ? NSLocalizedString(@"You sent the contact request to %@", nil) - : NSLocalizedString(@"You accepted the contact request from %@", nil); - NSString *plainTitle = [NSString stringWithFormat:format, name]; - - NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:plainTitle attributes:@{NSFontAttributeName : [UIFont dw_itemSubtitleFont]}]; - - NSRange range = [plainTitle rangeOfString:name]; - if (range.location != NSNotFound) { - [title setAttributes:@{NSFontAttributeName : [UIFont dw_itemTitleFont]} range:range]; - } - - _title = [title copy]; - } - return _title; -} - -- (NSString *)subtitle { - if (_subtitle == nil) { - _subtitle = [[DWDateFormatter sharedInstance] shortStringFromDate:self.date]; - } - return _subtitle; -} - -- (DSFriendRequestEntity *)friendRequestToPay { - return nil; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNewIncomingRequestItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNewIncomingRequestItem.h deleted file mode 100644 index 7e462378b..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNewIncomingRequestItem.h +++ /dev/null @@ -1,43 +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 "DWDPIncomingRequestItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@protocol DWDPNewIncomingRequestItemDelegate - -- (void)acceptIncomingRequest:(id)item; -- (void)declineIncomingRequest:(id)item; - -@end - -typedef NS_ENUM(NSUInteger, DWDPNewIncomingRequestItemState) { - DWDPNewIncomingRequestItemState_Ready, - DWDPNewIncomingRequestItemState_Processing, - DWDPNewIncomingRequestItemState_Accepted, // succeeded - DWDPNewIncomingRequestItemState_Declined, // succeeded - DWDPNewIncomingRequestItemState_Failed, -}; - -@protocol DWDPNewIncomingRequestItem - -@property (nonatomic, assign) DWDPNewIncomingRequestItemState requestState; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPTxItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPTxItem.h deleted file mode 100644 index 3ea70f881..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPTxItem.h +++ /dev/null @@ -1,32 +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 "DWDPBasicItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSTransaction; - -@protocol DWDPTxItem - -/// Contains both Dash and fiat amounts -@property (readonly, nonatomic, copy) NSAttributedString *amountString; -@property (readonly, nonatomic, strong) DSTransaction *transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPFriendRequestBackedItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPFriendRequestBackedItem.h deleted file mode 100644 index f400a6991..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPFriendRequestBackedItem.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 - -@class DSFriendRequestEntity; - -@protocol DWDPFriendRequestBackedItem - -@property (readonly, strong, nonatomic) DSFriendRequestEntity *friendRequestEntity; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.h b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.h deleted file mode 100644 index 24b6bf9f7..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.h +++ /dev/null @@ -1,33 +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 "DWDPGenericItemView.h" - -#import "DWDPNewIncomingRequestItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPGenericContactRequestItemView : DWDPGenericItemView - -@property (readonly, nonatomic, strong) UIButton *acceptButton; -@property (readonly, nonatomic, strong) UIButton *declineButton; - -@property (nonatomic, assign) DWDPNewIncomingRequestItemState requestState; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.m deleted file mode 100644 index 250fe20c2..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.m +++ /dev/null @@ -1,182 +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 "DWDPGenericContactRequestItemView.h" - -#import "DWActionButton.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPGenericContactRequestItemView () - -@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; -@property (readonly, nonatomic, strong) UIImageView *statusImageView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPGenericContactRequestItemView - -@synthesize acceptButton = _acceptButton; -@synthesize declineButton = _declineButton; - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setup_contactRequestItemView]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setup_contactRequestItemView]; - } - return self; -} - -- (void)setup_contactRequestItemView { - DWActionButton *acceptButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; - acceptButton.translatesAutoresizingMaskIntoConstraints = NO; - acceptButton.small = YES; - [acceptButton setTitle:NSLocalizedString(@"Accept", nil) forState:UIControlStateNormal]; - [self.accessoryView addSubview:acceptButton]; - _acceptButton = acceptButton; - - DWActionButton *declineButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; - declineButton.translatesAutoresizingMaskIntoConstraints = NO; - declineButton.accentColor = [UIColor dw_declineButtonColor]; - [declineButton setImage:[UIImage imageNamed:@"icon_decline"] forState:UIControlStateNormal]; - [self.accessoryView addSubview:declineButton]; - _declineButton = declineButton; - - UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; - activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; - activityIndicatorView.color = [UIColor dw_tertiaryTextColor]; - [self.accessoryView addSubview:activityIndicatorView]; - _activityIndicatorView = activityIndicatorView; - - UIImageView *statusImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; - statusImageView.translatesAutoresizingMaskIntoConstraints = NO; - statusImageView.contentMode = UIViewContentModeCenter; - statusImageView.image = [UIImage imageNamed:@"dp_established_contact"]; - [self.accessoryView addSubview:statusImageView]; - _statusImageView = statusImageView; - - const CGFloat buttonHeight = 30.0; - const CGFloat spacing = 10.0; - - [statusImageView setContentCompressionResistancePriority:UILayoutPriorityRequired - 10 forAxis:UILayoutConstraintAxisHorizontal]; - [statusImageView setContentCompressionResistancePriority:UILayoutPriorityRequired - 10 forAxis:UILayoutConstraintAxisVertical]; - - NSLayoutConstraint *acceptTopConstraint = [acceptButton.topAnchor constraintGreaterThanOrEqualToAnchor:self.accessoryView.topAnchor]; - acceptTopConstraint.priority = UILayoutPriorityRequired - 20; - NSLayoutConstraint *acceptBottomConstraint = [self.accessoryView.bottomAnchor constraintGreaterThanOrEqualToAnchor:acceptButton.bottomAnchor]; - acceptBottomConstraint.priority = UILayoutPriorityRequired - 19; - NSLayoutConstraint *declineTopConstraint = [declineButton.topAnchor constraintGreaterThanOrEqualToAnchor:self.accessoryView.topAnchor]; - declineTopConstraint.priority = UILayoutPriorityRequired - 18; - NSLayoutConstraint *declineBottomConstraint = [self.accessoryView.bottomAnchor constraintGreaterThanOrEqualToAnchor:declineButton.bottomAnchor]; - declineBottomConstraint.priority = UILayoutPriorityRequired - 17; - - [NSLayoutConstraint activateConstraints:@[ - acceptTopConstraint, - [acceptButton.leadingAnchor constraintEqualToAnchor:self.accessoryView.leadingAnchor], - acceptBottomConstraint, - [acceptButton.centerYAnchor constraintEqualToAnchor:self.accessoryView.centerYAnchor], - [acceptButton.heightAnchor constraintEqualToConstant:buttonHeight], - - declineTopConstraint, - [declineButton.leadingAnchor constraintEqualToAnchor:acceptButton.trailingAnchor - constant:spacing], - [self.accessoryView.trailingAnchor constraintEqualToAnchor:declineButton.trailingAnchor], - declineBottomConstraint, - [declineButton.centerYAnchor constraintEqualToAnchor:self.accessoryView.centerYAnchor], - [declineButton.heightAnchor constraintEqualToConstant:buttonHeight], - [declineButton.widthAnchor constraintEqualToConstant:buttonHeight], - - [self.accessoryView.trailingAnchor constraintEqualToAnchor:activityIndicatorView.trailingAnchor], - [activityIndicatorView.centerYAnchor constraintEqualToAnchor:self.accessoryView.centerYAnchor], - - [statusImageView.topAnchor constraintEqualToAnchor:self.accessoryView.topAnchor], - [self.accessoryView.trailingAnchor constraintEqualToAnchor:statusImageView.trailingAnchor], - [self.accessoryView.bottomAnchor constraintEqualToAnchor:statusImageView.bottomAnchor], - ]]; -} - -- (void)setRequestState:(DWDPNewIncomingRequestItemState)requestState { - _requestState = requestState; - - switch (requestState) { - case DWDPNewIncomingRequestItemState_Ready: - [self setReadyState]; - break; - case DWDPNewIncomingRequestItemState_Processing: - [self setProcessingState]; - break; - case DWDPNewIncomingRequestItemState_Accepted: - [self setAcceptedState]; - break; - case DWDPNewIncomingRequestItemState_Declined: - [self setDeclinedState]; - break; - case DWDPNewIncomingRequestItemState_Failed: - [self setFailedState]; - break; - } -} - -- (void)setReadyState { - self.acceptButton.hidden = NO; - self.declineButton.hidden = NO; - - self.statusImageView.hidden = YES; - - [self.activityIndicatorView stopAnimating]; -} - -- (void)setProcessingState { - [self.activityIndicatorView startAnimating]; - - self.acceptButton.hidden = YES; - self.declineButton.hidden = YES; - - self.statusImageView.hidden = YES; -} - -- (void)setAcceptedState { - [self.activityIndicatorView stopAnimating]; - - self.acceptButton.hidden = YES; - self.declineButton.hidden = YES; - - self.statusImageView.hidden = NO; -} - -- (void)setDeclinedState { - // TODO: DP impl - [self setAcceptedState]; -} - -- (void)setFailedState { - // TODO: DP impl - [self setReadyState]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.h b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.h deleted file mode 100644 index 4dccd1a58..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.h +++ /dev/null @@ -1,28 +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 "DWDPGenericItemView.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPGenericImageItemView : DWDPGenericItemView - -@property (readonly, nonatomic, strong) UIImageView *imageView; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.m deleted file mode 100644 index 050463d9a..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.m +++ /dev/null @@ -1,58 +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 "DWDPGenericImageItemView.h" - -@implementation DWDPGenericImageItemView - -@synthesize imageView = _imageView; - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setup_imageItemView]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setup_imageItemView]; - } - return self; -} - -- (void)setup_imageItemView { - UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; - imageView.translatesAutoresizingMaskIntoConstraints = NO; - imageView.contentMode = UIViewContentModeCenter; - [self.accessoryView addSubview:imageView]; - _imageView = imageView; - - [imageView setContentCompressionResistancePriority:UILayoutPriorityRequired - 10 forAxis:UILayoutConstraintAxisHorizontal]; - [imageView setContentCompressionResistancePriority:UILayoutPriorityRequired - 10 forAxis:UILayoutConstraintAxisVertical]; - - [NSLayoutConstraint activateConstraints:@[ - [imageView.topAnchor constraintEqualToAnchor:self.accessoryView.topAnchor], - [imageView.leadingAnchor constraintEqualToAnchor:self.accessoryView.leadingAnchor], - [self.accessoryView.trailingAnchor constraintEqualToAnchor:imageView.trailingAnchor], - [self.accessoryView.bottomAnchor constraintEqualToAnchor:imageView.bottomAnchor], - ]]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.m deleted file mode 100644 index 30a057d45..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.m +++ /dev/null @@ -1,131 +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 "DWDPGenericItemView.h" - -#import "DWUIKit.h" -#import "UIFont+DWDPItem.h" - -static CGFloat const AVATAR_SIZE = 36.0; -static CGFloat const SPACING = 10.0; - -@interface DWDPGenericItemView () - -@property (readonly, nonatomic, strong) NSLayoutConstraint *textLabelLeadingConstraint; - -@end - -@implementation DWDPGenericItemView - -@synthesize avatarView = _avatarView; -@synthesize textLabel = _textLabel; -@synthesize accessoryView = _accessoryView; - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setup_genericItemView]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setup_genericItemView]; - } - return self; -} - -- (void)setup_genericItemView { - DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:CGRectZero]; - avatarView.translatesAutoresizingMaskIntoConstraints = NO; - avatarView.small = YES; - avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; - [self addSubview:avatarView]; - _avatarView = avatarView; - - UILabel *textLabel = [[UILabel alloc] init]; - textLabel.translatesAutoresizingMaskIntoConstraints = NO; - textLabel.font = [UIFont dw_itemTitleFont]; - textLabel.adjustsFontForContentSizeCategory = YES; - textLabel.textColor = [UIColor dw_darkTitleColor]; - textLabel.numberOfLines = 0; - [self addSubview:textLabel]; - _textLabel = textLabel; - - UIView *accessoryView = [[UIView alloc] init]; - accessoryView.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:accessoryView]; - _accessoryView = accessoryView; - - [textLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - [accessoryView setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 forAxis:UILayoutConstraintAxisVertical]; - - [textLabel setContentHuggingPriority:UILayoutPriorityDefaultLow - 1 forAxis:UILayoutConstraintAxisHorizontal]; - [accessoryView setContentHuggingPriority:UILayoutPriorityDefaultLow + 1 forAxis:UILayoutConstraintAxisHorizontal]; - - [textLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 3 forAxis:UILayoutConstraintAxisHorizontal]; - [accessoryView setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 forAxis:UILayoutConstraintAxisHorizontal]; - - NSLayoutConstraint *avatarTopConstraint = [avatarView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor]; - avatarTopConstraint.priority = UILayoutPriorityRequired - 10; - NSLayoutConstraint *avatarBottomConstraint = [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:avatarView.bottomAnchor]; - avatarBottomConstraint.priority = UILayoutPriorityRequired - 11; - - _textLabelLeadingConstraint = [textLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:AVATAR_SIZE + SPACING]; - - [NSLayoutConstraint activateConstraints:@[ - [avatarView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [avatarView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], - avatarTopConstraint, - avatarBottomConstraint, - [avatarView.widthAnchor constraintEqualToConstant:AVATAR_SIZE], - [avatarView.heightAnchor constraintEqualToConstant:AVATAR_SIZE], - - [textLabel.topAnchor constraintEqualToAnchor:self.topAnchor], - _textLabelLeadingConstraint, - [self.bottomAnchor constraintEqualToAnchor:textLabel.bottomAnchor], - - [accessoryView.topAnchor constraintEqualToAnchor:self.topAnchor], - [accessoryView.leadingAnchor constraintEqualToAnchor:textLabel.trailingAnchor - constant:SPACING], - [self.trailingAnchor constraintEqualToAnchor:accessoryView.trailingAnchor], - [self.bottomAnchor constraintEqualToAnchor:accessoryView.bottomAnchor], - ]]; -} - -- (void)setBackgroundColor:(UIColor *)backgroundColor { - [super setBackgroundColor:backgroundColor]; - - self.textLabel.backgroundColor = backgroundColor; - self.accessoryView.backgroundColor = backgroundColor; -} - -- (void)setAvatarHidden:(BOOL)avatarHidden { - _avatarHidden = avatarHidden; - - self.avatarView.hidden = avatarHidden; - if (avatarHidden) { - self.textLabelLeadingConstraint.constant = 0.0; - } - else { - self.textLabelLeadingConstraint.constant = AVATAR_SIZE + SPACING; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.h b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.h deleted file mode 100644 index 9ffcc3568..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.h +++ /dev/null @@ -1,28 +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 "DWDPGenericItemView.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPGenericStatusItemView : DWDPGenericItemView - -@property (readonly, nonatomic, strong) UILabel *statusLabel; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.m deleted file mode 100644 index 81679a4c1..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.m +++ /dev/null @@ -1,68 +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 "DWDPGenericStatusItemView.h" - -#import "DWUIKit.h" -#import "UIFont+DWDPItem.h" - -@implementation DWDPGenericStatusItemView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setup_statusItemView]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setup_statusItemView]; - } - return self; -} - -- (void)setup_statusItemView { - UILabel *statusLabel = [[UILabel alloc] init]; - statusLabel.translatesAutoresizingMaskIntoConstraints = NO; - statusLabel.font = [UIFont dw_itemSubtitleFont]; - statusLabel.adjustsFontForContentSizeCategory = YES; - statusLabel.textColor = [UIColor dw_tertiaryTextColor]; - statusLabel.numberOfLines = 0; - statusLabel.textAlignment = NSTextAlignmentRight; - [self.accessoryView addSubview:statusLabel]; - _statusLabel = statusLabel; - - [statusLabel setContentHuggingPriority:UILayoutPriorityDefaultLow - 3 forAxis:UILayoutConstraintAxisHorizontal]; - - [NSLayoutConstraint activateConstraints:@[ - [statusLabel.topAnchor constraintEqualToAnchor:self.accessoryView.topAnchor], - [statusLabel.leadingAnchor constraintEqualToAnchor:self.accessoryView.leadingAnchor], - [self.accessoryView.trailingAnchor constraintEqualToAnchor:statusLabel.trailingAnchor], - [self.accessoryView.bottomAnchor constraintEqualToAnchor:statusLabel.bottomAnchor], - ]]; -} - -- (void)setBackgroundColor:(UIColor *)backgroundColor { - [super setBackgroundColor:backgroundColor]; - - self.statusLabel.backgroundColor = backgroundColor; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.m deleted file mode 100644 index 29a4dba4b..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.m +++ /dev/null @@ -1,67 +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 "DWDPTxItemView.h" - -#import "DWUIKit.h" -#import "UIFont+DWDPItem.h" - -@implementation DWDPTxItemView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setup_statusItemView]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setup_statusItemView]; - } - return self; -} - -- (void)setup_statusItemView { - UILabel *amountLabel = [[UILabel alloc] init]; - amountLabel.translatesAutoresizingMaskIntoConstraints = NO; - amountLabel.font = [UIFont dw_itemSubtitleFont]; - amountLabel.adjustsFontForContentSizeCategory = YES; - amountLabel.textColor = [UIColor dw_darkTitleColor]; - amountLabel.numberOfLines = 0; - amountLabel.textAlignment = NSTextAlignmentRight; - [self.accessoryView addSubview:amountLabel]; - _amountLabel = amountLabel; - - [amountLabel setContentHuggingPriority:UILayoutPriorityDefaultLow - 3 forAxis:UILayoutConstraintAxisHorizontal]; - - [NSLayoutConstraint activateConstraints:@[ - [amountLabel.topAnchor constraintEqualToAnchor:self.accessoryView.topAnchor], - [amountLabel.leadingAnchor constraintEqualToAnchor:self.accessoryView.leadingAnchor], - [self.accessoryView.trailingAnchor constraintEqualToAnchor:amountLabel.trailingAnchor], - [self.accessoryView.bottomAnchor constraintEqualToAnchor:amountLabel.bottomAnchor], - ]]; -} - -- (void)setBackgroundColor:(UIColor *)backgroundColor { - [super setBackgroundColor:backgroundColor]; - - self.amountLabel.backgroundColor = backgroundColor; -} -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.h b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.h deleted file mode 100644 index 653b26a6a..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.h +++ /dev/null @@ -1,45 +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 - -#import "DWDPGenericItemView.h" - -// supports displaying: -#import "DWDPBasicItem.h" -#import "DWDPRespondedRequestItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPBasicCell : KVOUICollectionViewCell - -@property (readonly, class, nonatomic) Class itemViewClass; - -@property (readonly, nonatomic, strong) DWDPGenericItemView *itemView; -@property (nonatomic, assign) BOOL displayItemBackgroundView; -@property (nonatomic, assign) CGFloat contentWidth; - -@property (nullable, nonatomic, weak) id delegate; - -@property (nullable, nonatomic, strong) id item; -- (void)setItem:(id)item highlightedText:(nullable NSString *)highlightedText NS_REQUIRES_SUPER; - -- (void)reloadAttributedData; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.m b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.m deleted file mode 100644 index 4e5478442..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.m +++ /dev/null @@ -1,200 +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 "DWDPBasicCell.h" - -#import "DWShadowView.h" -#import "DWUIKit.h" -#import "NSAttributedString+DWHighlightText.h" -#import "UIFont+DWDPItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPBasicCell () - -@property (readonly, nonatomic, strong) DWShadowView *shadowView; - -@property (nullable, nonatomic, copy) NSString *highlightedText; -@property (nullable, nonatomic, strong) NSLayoutConstraint *contentWidthConstraint; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPBasicCell - -+ (Class)itemViewClass { - return DWDPGenericItemView.class; -} - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - self.contentView.backgroundColor = self.backgroundColor; - - DWShadowView *shadowView = [[DWShadowView alloc] initWithFrame:CGRectZero]; - shadowView.translatesAutoresizingMaskIntoConstraints = NO; - [self.contentView addSubview:shadowView]; - _shadowView = shadowView; - - UIView *roundedContentView = [[UIView alloc] initWithFrame:CGRectZero]; - roundedContentView.translatesAutoresizingMaskIntoConstraints = NO; - roundedContentView.backgroundColor = [UIColor dw_backgroundColor]; - roundedContentView.layer.cornerRadius = 8.0; - roundedContentView.layer.masksToBounds = YES; - [shadowView addSubview:roundedContentView]; - - Class klass = [self.class itemViewClass]; - DWDPGenericItemView *itemView = [[klass alloc] initWithFrame:CGRectZero]; - itemView.translatesAutoresizingMaskIntoConstraints = NO; - itemView.backgroundColor = self.backgroundColor; - [self.contentView addSubview:itemView]; - _itemView = itemView; - - const CGFloat verticalPadding = 5.0; - const CGFloat itemVerticalPadding = 18.0; - const CGFloat itemHorizontalPadding = verticalPadding + 10.0; - - UILayoutGuide *guide = self.contentView.layoutMarginsGuide; - [NSLayoutConstraint activateConstraints:@[ - [shadowView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor - constant:verticalPadding], - [shadowView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], - [guide.trailingAnchor constraintEqualToAnchor:shadowView.trailingAnchor], - [self.contentView.bottomAnchor constraintEqualToAnchor:shadowView.bottomAnchor - constant:verticalPadding], - - [roundedContentView.topAnchor constraintEqualToAnchor:shadowView.topAnchor], - [roundedContentView.leadingAnchor constraintEqualToAnchor:shadowView.leadingAnchor], - [shadowView.trailingAnchor constraintEqualToAnchor:roundedContentView.trailingAnchor], - [shadowView.bottomAnchor constraintEqualToAnchor:roundedContentView.bottomAnchor], - - [itemView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor - constant:itemVerticalPadding], - [itemView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor - constant:itemHorizontalPadding], - [guide.trailingAnchor constraintEqualToAnchor:itemView.trailingAnchor - constant:itemHorizontalPadding], - [self.contentView.bottomAnchor constraintEqualToAnchor:itemView.bottomAnchor - constant:itemVerticalPadding], - (_contentWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:200]), - ]]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self - selector:@selector(contentSizeCategoryDidChangeNotification) - name:UIContentSizeCategoryDidChangeNotification - object:nil]; - } - return self; -} - -- (void)setDisplayItemBackgroundView:(BOOL)displayItemBackgroundView { - _displayItemBackgroundView = displayItemBackgroundView; - - self.shadowView.hidden = !displayItemBackgroundView; - self.itemView.backgroundColor = displayItemBackgroundView ? [UIColor dw_backgroundColor] : [UIColor dw_secondaryBackgroundColor]; -} - -- (CGFloat)contentWidth { - return self.contentWidthConstraint.constant; -} - -- (void)setContentWidth:(CGFloat)contentWidth { - self.contentWidthConstraint.constant = contentWidth; -} - -- (void)setHighlighted:(BOOL)highlighted { - [super setHighlighted:highlighted]; - - [self dw_pressedAnimation:DWPressedAnimationStrength_Light pressed:highlighted]; -} - -- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { - [super traitCollectionDidChange:previousTraitCollection]; - - [self reloadAttributedData]; -} - -- (void)setItem:(id)item { - [self setItem:item highlightedText:nil]; -} - -- (void)setItem:(id)item highlightedText:(NSString *)highlightedText { - NSString *key = DW_KEYPATH(self, item); - [self willChangeValueForKey:key]; - _item = item; - [self didChangeValueForKey:key]; - - self.highlightedText = highlightedText; - - self.itemView.avatarView.username = item.username; - - [self reloadAttributedData]; -} - -#pragma mark - Notifications - -- (void)contentSizeCategoryDidChangeNotification { - [self reloadAttributedData]; -} - -#pragma mark - Private - -- (void)reloadAttributedData { - UIColor *highlightedTextColor = [UIColor dw_dashBlueColor]; - - NSAttributedString *titleString = [NSAttributedString - attributedText:self.item.title - textColor:[UIColor dw_darkTitleColor] - highlightedText:self.highlightedText - highlightedTextColor:highlightedTextColor]; - - NSAttributedString *subtitleString = [NSAttributedString - attributedText:self.item.subtitle - font:[UIFont dw_itemSubtitleFont] - textColor:[UIColor dw_tertiaryTextColor] - highlightedText:self.highlightedText - highlightedTextColor:highlightedTextColor]; - - NSAttributedString *resultString = nil; - if (titleString && subtitleString) { - NSMutableAttributedString *mutableResultString = [[NSMutableAttributedString alloc] init]; - [mutableResultString beginEditing]; - - [mutableResultString appendAttributedString:titleString]; - - NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; - style.maximumLineHeight = 4.0; - NSAttributedString *spacingString = [[NSAttributedString alloc] initWithString:@"\n\n" - attributes:@{NSParagraphStyleAttributeName : style}]; - [mutableResultString appendAttributedString:spacingString]; - - [mutableResultString appendAttributedString:subtitleString]; - - [mutableResultString endEditing]; - resultString = [mutableResultString copy]; - } - else { - resultString = titleString; - } - - self.itemView.textLabel.attributedText = resultString; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.m b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.m deleted file mode 100644 index 8bac21ebc..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.m +++ /dev/null @@ -1,49 +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 "DWDPImageStatusCell.h" - -#import "DWDPGenericImageItemView.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPImageStatusCell () - -@property (readonly, nonatomic, strong) DWDPGenericImageItemView *itemView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPImageStatusCell - -@dynamic itemView; - -+ (Class)itemViewClass { - return DWDPGenericImageItemView.class; -} - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - UIImage *image = [UIImage imageNamed:@"dp_established_contact"]; - self.itemView.imageView.image = image; - } - return self; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.h b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.h deleted file mode 100644 index c63676acb..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.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 "DWDPBasicCell.h" - -#import "DWDPNewIncomingRequestItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPIncomingRequestCell : DWDPBasicCell - -@property (nullable, nonatomic, strong) id item; -@property (nullable, nonatomic, weak) id delegate; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.m b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.m deleted file mode 100644 index 59f895a40..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.m +++ /dev/null @@ -1,75 +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 "DWDPIncomingRequestCell.h" - -#import "DWDPGenericContactRequestItemView.h" - -#import "DWDPNewIncomingRequestObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPIncomingRequestCell () - -@property (readonly, nonatomic, strong) DWDPGenericContactRequestItemView *itemView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPIncomingRequestCell - -@dynamic item; -@dynamic itemView; -@dynamic delegate; - -+ (Class)itemViewClass { - return DWDPGenericContactRequestItemView.class; -} - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self.itemView.acceptButton addTarget:self action:@selector(acceptButtonAction) forControlEvents:UIControlEventTouchUpInside]; - [self.itemView.declineButton addTarget:self action:@selector(declineButtonAction) forControlEvents:UIControlEventTouchUpInside]; - - [self mvvm_observe:@"item.requestState" - with:^(typeof(self) self, id value) { - [self updateItemRequestState]; - }]; - } - return self; -} - -#pragma mark - Actions - -- (void)acceptButtonAction { - [self.delegate acceptIncomingRequest:self.item]; -} - -- (void)declineButtonAction { - [self.delegate declineIncomingRequest:self.item]; -} - -#pragma mark - Private - -- (void)updateItemRequestState { - id requestItem = (id)self.item; - self.itemView.requestState = requestItem.requestState; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.m b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.m deleted file mode 100644 index 641c2bae6..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.m +++ /dev/null @@ -1,49 +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 "DWDPTxListCell.h" - -#import "DWDPTxItem.h" -#import "DWDPTxItemView.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPTxListCell () - -@property (readonly, nonatomic, strong) DWDPTxItemView *itemView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPTxListCell - -@dynamic itemView; - -+ (Class)itemViewClass { - return DWDPTxItemView.class; -} - -- (void)reloadAttributedData { - [super reloadAttributedData]; - - id txItem = (id)self.item; - NSAssert([txItem conformsToProtocol:@protocol(DWDPTxItem)], @"Invalid item type"); - self.itemView.amountLabel.attributedText = txItem.amountString; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.m b/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.m deleted file mode 100644 index 4104b9384..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.m +++ /dev/null @@ -1,69 +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 "UICollectionView+DWDPItemDequeue.h" - -#import "DWUIKit.h" - -#import "DWDPEstablishedContactItem.h" -#import "DWDPNewIncomingRequestItem.h" -#import "DWDPPendingRequestItem.h" -#import "DWDPRespondedRequestItem.h" -#import "DWDPTxItem.h" - -#import "DWDPBasicCell.h" -#import "DWDPImageStatusCell.h" -#import "DWDPIncomingRequestCell.h" -#import "DWDPTextStatusCell.h" -#import "DWDPTxListCell.h" - -@implementation UICollectionView (DWDPItemDequeue) - -- (void)dw_registerDPItemCells { - [self registerClass:DWDPBasicCell.class forCellWithReuseIdentifier:DWDPBasicCell.dw_reuseIdentifier]; - [self registerClass:DWDPIncomingRequestCell.class forCellWithReuseIdentifier:DWDPIncomingRequestCell.dw_reuseIdentifier]; - [self registerClass:DWDPImageStatusCell.class forCellWithReuseIdentifier:DWDPImageStatusCell.dw_reuseIdentifier]; - [self registerClass:DWDPTextStatusCell.class forCellWithReuseIdentifier:DWDPTextStatusCell.dw_reuseIdentifier]; - [self registerClass:DWDPTxListCell.class forCellWithReuseIdentifier:DWDPTxListCell.dw_reuseIdentifier]; -} - -- (__kindof UICollectionViewCell *)dw_dequeueReusableCellForItem:(id)item atIndexPath:(NSIndexPath *)indexPath { - // DWDPRespondedRequestItem should come before DWDPIncomingRequestItem since the first one also conforms to DWDPIncomingRequestItem - NSString *cellID = nil; - if ([item conformsToProtocol:@protocol(DWDPEstablishedContactItem)]) { - cellID = DWDPImageStatusCell.dw_reuseIdentifier; - } - else if ([item conformsToProtocol:@protocol(DWDPPendingRequestItem)]) { - cellID = DWDPTextStatusCell.dw_reuseIdentifier; - } - else if ([item conformsToProtocol:@protocol(DWDPRespondedRequestItem)]) { - cellID = DWDPBasicCell.dw_reuseIdentifier; - } - else if ([item conformsToProtocol:@protocol(DWDPNewIncomingRequestItem)]) { - cellID = DWDPIncomingRequestCell.dw_reuseIdentifier; - } - else if ([item conformsToProtocol:@protocol(DWDPTxItem)]) { - cellID = DWDPTxListCell.dw_reuseIdentifier; - } - else { // any DWDPBasicUserItem - cellID = DWDPBasicCell.dw_reuseIdentifier; - } - - return [self dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.m b/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.m deleted file mode 100644 index f964f4ec0..000000000 --- a/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.m +++ /dev/null @@ -1,32 +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 "UIFont+DWDPItem.h" - -#import "UIFont+DWFont.h" - -@implementation UIFont (DWDPItem) - -+ (UIFont *)dw_itemTitleFont { - return [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; -} - -+ (UIFont *)dw_itemSubtitleFont { - return [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.m b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.m deleted file mode 100644 index 32ad2b812..000000000 --- a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.m +++ /dev/null @@ -1,94 +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 "DWNoNotificationsCell.h" - -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWNoNotificationsCell () - -@property (nullable, nonatomic, strong) NSLayoutConstraint *contentWidthConstraint; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWNoNotificationsCell - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - self.contentView.backgroundColor = self.backgroundColor; - - UIImage *image = [UIImage imageNamed:@"dp_no_notifications"]; - UIImageView *iconImageView = [[UIImageView alloc] initWithImage:image]; - iconImageView.translatesAutoresizingMaskIntoConstraints = NO; - iconImageView.contentMode = UIViewContentModeScaleAspectFit; - [self.contentView addSubview:iconImageView]; - - UILabel *label = [[UILabel alloc] init]; - label.translatesAutoresizingMaskIntoConstraints = NO; - label.backgroundColor = self.backgroundColor; - label.numberOfLines = 0; - label.textAlignment = NSTextAlignmentCenter; - label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; - label.adjustsFontForContentSizeCategory = YES; - label.textColor = [UIColor dw_tertiaryTextColor]; - label.text = NSLocalizedString(@"There are no new notifications", nil); - [self.contentView addSubview:label]; - - [iconImageView setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 - forAxis:UILayoutConstraintAxisVertical]; - [label setContentCompressionResistancePriority:UILayoutPriorityRequired - forAxis:UILayoutConstraintAxisVertical]; - - const CGFloat spacing = 30.0; - UILayoutGuide *guide = self.contentView.layoutMarginsGuide; - - NSLayoutConstraint *labelIconConstraint = [label.topAnchor constraintGreaterThanOrEqualToAnchor:iconImageView.bottomAnchor - constant:spacing]; - labelIconConstraint.priority = UILayoutPriorityRequired - 2; - - [NSLayoutConstraint activateConstraints:@[ - [iconImageView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor - constant:spacing], - [iconImageView.centerXAnchor constraintEqualToAnchor:self.contentView.centerXAnchor], - - labelIconConstraint, - [label.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], - [guide.bottomAnchor constraintEqualToAnchor:label.bottomAnchor - constant:spacing], - [guide.trailingAnchor constraintEqualToAnchor:label.trailingAnchor], - - (_contentWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:200]), - ]]; - } - return self; -} - -- (CGFloat)contentWidth { - return self.contentWidthConstraint.constant; -} - -- (void)setContentWidth:(CGFloat)contentWidth { - self.contentWidthConstraint.constant = contentWidth; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.h b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.h new file mode 100644 index 000000000..d3084b874 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.h @@ -0,0 +1,37 @@ +// +// 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 DWNotificationsInvitationCell; + +@protocol DWNotificationsInvitationCellDelegate + +- (void)notificationsInvitationCellCloseAction:(DWNotificationsInvitationCell *)cell; + +@end + +@interface DWNotificationsInvitationCell : UICollectionViewCell + +@property (nonatomic, assign) CGFloat contentWidth; +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.m b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.m new file mode 100644 index 000000000..3b1847fb4 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.m @@ -0,0 +1,124 @@ +// +// 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 "DWNotificationsInvitationCell.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWNotificationsInvitationCell () + +@property (readonly, nonatomic, strong) NSLayoutConstraint *contentWidthConstraint; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWNotificationsInvitationCell + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UIView *view = [[UIView alloc] init]; + view.translatesAutoresizingMaskIntoConstraints = NO; + view.backgroundColor = [UIColor dw_lightBlueColor]; + view.layer.cornerRadius = 8; + view.layer.masksToBounds = YES; + [self.contentView addSubview:view]; + + + UIImage *image = [UIImage imageNamed:@"menu_invite"]; + UIImageView *iconImageView = [[UIImageView alloc] initWithImage:image]; + iconImageView.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:iconImageView]; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.textColor = [UIColor blackColor]; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + titleLabel.text = NSLocalizedString(@"Invite your friends and family to the Dash Network", nil); + titleLabel.numberOfLines = 0; + titleLabel.adjustsFontForContentSizeCategory = YES; + [view addSubview:titleLabel]; + + UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + closeButton.translatesAutoresizingMaskIntoConstraints = NO; + [closeButton setTitle:@"X" forState:UIControlStateNormal]; + closeButton.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + [closeButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + [closeButton addTarget:self + action:@selector(closeButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + [view addSubview:closeButton]; + + [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + [iconImageView setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisHorizontal]; + [iconImageView setContentHuggingPriority:UILayoutPriorityDefaultLow + 10 + forAxis:UILayoutConstraintAxisHorizontal]; + [view setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + + _contentWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:300]; + + UILayoutGuide *guide = self.contentView.layoutMarginsGuide; + [NSLayoutConstraint activateConstraints:@[ + _contentWidthConstraint, + + [view.topAnchor constraintEqualToAnchor:guide.topAnchor], + [view.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor + constant:8], + [guide.trailingAnchor constraintEqualToAnchor:view.trailingAnchor + constant:8], + [guide.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], + + [iconImageView.leadingAnchor constraintEqualToAnchor:view.leadingAnchor + constant:16], + [iconImageView.centerYAnchor constraintEqualToAnchor:view.centerYAnchor], + [iconImageView.topAnchor constraintGreaterThanOrEqualToAnchor:view.topAnchor], + [view.bottomAnchor constraintGreaterThanOrEqualToAnchor:iconImageView.bottomAnchor], + + [titleLabel.leadingAnchor constraintEqualToAnchor:iconImageView.trailingAnchor + constant:12], + [titleLabel.topAnchor constraintEqualToAnchor:view.topAnchor + constant:16], + [view.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:16], + + [closeButton.leadingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor + constant:12], + [view.trailingAnchor constraintEqualToAnchor:closeButton.trailingAnchor], + [closeButton.centerYAnchor constraintEqualToAnchor:view.centerYAnchor], + [closeButton.widthAnchor constraintEqualToConstant:44], + [closeButton.heightAnchor constraintEqualToConstant:44], + [closeButton.topAnchor constraintGreaterThanOrEqualToAnchor:view.topAnchor], + [view.bottomAnchor constraintGreaterThanOrEqualToAnchor:closeButton.bottomAnchor], + ]]; + } + return self; +} + +- (void)setContentWidth:(CGFloat)contentWidth { + self.contentWidthConstraint.constant = contentWidth; +} + +- (void)closeButtonAction { + [self.delegate notificationsInvitationCellCloseAction:self]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.h b/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.h deleted file mode 100644 index 5db6931e8..000000000 --- a/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.h +++ /dev/null @@ -1,29 +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 - -/// UITableView-like collection layout. -@interface DWListCollectionLayout : UICollectionViewFlowLayout - -@property (readonly, nonatomic, assign) CGFloat contentWidth; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.m b/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.m deleted file mode 100644 index 744d2d247..000000000 --- a/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.m +++ /dev/null @@ -1,39 +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 "DWListCollectionLayout.h" - -static UIEdgeInsets const INSETS = {0.0, 10.0, 0.0, 10.0}; - -@implementation DWListCollectionLayout - -- (instancetype)init { - self = [super init]; - if (self) { - self.scrollDirection = UICollectionViewScrollDirectionVertical; - self.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize; - self.sectionInset = INSETS; - self.sectionHeadersPinToVisibleBounds = YES; - } - return self; -} - -- (CGFloat)contentWidth { - return ceil(CGRectGetWidth(self.collectionView.safeAreaLayoutGuide.layoutFrame) - self.sectionInset.left - self.sectionInset.right); -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.m b/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.m deleted file mode 100644 index d669db3bb..000000000 --- a/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.m +++ /dev/null @@ -1,252 +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 "DWNotificationsViewController.h" - -#import "DWDPBasicCell.h" -#import "DWDPNewIncomingRequestItem.h" -#import "DWListCollectionLayout.h" -#import "DWNoNotificationsCell.h" -#import "DWNotificationsModel.h" -#import "DWTitleActionHeaderView.h" -#import "DWUIKit.h" -#import "UICollectionView+DWDPItemDequeue.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWNotificationsViewController () - -@property (null_resettable, nonatomic, strong) DWNotificationsModel *model; -@property (null_resettable, nonatomic, strong) UICollectionView *collectionView; -@property (null_resettable, nonatomic, strong) DWTitleActionHeaderView *measuringHeaderView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWNotificationsViewController - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - self.hidesBottomBarWhenPushed = YES; - } - return self; -} - -- (void)dealloc { - DSLog(@"☠️ %@", NSStringFromClass(self.class)); -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - [self updateTitle]; - - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - [self.view addSubview:self.collectionView]; -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - - if (self.isMovingFromParentViewController) { - [self.model saveMostRecentViewedNotificationDate]; - } -} - -- (UIStatusBarStyle)preferredStatusBarStyle { - return UIStatusBarStyleLightContent; -} - -#pragma mark - UICollectionViewDataSource - -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { - return 2; -} - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - DWNotificationsData *data = self.model.data; - if (section == 0) { - if (data.unreadItems.count == 0) { - return 1; // empty state - } - else { - return data.unreadItems.count; - } - } - else { - return data.oldItems.count; - } -} - -- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; - NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); - const CGFloat contentWidth = layout.contentWidth; - - if (indexPath.section == 0 && self.model.data.unreadItems.count == 0) { - DWNoNotificationsCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:DWNoNotificationsCell.dw_reuseIdentifier - forIndexPath:indexPath]; - cell.contentWidth = contentWidth; - return cell; - } - - id item = [self itemAtIndexPath:indexPath]; - - DWDPBasicCell *cell = [collectionView dw_dequeueReusableCellForItem:item atIndexPath:indexPath]; - cell.displayItemBackgroundView = indexPath.section == 0; - cell.contentWidth = contentWidth; - cell.delegate = self; - cell.item = item; - return cell; -} - -#pragma mark - UICollectionViewDelegate - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - const NSInteger section = indexPath.section; - // hide Earlier section header if it's empty - if (section == 1 && [collectionView numberOfItemsInSection:section] == 0) { - return [[UICollectionReusableView alloc] init]; - } - - DWTitleActionHeaderView *view = (DWTitleActionHeaderView *)[collectionView - dequeueReusableSupplementaryViewOfKind:kind - withReuseIdentifier:DWTitleActionHeaderView.dw_reuseIdentifier - forIndexPath:indexPath]; - view.titleLabel.text = [self titleForSection:section]; - view.actionButton.hidden = YES; - return view; -} - -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 0 && self.model.data.unreadItems.count > 0) { // unread items - id item = [self itemAtIndexPath:indexPath]; - [self.model markNotificationAsRead:item]; - } -} - -#pragma mark - UICollectionViewDelegateFlowLayout - -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { - // hide Earlier section header if it's empty - if (section == 1 && [collectionView numberOfItemsInSection:section] == 0) { - return CGSizeZero; - } - - DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; - NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); - const CGFloat contentWidth = layout.contentWidth; - - self.measuringHeaderView.titleLabel.text = [self titleForSection:section]; - self.measuringHeaderView.frame = CGRectMake(0, 0, contentWidth, 300); - CGSize size = [self.measuringHeaderView systemLayoutSizeFittingSize:CGSizeMake(contentWidth, UILayoutFittingExpandedSize.height) - withHorizontalFittingPriority:UILayoutPriorityRequired - verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; - - return size; -} - -#pragma mark - DWNotificationsModelDelegate - -- (void)notificationsModelDidUpdate:(DWNotificationsModel *)model { - [self.collectionView reloadData]; - [self updateTitle]; -} - -#pragma mark - DWDPNewIncomingRequestItemDelegate - -- (void)acceptIncomingRequest:(id)item { - [self.model acceptContactRequest:item]; -} - -- (void)declineIncomingRequest:(id)item { - [self.model declineContactRequest:item]; -} - -#pragma mark - Private - -- (UICollectionView *)collectionView { - if (_collectionView == nil) { - DWListCollectionLayout *layout = [[DWListCollectionLayout alloc] init]; - - UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds - collectionViewLayout:layout]; - collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - collectionView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - collectionView.delegate = self; - collectionView.dataSource = self; - collectionView.alwaysBounceVertical = YES; - [collectionView dw_registerDPItemCells]; - [collectionView registerClass:DWNoNotificationsCell.class - forCellWithReuseIdentifier:DWNoNotificationsCell.dw_reuseIdentifier]; - [collectionView registerClass:DWTitleActionHeaderView.class - forSupplementaryViewOfKind:UICollectionElementKindSectionHeader - withReuseIdentifier:DWTitleActionHeaderView.dw_reuseIdentifier]; - - _collectionView = collectionView; - } - return _collectionView; -} - -- (DWNotificationsModel *)model { - if (!_model) { - _model = [[DWNotificationsModel alloc] init]; - _model.delegate = self; - } - return _model; -} - -- (DWTitleActionHeaderView *)measuringHeaderView { - if (_measuringHeaderView == nil) { - DWTitleActionHeaderView *view = [[DWTitleActionHeaderView alloc] initWithFrame:CGRectZero]; - view.translatesAutoresizingMaskIntoConstraints = NO; - view.actionButton.hidden = YES; - _measuringHeaderView = view; - } - return _measuringHeaderView; -} - -- (void)updateTitle { - const NSUInteger unreadCount = self.model.data.unreadItems.count; - NSString *title = NSLocalizedString(@"Notifications", nil); - if (unreadCount > 0) { - self.title = [NSString stringWithFormat:@"%@ (%ld)", title, unreadCount]; - } - else { - self.title = title; - } -} - -- (id)itemAtIndexPath:(NSIndexPath *)indexPath { - DWNotificationsData *data = self.model.data; - NSArray> *items = indexPath.section == 0 ? data.unreadItems : data.oldItems; - return items[indexPath.row]; -} - -- (NSString *)titleForSection:(NSInteger)section { - if (section == 0) { - return NSLocalizedString(@"New", @"(List of) New (notifications)"); - } - else { - return NSLocalizedString(@"Earlier", @"(List of notifications happened) Earlier (some time ago)"); - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.h b/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.h deleted file mode 100644 index 4da483432..000000000 --- a/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.h +++ /dev/null @@ -1,46 +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 - -#import "DWNotificationsData.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DWNotificationsModel; - -@protocol DWNotificationsModelDelegate - -- (void)notificationsModelDidUpdate:(DWNotificationsModel *)model; - -@end - -@interface DWNotificationsModel : NSObject - -@property (readonly, nonatomic, copy) DWNotificationsData *data; - -@property (nullable, nonatomic, weak) id delegate; - -- (void)acceptContactRequest:(id)item; -- (void)declineContactRequest:(id)item; - -- (void)markNotificationAsRead:(id)item; -- (void)saveMostRecentViewedNotificationDate; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.m b/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.m deleted file mode 100644 index e01dd2424..000000000 --- a/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.m +++ /dev/null @@ -1,92 +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 "DWNotificationsModel.h" - -#import "DWDashPayContactsActions.h" -#import "DWEnvironment.h" -#import "DWGlobalOptions.h" -#import "DWNotificationsProvider.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWNotificationsModel () - -@property (nullable, nonatomic, copy) DWNotificationsData *data; -@property (nullable, nonatomic, strong) NSDate *mostRecentViewedNotificationDate; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWNotificationsModel - -- (instancetype)init { - self = [super init]; - if (self) { - _data = [[DWNotificationsData alloc] init]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(notificationsDidUpdate) - name:DWNotificationsProviderDidUpdateNotification - object:nil]; - [self notificationsDidUpdate]; // initial update (when notification was missed) - } - return self; -} - -- (void)dealloc { - [[DWNotificationsProvider sharedInstance] forceUpdate]; -} - -- (void)acceptContactRequest:(id)item { - [DWDashPayContactsActions acceptContactRequest:item completion:nil]; -} - -- (void)declineContactRequest:(id)item { - [DWDashPayContactsActions declineContactRequest:item completion:nil]; -} - -- (void)markNotificationAsRead:(id)item { - if (self.mostRecentViewedNotificationDate == nil || - [item.date compare:self.mostRecentViewedNotificationDate] == NSOrderedDescending) { - self.mostRecentViewedNotificationDate = item.date; - } -} - -- (void)saveMostRecentViewedNotificationDate { - if (self.mostRecentViewedNotificationDate == nil) { - return; - } - - DWGlobalOptions *options = [DWGlobalOptions sharedInstance]; - if (options.mostRecentViewedNotificationDate == nil || - [self.mostRecentViewedNotificationDate compare:options.mostRecentViewedNotificationDate] == NSOrderedDescending) { - options.mostRecentViewedNotificationDate = self.mostRecentViewedNotificationDate; - } -} - -#pragma mark - Private - -- (void)notificationsDidUpdate { - DWNotificationsProvider *provider = [DWNotificationsProvider sharedInstance]; - self.data = provider.data; - - [self.delegate notificationsModelDidUpdate:self]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.m b/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.m deleted file mode 100644 index 9e28cf279..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.m +++ /dev/null @@ -1,66 +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 "DWModalUserProfileViewController.h" - -#import "DWUserProfileViewController.h" -#import "UIViewController+DWEmbedding.h" -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWModalUserProfileViewController () - -@property (nonatomic, strong) DWUserProfileViewController *profileController; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWModalUserProfileViewController - -- (instancetype)initWithItem:(id)item - payModel:(id)payModel - dataProvider:(id)dataProvider { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _profileController = [[DWUserProfileViewController alloc] initWithItem:item - payModel:payModel - dataProvider:dataProvider - shouldSkipUpdating:YES]; - } - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"payments_nav_cross"] - style:UIBarButtonItemStyleDone - target:self - action:@selector(cancelButtonAction)]; - self.profileController.navigationItem.rightBarButtonItem = cancelButton; - - UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self.profileController]; - [self dw_embedChild:navigationController]; -} - -- (void)cancelButtonAction { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.h b/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.h deleted file mode 100644 index d6bf5e9c9..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.h +++ /dev/null @@ -1,42 +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 "DWBasePayViewController.h" - -#import "DWDPBasicUserItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUserProfileViewController : DWBasePayViewController - -- (instancetype)initWithItem:(id)item - payModel:(id)payModel - dataProvider:(id)dataProvider; - -- (instancetype)initWithItem:(id)item - payModel:(id)payModel - dataProvider:(id)dataProvider - shouldSkipUpdating:(BOOL)shouldSkipUpdating NS_DESIGNATED_INITIALIZER; - -- (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 - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.m b/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.m deleted file mode 100644 index 05e9464b2..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.m +++ /dev/null @@ -1,392 +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 "DWUserProfileViewController.h" - -#import "DWDPBasicCell.h" -#import "DWDPTxItem.h" -#import "DWFilterHeaderView.h" -#import "DWStretchyHeaderListCollectionLayout.h" -#import "DWUIKit.h" -#import "DWUserProfileContactActionsCell.h" -#import "DWUserProfileHeaderView.h" -#import "DWUserProfileModel.h" -#import "DWUserProfileNavigationTitleView.h" -#import "DWUserProfileSendRequestCell.h" -#import "UICollectionView+DWDPItemDequeue.h" -#import "UIViewController+DWTxFilter.h" -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -static CGFloat const FILTER_PADDING = 15.0; // same as horizontal padding for itemView inside DWDPBasicCell - -@interface DWUserProfileViewController () - -@property (readonly, nonatomic, strong) DWUserProfileModel *model; - -@property (null_resettable, nonatomic, strong) UICollectionView *collectionView; -@property (nullable, nonatomic, weak) DWUserProfileHeaderView *headerView; - -@property (null_resettable, nonatomic, strong) DWFilterHeaderView *measuringFilterHeaderView; -@property (null_resettable, nonatomic, strong) DWUserProfileHeaderView *measuringProfileHeaderView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserProfileViewController - -- (instancetype)initWithItem:(id)item - payModel:(id)payModel - dataProvider:(id)dataProvider { - return [self initWithItem:item payModel:payModel dataProvider:dataProvider shouldSkipUpdating:NO]; -} - -- (instancetype)initWithItem:(id)item - payModel:(id)payModel - dataProvider:(id)dataProvider - shouldSkipUpdating:(BOOL)shouldSkipUpdating { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _model = [[DWUserProfileModel alloc] initWithItem:item txDataProvider:dataProvider]; - _model.delegate = self; - if (shouldSkipUpdating) { - [_model skipUpdating]; - } - else { - [_model update]; - } - - self.payModel = payModel; - self.dataProvider = dataProvider; - - self.hidesBottomBarWhenPushed = YES; - } - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - DWUserProfileNavigationTitleView *titleView = [[DWUserProfileNavigationTitleView alloc] initWithFrame:CGRectZero]; - [titleView updateWithUsername:self.model.username]; - CGSize titleSize = [titleView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; - titleView.frame = CGRectMake(0, 0, titleSize.width, titleSize.height); - self.navigationItem.titleView = titleView; - - [self.view addSubview:self.collectionView]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - - [self.collectionView flashScrollIndicators]; -} - -- (id)contactItem { - return self.model.item; -} - -#pragma mark - UICollectionViewDataSource - -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { - return 2; -} - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - if (section == 0) { - const BOOL shouldDisplayActions = [self shouldShowActions]; - return shouldDisplayActions ? 1 : 0; - } - else { - return self.model.dataSource.count; - } -} - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 0) { - DWUserProfileHeaderView *headerView = (DWUserProfileHeaderView *)[collectionView - dequeueReusableSupplementaryViewOfKind:kind - withReuseIdentifier:DWUserProfileHeaderView.dw_reuseIdentifier - forIndexPath:indexPath]; - headerView.model = self.model; - headerView.delegate = self; - self.headerView = headerView; - return headerView; - } - else { - DWFilterHeaderView *headerView = (DWFilterHeaderView *)[collectionView - dequeueReusableSupplementaryViewOfKind:kind - withReuseIdentifier:DWFilterHeaderView.dw_reuseIdentifier - forIndexPath:indexPath]; - headerView.padding = FILTER_PADDING; - headerView.titleLabel.text = NSLocalizedString(@"Activity", nil); - headerView.delegate = self; - [headerView.filterButton setTitle:[self titleForFilterButton] forState:UIControlStateNormal]; - return headerView; - } -} - -- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; - NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); - const CGFloat contentWidth = layout.contentWidth; - - if (indexPath.section == 0) { - if ([self shouldShowSendRequestAction]) { - DWUserProfileSendRequestCell *cell = [collectionView - dequeueReusableCellWithReuseIdentifier:DWUserProfileSendRequestCell.dw_reuseIdentifier - forIndexPath:indexPath]; - cell.contentWidth = contentWidth; - cell.model = self.model; - cell.delegate = self; - return cell; - } - DWUserProfileContactActionsCell *cell = [collectionView - dequeueReusableCellWithReuseIdentifier:DWUserProfileContactActionsCell.dw_reuseIdentifier - forIndexPath:indexPath]; - cell.contentWidth = contentWidth; - cell.model = self.model; - cell.delegate = self; - return cell; - } - else { - id item = [self itemAtIndexPath:indexPath]; - - DWDPBasicCell *cell = [collectionView dw_dequeueReusableCellForItem:item atIndexPath:indexPath]; - cell.contentWidth = contentWidth; - cell.itemView.avatarHidden = YES; - cell.displayItemBackgroundView = NO; - cell.item = item; - return cell; - } -} - -#pragma mark - UICollectionViewDelegateFlowLayout - -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { - DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; - NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); - const CGFloat contentWidth = layout.contentWidth; - - if (section == 1 && self.model.dataSource.count == 0) { - return CGSizeZero; - } - - UIView *measuringView = section == 0 ? self.measuringProfileHeaderView : self.measuringFilterHeaderView; - measuringView.frame = CGRectMake(0, 0, contentWidth, 300); - CGSize size = [measuringView systemLayoutSizeFittingSize:CGSizeMake(contentWidth, UILayoutFittingCompressedSize.height) - withHorizontalFittingPriority:UILayoutPriorityRequired - verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; - - return size; -} - -#pragma mark - UICollectionViewDelegate - -- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - [collectionView deselectItemAtIndexPath:indexPath animated:YES]; - - if (indexPath.section != 1) { - return; - } - - id item = [self itemAtIndexPath:indexPath]; - if (![item conformsToProtocol:@protocol(DWDPTxItem)]) { - return; - } - - DSTransaction *transaction = ((id)item).transaction; - - TxDetailModel *model = [[TxDetailModel alloc] initWithTransaction:transaction]; - TXDetailViewController *controller = [[TXDetailViewController alloc] initWithModel:model]; - - DWNavigationController *nvc = [[DWNavigationController alloc] initWithRootViewController:controller]; - [self presentViewController:nvc animated:YES completion:nil]; -} - -#pragma mark - UIScrollViewDelegate - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - const CGFloat contentOffsetY = scrollView.contentOffset.y; - const CGFloat headerHeight = CGRectGetHeight(self.headerView.bounds); - const float percent = headerHeight > 0.0 ? contentOffsetY / headerHeight : 0.0; - - [self.headerView setScrollingPercent:percent]; - - DWUserProfileNavigationTitleView *titleView = (DWUserProfileNavigationTitleView *)self.navigationItem.titleView; - NSAssert([titleView isKindOfClass:DWUserProfileNavigationTitleView.class], @"Invalid titleView"); - [titleView setScrollingPercent:percent]; -} - -#pragma mark - DWUserProfileModelDelegate - -- (void)userProfileModelDidUpdate:(DWUserProfileModel *)model { - [self.collectionView reloadData]; -} - -#pragma mark - DWUserProfileHeaderViewDelegate - -- (void)userProfileHeaderView:(DWUserProfileHeaderView *)view actionButtonAction:(UIButton *)sender { - const BOOL canPay = self.model.friendshipStatus == DSBlockchainIdentityFriendshipStatus_Incoming || - self.model.friendshipStatus == DSBlockchainIdentityFriendshipStatus_Friends; - NSParameterAssert(canPay); - if (canPay) { - [self performPayToUser:self.model.item]; - } -} - -#pragma mark - DWUserProfileSendRequestCellDelegate - -- (void)userProfileSendRequestCell:(DWUserProfileSendRequestCell *)cell sendRequestButtonAction:(UIButton *)sender { - const BOOL canSendRequest = self.model.friendshipStatus == DSBlockchainIdentityFriendshipStatus_None; - NSParameterAssert(canSendRequest); - if (canSendRequest) { - [self.model sendContactRequest]; - } -} - -#pragma mark - DWUserProfileContactActionsCellDelegate - -- (void)userProfileContactActionsCell:(DWUserProfileContactActionsCell *)cell mainButtonAction:(UIButton *)sender { - const BOOL canAcceptRequest = self.model.friendshipStatus == DSBlockchainIdentityFriendshipStatus_Incoming; - NSParameterAssert(canAcceptRequest); - if (canAcceptRequest) { - [self.model acceptContactRequest]; - } -} - -- (void)userProfileContactActionsCell:(DWUserProfileContactActionsCell *)cell secondaryButtonAction:(UIButton *)sender { - // TODO: DP decline request -} - -#pragma mark - DWFilterHeaderViewDelegate - -- (void)filterHeaderView:(DWFilterHeaderView *)view filterButtonAction:(UIView *)sender { - [self showTxFilterWithSender:sender displayModeProvider:self.model shouldShowRewards:NO]; -} - -#pragma mark - Private - -- (BOOL)shouldShowActions { - if (self.model.state != DWUserProfileModelState_Done) { - return NO; - } - - const DSBlockchainIdentityFriendshipStatus status = self.model.friendshipStatus; - return (status == DSBlockchainIdentityFriendshipStatus_Incoming || - status == DSBlockchainIdentityFriendshipStatus_None || - status == DSBlockchainIdentityFriendshipStatus_Outgoing); -} - -- (BOOL)shouldShowSendRequestAction { - NSParameterAssert(self.model.state == DWUserProfileModelState_Done); - - const DSBlockchainIdentityFriendshipStatus status = self.model.friendshipStatus; - return (status == DSBlockchainIdentityFriendshipStatus_None || - status == DSBlockchainIdentityFriendshipStatus_Outgoing); -} - -- (BOOL)shouldShowAcceptDeclineRequestAction { - NSParameterAssert(self.model.state == DWUserProfileModelState_Done); - - const DSBlockchainIdentityFriendshipStatus status = self.model.friendshipStatus; - return status == DSBlockchainIdentityFriendshipStatus_Incoming; -} - -- (id)itemAtIndexPath:(NSIndexPath *)indexPath { - NSAssert(indexPath.section > 0, @"Section 0 is empty and should not have any data items"); - NSIndexPath *dataIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:0]; - id item = [self.model.dataSource itemAtIndexPath:dataIndexPath]; - return item; -} - -- (UICollectionView *)collectionView { - if (_collectionView == nil) { - DWStretchyHeaderListCollectionLayout *layout = [[DWStretchyHeaderListCollectionLayout alloc] init]; - - UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds - collectionViewLayout:layout]; - collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - collectionView.dataSource = self; - collectionView.delegate = self; - collectionView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - collectionView.alwaysBounceVertical = YES; - - [collectionView dw_registerDPItemCells]; - - [collectionView registerClass:DWUserProfileContactActionsCell.class - forCellWithReuseIdentifier:DWUserProfileContactActionsCell.dw_reuseIdentifier]; - [collectionView registerClass:DWUserProfileSendRequestCell.class - forCellWithReuseIdentifier:DWUserProfileSendRequestCell.dw_reuseIdentifier]; - - [collectionView registerClass:DWUserProfileHeaderView.class - forSupplementaryViewOfKind:UICollectionElementKindSectionHeader - withReuseIdentifier:DWUserProfileHeaderView.dw_reuseIdentifier]; - [collectionView registerClass:DWFilterHeaderView.class - forSupplementaryViewOfKind:UICollectionElementKindSectionHeader - withReuseIdentifier:DWFilterHeaderView.dw_reuseIdentifier]; - - _collectionView = collectionView; - } - return _collectionView; -} - -- (DWUserProfileHeaderView *)measuringProfileHeaderView { - if (_measuringProfileHeaderView == nil) { - _measuringProfileHeaderView = [[DWUserProfileHeaderView alloc] initWithFrame:CGRectZero]; - } - _measuringProfileHeaderView.model = self.model; - return _measuringProfileHeaderView; -} - -- (DWFilterHeaderView *)measuringFilterHeaderView { - if (_measuringFilterHeaderView == nil) { - _measuringFilterHeaderView = [[DWFilterHeaderView alloc] initWithFrame:CGRectZero]; - _measuringFilterHeaderView.padding = FILTER_PADDING; - _measuringFilterHeaderView.titleLabel.text = NSLocalizedString(@"Activity", nil); - } - [_measuringFilterHeaderView.filterButton setTitle:[self titleForFilterButton] - forState:UIControlStateNormal]; - return _measuringFilterHeaderView; -} - -- (NSString *)titleForFilterButton { - switch (self.model.displayMode) { - case DWHomeTxDisplayMode_All: - return NSLocalizedString(@"All", nil); - case DWHomeTxDisplayMode_Received: - return NSLocalizedString(@"Received", nil); - case DWHomeTxDisplayMode_Sent: - return NSLocalizedString(@"Sent", nil); - case DWHomeTxDisplayMode_Rewards: - NSAssert(NO, @"Not implemented here"); - return nil; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.h b/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.h deleted file mode 100644 index 3153204a1..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.h +++ /dev/null @@ -1,71 +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 - -#import -#import - -#import "DWDPBasicUserItem.h" -#import "DWDPBlockchainIdentityBackedItem.h" -#import "DWTxDisplayModeProtocol.h" -#import "DWUserProfileDataSource.h" - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSInteger, DWUserProfileModelState) { - DWUserProfileModelState_None, - DWUserProfileModelState_Loading, - DWUserProfileModelState_Error, - DWUserProfileModelState_Done, -}; - -@class DWUserProfileModel; -@protocol DWTransactionListDataProviderProtocol; - -@protocol DWUserProfileModelDelegate - -- (void)userProfileModelDidUpdate:(DWUserProfileModel *)model; - -@end - -@interface DWUserProfileModel : NSObject - -@property (readonly, nonatomic, strong) id item; -@property (readonly, nonatomic, assign) DWUserProfileModelState state; -@property (readonly, nonatomic, copy) NSString *username; -@property (readonly, nonatomic, assign) DSBlockchainIdentityFriendshipStatus friendshipStatus; -@property (readonly, nonatomic, strong) id dataSource; -@property (readonly, nonatomic, assign) DWUserProfileModelState requestState; - -@property (nullable, nonatomic, weak) id delegate; - -- (void)skipUpdating; -- (void)update; - -- (void)sendContactRequest; -- (void)acceptContactRequest; - -- (instancetype)initWithItem:(id)item - txDataProvider:(id)txDataProvider; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.m b/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.m deleted file mode 100644 index 7eb7f599a..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.m +++ /dev/null @@ -1,237 +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 "DWUserProfileModel.h" - -#import "DWDashPayContactsActions.h" -#import "DWDashPayContactsUpdater.h" -#import "DWEnvironment.h" -#import "DWProfileTxsFetchedDataSource.h" -#import "DWUserProfileDataSourceObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUserProfileModel () - -@property (nonatomic, assign) DWUserProfileModelState state; -@property (nullable, nonatomic, strong) DWProfileTxsFetchedDataSource *txsFetchedDataSource; -@property (nonatomic, strong) id dataSource; -@property (readonly, nonatomic, strong) id txDataProvider; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserProfileModel - -@synthesize displayMode = _displayMode; - -- (instancetype)initWithItem:(id)item - txDataProvider:(id)txDataProvider { - self = [super init]; - if (self) { - _item = item; - _txDataProvider = txDataProvider; - _dataSource = [[DWUserProfileDataSourceObject alloc] init]; // empty data source - - // TODO: DP global notification is used temporary. Remove its usage once FRC delegate issue is resolved - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self - selector:@selector(transactionManagerTransactionStatusDidChangeNotification) - name:DSTransactionManagerTransactionStatusDidChangeNotification - object:nil]; - } - return self; -} - -- (void)setDisplayMode:(DWHomeTxDisplayMode)displayMode { - _displayMode = displayMode; - - [self updateDataSource]; -} - -- (void)skipUpdating { - [self updateDataSource]; - self.state = DWUserProfileModelState_Done; -} - -- (void)setState:(DWUserProfileModelState)state { - _state = state; - - [self.delegate userProfileModelDidUpdate:self]; -} - -- (void)setRequestState:(DWUserProfileModelState)requestState { - _requestState = requestState; - - [self.delegate userProfileModelDidUpdate:self]; -} - -- (NSString *)username { - return self.item.username; -} - -- (void)update { - self.state = DWUserProfileModelState_Loading; - - __weak typeof(self) weakSelf = self; - [[DWDashPayContactsUpdater sharedInstance] fetchWithCompletion:^(BOOL success, NSArray *_Nonnull errors) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - [strongSelf updateDataSource]; - strongSelf.state = success ? DWUserProfileModelState_Done : DWUserProfileModelState_Error; - }]; -} - -- (DSBlockchainIdentityFriendshipStatus)friendshipStatus { - if (self.state == DWUserProfileModelState_None || self.state == DWUserProfileModelState_Loading) { - return DSBlockchainIdentityFriendshipStatus_Unknown; - } - - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - DSBlockchainIdentity *blockchainIdentity = self.item.blockchainIdentity; - return [myBlockchainIdentity friendshipStatusForRelationshipWithBlockchainIdentity:blockchainIdentity]; -} - -- (void)sendContactRequest { - self.requestState = DWUserProfileModelState_Loading; - - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - __weak typeof(self) weakSelf = self; - [myBlockchainIdentity sendNewFriendRequestToBlockchainIdentity:self.item.blockchainIdentity - completion:^(BOOL success, NSArray *_Nullable errors) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - [strongSelf updateDataSource]; - strongSelf.requestState = success ? DWUserProfileModelState_Done : DWUserProfileModelState_Error; - }]; -} - -- (void)acceptContactRequest { - self.requestState = DWUserProfileModelState_Loading; - - __weak typeof(self) weakSelf = self; - [DWDashPayContactsActions acceptContactRequest:self.item - completion:^(BOOL success, NSArray *_Nonnull errors) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - [strongSelf updateDataSource]; - strongSelf.requestState = success ? DWUserProfileModelState_Done : DWUserProfileModelState_Error; - }]; -} - -#pragma mark - DWFetchedResultsDataSourceDelegate - -- (void)fetchedResultsDataSourceDidUpdate:(DWFetchedResultsDataSource *)fetchedResultsDataSource { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - // TODO: DP fix me, not firing - // global txs notifications was used instead to workaround this issue - - [self updateDataSource]; -} - -#pragma mark - NSFetchedResultsControllerDelegate - -- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - // TODO: DP fix me, not firing - // global txs notifications was used instead to workaround this issue - - [self updateDataSource]; -} - -#pragma mark - Notifications - -- (void)transactionManagerTransactionStatusDidChangeNotification { - [self updateDataSource]; -} - -#pragma mark - Private - -- (void)updateDataSource { - NSManagedObjectContext *context = [NSManagedObjectContext viewContext]; - - DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; - DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; - if (myBlockchainIdentity == nil) { - return; - } - - DSBlockchainIdentity *friendBlockchainIdentity = self.item.blockchainIdentity; - NSAssert(myBlockchainIdentity.matchingDashpayUserInViewContext, @"Invalid DSBlockchainIdentity: myBlockchainIdentity"); - DSDashpayUserEntity *me = [myBlockchainIdentity matchingDashpayUserInContext:context]; - DSDashpayUserEntity *friend = nil; - if (friendBlockchainIdentity.matchingDashpayUserInViewContext) { - friend = [friendBlockchainIdentity matchingDashpayUserInContext:context]; - } - - DSFriendRequestEntity *meToFriend = nil; - if (friend != nil) { - meToFriend = [[me.outgoingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"destinationContact == %@", friend]] anyObject]; - } - - DSFriendRequestEntity *friendToMe = nil; - if (friend != nil) { - friendToMe = [[me.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", friend]] anyObject]; - } - - BOOL shouldShowContactRequests = YES; - if (self.displayMode == DWHomeTxDisplayMode_Sent) { - meToFriend = nil; - shouldShowContactRequests = NO; - } - else if (self.displayMode == DWHomeTxDisplayMode_Received) { - friendToMe = nil; - shouldShowContactRequests = NO; - } - - if (meToFriend || friendToMe) { - self.txsFetchedDataSource = [[DWProfileTxsFetchedDataSource alloc] initWithMeToFriendRequest:meToFriend - friendToMeRequest:friendToMe - inContext:context]; - self.txsFetchedDataSource.delegate = self; - [self.txsFetchedDataSource start]; - self.txsFetchedDataSource.fetchedResultsController.delegate = self; - } - else { - self.txsFetchedDataSource = nil; - } - - self.dataSource = [[DWUserProfileDataSourceObject alloc] initWithTxFRC:self.txsFetchedDataSource.fetchedResultsController - txDataProvider:self.txDataProvider - friendToMeRequest:shouldShowContactRequests ? friendToMe : nil - meToFriendRequest:shouldShowContactRequests ? meToFriend : nil - friendBlockchainIdentity:friendBlockchainIdentity - myBlockchainIdentity:myBlockchainIdentity]; - - [self.delegate userProfileModelDidUpdate:self]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.h b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.h deleted file mode 100644 index ea8967158..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.h +++ /dev/null @@ -1,34 +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 "DWFetchedResultsDataSource.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSFriendRequestEntity; - -@interface DWProfileTxsFetchedDataSource : DWFetchedResultsDataSource - -- (instancetype)initWithMeToFriendRequest:(nullable DSFriendRequestEntity *)meToFriend - friendToMeRequest:(nullable DSFriendRequestEntity *)friendToMe - inContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.m b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.m deleted file mode 100644 index 6a6ef3fce..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.m +++ /dev/null @@ -1,85 +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 "DWProfileTxsFetchedDataSource.h" - -#import "DWEnvironment.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWProfileTxsFetchedDataSource () - -@property (nullable, readonly, strong, nonatomic) DSFriendRequestEntity *meToFriend; -@property (nullable, readonly, strong, nonatomic) DSFriendRequestEntity *friendToMe; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWProfileTxsFetchedDataSource - -- (instancetype)initWithMeToFriendRequest:(DSFriendRequestEntity *)meToFriend - friendToMeRequest:(DSFriendRequestEntity *)friendToMe - inContext:(NSManagedObjectContext *)context { - NSAssert(meToFriend || friendToMe, @"Either of requests should exist"); // otherwise FRC would return all DSTxOutputEntity due to empty predicate - self = [super initWithContext:context]; - if (self) { - _meToFriend = meToFriend; - _friendToMe = friendToMe; - } - return self; -} - -- (NSString *)entityName { - return NSStringFromClass(DSTxOutputEntity.class); -} - -- (NSPredicate *)predicate { - NSPredicate *meToFriendPredicate = nil; - NSPredicate *friendToMePredicate = nil; - if (self.meToFriend != nil) { - meToFriendPredicate = [NSPredicate predicateWithFormat:@"localAddress.derivationPath.friendRequest == %@", self.meToFriend]; - } - if (self.friendToMe != nil) { - friendToMePredicate = [NSPredicate predicateWithFormat:@"localAddress.derivationPath.friendRequest == %@", self.friendToMe]; - } - - NSPredicate *predicate = nil; - if (meToFriendPredicate != nil && friendToMePredicate != nil) { - predicate = [NSCompoundPredicate orPredicateWithSubpredicates:@[ meToFriendPredicate, friendToMePredicate ]]; - } - else if (meToFriendPredicate != nil) { - predicate = meToFriendPredicate; - } - else if (friendToMePredicate != nil) { - predicate = friendToMePredicate; - } - NSParameterAssert(predicate); - return predicate; -} - -- (NSArray *)sortDescriptors { - NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] - initWithKey:@"transaction.transactionHash.blockHeight" - ascending:NO]; - NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] - initWithKey:@"transaction.transactionHash.timestamp" - ascending:NO]; - return @[ sortDescriptor1, sortDescriptor2 ]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSource.h b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSource.h deleted file mode 100644 index 7d90ffe26..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSource.h +++ /dev/null @@ -1,32 +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 "DWDPBasicItem.h" - -NS_ASSUME_NONNULL_BEGIN - -@protocol DWUserProfileDataSource - -@property (readonly, nonatomic, assign, getter=isEmpty) BOOL empty; - -@property (readonly, nonatomic, assign) NSUInteger count; - -- (id)itemAtIndexPath:(NSIndexPath *)indexPath; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.h b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.h deleted file mode 100644 index 49b692af6..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.h +++ /dev/null @@ -1,41 +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 "DWTransactionListDataProviderProtocol.h" -#import "DWUserProfileDataSource.h" - -NS_ASSUME_NONNULL_BEGIN - -@class NSFetchedResultsController; -@class DSFriendRequestEntity; -@class DSBlockchainIdentity; - -@interface DWUserProfileDataSourceObject : NSObject - -- (instancetype)initWithTxFRC:(NSFetchedResultsController *)frc - txDataProvider:(id)txDataProvider - friendToMeRequest:(nullable DSFriendRequestEntity *)friendToMe - meToFriendRequest:(nullable DSFriendRequestEntity *)meToFriend - friendBlockchainIdentity:(DSBlockchainIdentity *)friendBlockchainIdentity - myBlockchainIdentity:(DSBlockchainIdentity *)myBlockchainIdentity; - -/// Initialize as empty -- (instancetype)init; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.m b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.m deleted file mode 100644 index 021e4251d..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.m +++ /dev/null @@ -1,216 +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 "DWUserProfileDataSourceObject.h" - -#import "DWDPAcceptedRequestNotificationObject.h" -#import "DWDPOutgoingRequestNotificationObject.h" -#import "DWDPTxObject.h" -#import "DWEnvironment.h" -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUserProfileDataSourceObject () - -@property (readonly, nonatomic, assign) BOOL hasDataToShow; -@property (nullable, readonly, nonatomic, strong) NSFetchedResultsController *frc; -@property (readonly, nonatomic, strong) id txDataProvider; -@property (readonly, nonatomic, strong) DSBlockchainIdentity *friendBlockchainIdentity; - -@property (readonly, nonatomic, strong) NSMutableArray> *items; -@property (nullable, readonly, nonatomic, strong) DWDPAcceptedRequestNotificationObject *incomingNotification; -@property (nullable, readonly, nonatomic, strong) DWDPOutgoingRequestNotificationObject *outgoingNotification; -@property (nonatomic, assign) BOOL incomingNotificationAdded; -@property (nonatomic, assign) BOOL outgoingNotificationAdded; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserProfileDataSourceObject - -- (instancetype)initWithTxFRC:(NSFetchedResultsController *)frc - txDataProvider:(id)txDataProvider - friendToMeRequest:(nullable DSFriendRequestEntity *)friendToMe - meToFriendRequest:(nullable DSFriendRequestEntity *)meToFriend - friendBlockchainIdentity:(DSBlockchainIdentity *)friendBlockchainIdentity - myBlockchainIdentity:(DSBlockchainIdentity *)myBlockchainIdentity { - self = [super init]; - if (self) { - _frc = frc; - _txDataProvider = txDataProvider; - _friendBlockchainIdentity = friendBlockchainIdentity; - _items = [NSMutableArray array]; - _hasDataToShow = (frc != nil) || (friendToMe != nil) || (meToFriend != nil); - - BOOL isFriendInitiated; - if (friendToMe && meToFriend) { - isFriendInitiated = friendToMe.timestamp < meToFriend.timestamp; - } - else if (friendToMe) { - isFriendInitiated = YES; - } - else { - isFriendInitiated = NO; - } - - if (friendToMe) { - _incomingNotification = - [[DWDPAcceptedRequestNotificationObject alloc] initWithFriendRequestEntity:friendToMe - blockchainIdentity:friendBlockchainIdentity - isInitiatedByThem:isFriendInitiated]; - } - else { - // mark it as added to the list to skip - _incomingNotificationAdded = YES; - } - - if (meToFriend) { - _outgoingNotification = - [[DWDPOutgoingRequestNotificationObject alloc] initWithFriendRequestEntity:meToFriend - blockchainIdentity:myBlockchainIdentity - isInitiatedByMe:!isFriendInitiated]; - } - else { - // mark it as added to the list to skip - _outgoingNotificationAdded = YES; - } - } - return self; -} - -- (instancetype)init { - self = [super init]; - if (self) { - // empty, _hasDataToShow == NO - } - return self; -} - -- (BOOL)isEmpty { - return self.count == 0; -} - -- (NSUInteger)count { - if (self.hasDataToShow == NO) { - return 0; - } - - NSUInteger count = self.frc.sections.firstObject.numberOfObjects; - if (self.incomingNotification) { - count += 1; - } - if (self.outgoingNotification) { - count += 1; - } - return count; -} - -- (id)itemAtIndexPath:(NSIndexPath *)indexPath { - if (self.hasDataToShow == NO) { - NSAssert(NO, @"Invalid data source usage. Check `count` or `isEmpty` first."); - return nil; - } - - if (self.items.count == indexPath.item) { - NSInteger item = indexPath.item; - if (self.incomingNotification && self.incomingNotificationAdded) { - item -= 1; - } - if (self.outgoingNotification && self.outgoingNotificationAdded) { - item -= 1; - } - NSAssert(item >= 0, @"Inconsistent data source state: item index is out of bounds"); - - const NSUInteger count = self.frc.sections.firstObject.numberOfObjects; - if (item < count) { - NSIndexPath *txIndexPath = [NSIndexPath indexPathForItem:item inSection:0]; - DSTxOutputEntity *txOutputEntity = [self.frc objectAtIndexPath:txIndexPath]; - DSTransaction *transaction = [txOutputEntity.transaction transaction]; - NSDate *txDate = transaction.date; - - DWDPTxObject *txObject = [[DWDPTxObject alloc] initWithTransaction:transaction - dataProvider:self.txDataProvider - username:self.friendBlockchainIdentity.currentDashpayUsername]; - - if (self.incomingNotificationAdded == NO && [self isNotificationNewerThan:self.incomingNotification txDate:txDate]) { - [self.items addObject:self.incomingNotification]; - self.incomingNotificationAdded = YES; - - // optimization: since we already have constructed DSTransaction add it to the list - [self.items addObject:txObject]; - } - if (self.outgoingNotificationAdded == NO && [self isNotificationNewerThan:self.outgoingNotification txDate:txDate]) { - [self.items addObject:self.outgoingNotification]; - self.outgoingNotificationAdded = YES; - - // optimization: since we already have constructed DSTransaction add it to the list - [self.items addObject:txObject]; - } - else { - [self.items addObject:txObject]; - } - } - else { - [self appendAnyNotificationToTheItems]; - } - } - - return self.items[indexPath.item]; -} - -- (void)appendAnyNotificationToTheItems { - if (self.incomingNotification && self.outgoingNotification) { - // incoming.date > outgoing.date - const BOOL isIncomingNewer = ([self.incomingNotification.date compare:self.outgoingNotification.date] == NSOrderedDescending); - // list is sorted by the most recent date, add one which is newer - if (self.incomingNotificationAdded == NO && (isIncomingNewer || self.outgoingNotificationAdded == YES)) { - [self.items addObject:self.incomingNotification]; - self.incomingNotificationAdded = YES; - } - else { - NSAssert(self.outgoingNotificationAdded == NO, @"Outgoing notification has already been handled"); - [self.items addObject:self.outgoingNotification]; - self.outgoingNotificationAdded = YES; - } - } - else if (self.incomingNotificationAdded == NO) { - [self.items addObject:self.incomingNotification]; - self.incomingNotificationAdded = YES; - } - else if (self.outgoingNotificationAdded == NO) { - [self.items addObject:self.outgoingNotification]; - self.outgoingNotificationAdded = YES; - } - else { - NSAssert(NO, @"Inconsistent data source state. We should be able to add any of notifications."); - } -} - -- (BOOL)isNotificationNewerThan:(id)notification txDate:(NSDate *)txDate { - NSParameterAssert(notification); - NSParameterAssert(txDate); - - if (notification == nil) { - return NO; - } - - return ([notification.date compare:txDate] == NSOrderedDescending); -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWPendingContactInfoView.h b/DashWallet/Sources/UI/DashPay/Profile/Views/DWPendingContactInfoView.h new file mode 100644 index 000000000..341891c73 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWPendingContactInfoView.h @@ -0,0 +1,29 @@ +// +// 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 + +@interface DWPendingContactInfoView : UIView + +- (void)setAsSendingRequest; +- (void)setAsPendingRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWPendingContactInfoView.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWPendingContactInfoView.m new file mode 100644 index 000000000..a651b65e7 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWPendingContactInfoView.m @@ -0,0 +1,111 @@ +// +// 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 "DWPendingContactInfoView.h" + +#import "DWHourGlassAnimationView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWPendingContactInfoView () + +@property (readonly, nonatomic, strong) DWHourGlassAnimationView *animationView; +@property (readonly, nonatomic, strong) UIImageView *iconImageView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWPendingContactInfoView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_disabledButtonColor]; + + self.layer.masksToBounds = YES; + self.layer.cornerRadius = 8; + + DWHourGlassAnimationView *animationView = [[DWHourGlassAnimationView alloc] initWithFrame:CGRectZero]; + animationView.translatesAutoresizingMaskIntoConstraints = NO; + _animationView = animationView; + + UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dp_pending_contact"]]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.contentMode = UIViewContentModeCenter; + _iconImageView = imageView; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.numberOfLines = 0; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.adjustsFontForContentSizeCategory = YES; + _titleLabel = titleLabel; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + [contentView addSubview:animationView]; + [contentView addSubview:imageView]; + [contentView addSubview:titleLabel]; + [self addSubview:contentView]; + + [NSLayoutConstraint activateConstraints:@[ + [contentView.topAnchor constraintEqualToAnchor:self.topAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], + [contentView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [contentView.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintGreaterThanOrEqualToAnchor:contentView.trailingAnchor], + + [animationView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [animationView.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor], + [imageView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [imageView.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor], + [titleLabel.leadingAnchor constraintEqualToAnchor:animationView.trailingAnchor + constant:15.0], + [titleLabel.leadingAnchor constraintEqualToAnchor:imageView.trailingAnchor + constant:15.0], + [contentView.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + ]]; + } + return self; +} + +- (void)setAsSendingRequest { + self.iconImageView.hidden = YES; + + self.animationView.hidden = NO; + [self.animationView startAnimating]; + + self.titleLabel.text = NSLocalizedString(@"Sending Contact Request", nil); +} + +- (void)setAsPendingRequest { + self.animationView.hidden = YES; + [self.animationView stopAnimating]; + + self.iconImageView.hidden = NO; + + self.titleLabel.text = NSLocalizedString(@"Contact Request Pending", nil); +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.h b/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.h deleted file mode 100644 index 263af2079..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.h +++ /dev/null @@ -1,26 +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 "DWListCollectionLayout.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWStretchyHeaderListCollectionLayout : DWListCollectionLayout - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.m deleted file mode 100644 index 8376d89bd..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.m +++ /dev/null @@ -1,68 +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 "DWStretchyHeaderListCollectionLayout.h" - -@implementation DWStretchyHeaderListCollectionLayout - -- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { - NSArray *layoutAttributes = [[super layoutAttributesForElementsInRect:rect] copy]; - - for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) { - if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader] && - attributes.indexPath.section == 0) { - UICollectionView *collectionView = self.collectionView; - const CGFloat contentOffsetY = collectionView.contentOffset.y; - if (collectionView != nil && contentOffsetY < 0) { - const CGFloat width = CGRectGetWidth(collectionView.bounds); - const CGFloat height = CGRectGetHeight(attributes.frame) - contentOffsetY; - attributes.frame = CGRectMake(0, contentOffsetY, width, height); - } - } - } - - return layoutAttributes; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { - UICollectionViewLayoutAttributes *superAttributes = [super layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath]; - if ([superAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader] && - superAttributes.indexPath.section == 0) { - UICollectionViewLayoutAttributes *attributes = [superAttributes copy]; - UICollectionView *collectionView = self.collectionView; - const CGFloat contentOffsetY = collectionView.contentOffset.y; - if (collectionView != nil && contentOffsetY < 0) { - const CGFloat width = CGRectGetWidth(collectionView.bounds); - const CGFloat height = CGRectGetHeight(attributes.frame) - contentOffsetY; - attributes.frame = CGRectMake(0, contentOffsetY, width, height); - } - return attributes; - } - else { - return superAttributes; - } -} - -- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { - const CGRect oldBounds = self.collectionView.bounds; - if (newBounds.origin.y <= 0 || CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) { - return YES; - } - return NO; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.m deleted file mode 100644 index 6e3d3464b..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.m +++ /dev/null @@ -1,219 +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 "DWUserProfileContactActionsCell.h" - -#import "DWActionButton.h" -#import "DWUIKit.h" -#import "DWUserProfileModel.h" - -NS_ASSUME_NONNULL_BEGIN - -static CGFloat const BUTTON_HEIGHT = 38.0; - -@interface DWUserProfileContactActionsCell () - -@property (readonly, nonatomic, strong) UILabel *titleLabel; -@property (readonly, nonatomic, strong) UIButton *mainButton; -@property (readonly, nonatomic, strong) UIButton *secondaryButton; -@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; - -@property (nullable, nonatomic, strong) NSLayoutConstraint *contentWidthConstraint; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserProfileContactActionsCell - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - UIView *contentView = [[UIView alloc] init]; - contentView.translatesAutoresizingMaskIntoConstraints = NO; - contentView.backgroundColor = self.backgroundColor; - [self.contentView addSubview:contentView]; - - UILabel *titleLabel = [[UILabel alloc] init]; - titleLabel.translatesAutoresizingMaskIntoConstraints = NO; - titleLabel.backgroundColor = self.backgroundColor; - titleLabel.textColor = [UIColor dw_darkTitleColor]; - titleLabel.textAlignment = NSTextAlignmentCenter; - titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; - titleLabel.adjustsFontForContentSizeCategory = YES; - titleLabel.numberOfLines = 0; - [contentView addSubview:titleLabel]; - _titleLabel = titleLabel; - - DWActionButton *mainButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; - mainButton.translatesAutoresizingMaskIntoConstraints = NO; - mainButton.accentColor = [UIColor dw_greenColor]; - mainButton.small = YES; - [mainButton addTarget:self action:@selector(mainButtonAction:) forControlEvents:UIControlEventTouchUpInside]; - _mainButton = mainButton; - - DWActionButton *secondaryButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; - secondaryButton.translatesAutoresizingMaskIntoConstraints = NO; - secondaryButton.accentColor = [UIColor dw_quaternaryTextColor]; - secondaryButton.small = YES; - [mainButton addTarget:self action:@selector(secondaryButtonAction:) forControlEvents:UIControlEventTouchUpInside]; - _secondaryButton = secondaryButton; - - UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; - activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; - activityIndicatorView.color = [UIColor dw_dashBlueColor]; - activityIndicatorView.hidesWhenStopped = NO; - [activityIndicatorView startAnimating]; - _activityIndicatorView = activityIndicatorView; - - UIStackView *actionsStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ mainButton, secondaryButton, activityIndicatorView ]]; - actionsStackView.translatesAutoresizingMaskIntoConstraints = NO; - actionsStackView.axis = UILayoutConstraintAxisHorizontal; - actionsStackView.spacing = 10.0; - actionsStackView.alignment = UIStackViewAlignmentCenter; - - UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ actionsStackView ]]; - stackView.translatesAutoresizingMaskIntoConstraints = NO; - stackView.axis = UILayoutConstraintAxisVertical; - stackView.alignment = UIStackViewAlignmentCenter; - [contentView addSubview:stackView]; - - UIView *separatorView = [[UIView alloc] init]; - separatorView.translatesAutoresizingMaskIntoConstraints = NO; - separatorView.backgroundColor = [UIColor dw_separatorLineColor]; - [contentView addSubview:separatorView]; - - UILayoutGuide *guide = self.contentView.layoutMarginsGuide; - - [titleLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - - [NSLayoutConstraint activateConstraints:@[ - [contentView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor], - [contentView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], - [self.contentView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], - [self.contentView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], - - [titleLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor - constant:16.0], - [titleLabel.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], - [guide.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], - - [stackView.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor - constant:12.0], - [stackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], - [guide.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor], - - [mainButton.heightAnchor constraintEqualToConstant:BUTTON_HEIGHT], - [secondaryButton.heightAnchor constraintEqualToConstant:BUTTON_HEIGHT], - - [separatorView.topAnchor constraintEqualToAnchor:stackView.bottomAnchor - constant:20.0], - [separatorView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], - [guide.trailingAnchor constraintEqualToAnchor:separatorView.trailingAnchor], - [separatorView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], - [separatorView.heightAnchor constraintEqualToConstant:1.0], - - (_contentWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:200]), - ]]; - } - return self; -} - -- (CGFloat)contentWidth { - return self.contentWidthConstraint.constant; -} - -- (void)setContentWidth:(CGFloat)contentWidth { - self.contentWidthConstraint.constant = contentWidth; -} - -- (void)setModel:(DWUserProfileModel *)model { - _model = model; - - [self configureForIncomingStatus]; - - [self updateState:self.model.requestState]; -} - -- (void)prepareForReuse { - [super prepareForReuse]; - - [self.activityIndicatorView startAnimating]; -} - -#pragma mark - Private - -- (void)configureForIncomingStatus { - NSMutableAttributedString *mutableTitle = [[NSMutableAttributedString alloc] init]; - - NSAttributedString *username = [[NSAttributedString alloc] initWithString:self.model.username - attributes:@{ - NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline], - }]; - NSAttributedString *description = [[NSAttributedString alloc] - initWithString:NSLocalizedString(@"has requested to be your friend", @"Username has requested to be your friend") - attributes:@{ - NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody], - }]; - - [mutableTitle beginEditing]; - [mutableTitle appendAttributedString:username]; - [mutableTitle appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; - [mutableTitle appendAttributedString:description]; - [mutableTitle endEditing]; - - self.titleLabel.attributedText = mutableTitle; - [self.mainButton setTitle:NSLocalizedString(@"Accept", nil) forState:UIControlStateNormal]; - [self.secondaryButton setTitle:NSLocalizedString(@"Ignore", nil) forState:UIControlStateNormal]; -} - -- (void)mainButtonAction:(UIButton *)sender { - [self.delegate userProfileContactActionsCell:self mainButtonAction:sender]; -} - -- (void)secondaryButtonAction:(UIButton *)sender { - [self.delegate userProfileContactActionsCell:self secondaryButtonAction:sender]; -} - -// request state is used -- (void)updateState:(DWUserProfileModelState)state { - switch (state) { - case DWUserProfileModelState_None: - case DWUserProfileModelState_Error: - self.mainButton.hidden = NO; - self.secondaryButton.hidden = NO; - self.activityIndicatorView.hidden = YES; - - break; - case DWUserProfileModelState_Loading: - self.mainButton.hidden = YES; - self.secondaryButton.hidden = YES; - self.activityIndicatorView.hidden = NO; - - break; - case DWUserProfileModelState_Done: - self.mainButton.hidden = YES; - self.secondaryButton.hidden = YES; - self.activityIndicatorView.hidden = YES; - - break; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.h b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.h deleted file mode 100644 index 0d08f32cd..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.h +++ /dev/null @@ -1,41 +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 -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DWUserProfileModel; -@class DWUserProfileHeaderView; - -@protocol DWUserProfileHeaderViewDelegate - -- (void)userProfileHeaderView:(DWUserProfileHeaderView *)view actionButtonAction:(UIButton *)sender; - -@end - -@interface DWUserProfileHeaderView : KVOUICollectionReusableView - -@property (nullable, nonatomic, strong) DWUserProfileModel *model; -@property (nullable, nonatomic, weak) id delegate; - -- (void)setScrollingPercent:(float)percent; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.m deleted file mode 100644 index b744acd12..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.m +++ /dev/null @@ -1,314 +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 "DWUserProfileHeaderView.h" - -#import "DWActionButton.h" -#import "DWDPAvatarView.h" -#import "DWUIKit.h" -#import "DWUserProfileModel.h" - -NS_ASSUME_NONNULL_BEGIN - -static CGFloat const AVATAR_SIZE = 128.0; -static CGFloat const BUTTON_HEIGHT = 40.0; - -@interface DWUserProfileHeaderView () - -@property (readonly, nonatomic, strong) UIView *centerContentView; -@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; -@property (readonly, nonatomic, strong) UILabel *detailsLabel; -@property (readonly, nonatomic, strong) UIView *bottomContentView; -@property (readonly, nonatomic, strong) UILabel *pendingLabel; -@property (readonly, nonatomic, strong) DWActionButton *actionButton; -@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserProfileHeaderView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_backgroundColor]; - - UIView *topContainerView = [[UIView alloc] init]; - topContainerView.translatesAutoresizingMaskIntoConstraints = NO; - topContainerView.backgroundColor = self.backgroundColor; -#ifdef DEBUG - topContainerView.accessibilityIdentifier = @"topContainerView"; -#endif /* DEBUG */ - [self addSubview:topContainerView]; - - UIView *centerContentView = [[UIView alloc] init]; - centerContentView.translatesAutoresizingMaskIntoConstraints = NO; - centerContentView.backgroundColor = self.backgroundColor; -#ifdef DEBUG - centerContentView.accessibilityIdentifier = @"centerContentView"; -#endif /* DEBUG */ - [topContainerView addSubview:centerContentView]; - _centerContentView = centerContentView; - - DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:CGRectZero]; - avatarView.translatesAutoresizingMaskIntoConstraints = NO; - avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; -#ifdef DEBUG - avatarView.accessibilityIdentifier = @"avatarView"; -#endif /* DEBUG */ - [centerContentView addSubview:avatarView]; - _avatarView = avatarView; - - UILabel *detailsLabel = [[UILabel alloc] init]; - detailsLabel.translatesAutoresizingMaskIntoConstraints = NO; - detailsLabel.backgroundColor = self.backgroundColor; - detailsLabel.textColor = [UIColor dw_darkTitleColor]; - detailsLabel.textAlignment = NSTextAlignmentCenter; - detailsLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; - detailsLabel.adjustsFontForContentSizeCategory = YES; - detailsLabel.numberOfLines = 0; - [centerContentView addSubview:detailsLabel]; - _detailsLabel = detailsLabel; - - UIView *bottomContentView = [[UIView alloc] init]; - bottomContentView.translatesAutoresizingMaskIntoConstraints = NO; - bottomContentView.backgroundColor = self.backgroundColor; -#ifdef DEBUG - bottomContentView.accessibilityIdentifier = @"bottomContentView"; -#endif /* DEBUG */ - [self addSubview:bottomContentView]; - _bottomContentView = bottomContentView; - - UIView *bottomGrayView = [[UIView alloc] init]; - bottomGrayView.translatesAutoresizingMaskIntoConstraints = NO; - bottomGrayView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; -#ifdef DEBUG - bottomGrayView.accessibilityIdentifier = @"bottomGrayView"; -#endif /* DEBUG */ - [bottomContentView addSubview:bottomGrayView]; - - UILabel *pendingLabel = [[UILabel alloc] init]; - pendingLabel.translatesAutoresizingMaskIntoConstraints = NO; - pendingLabel.attributedText = [self.class pendingInfo]; - pendingLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; - pendingLabel.textAlignment = NSTextAlignmentCenter; - pendingLabel.textColor = [UIColor dw_orangeColor]; - _pendingLabel = pendingLabel; - - UIStackView *pendingStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ pendingLabel ]]; - pendingStackView.translatesAutoresizingMaskIntoConstraints = NO; - pendingStackView.axis = UILayoutConstraintAxisVertical; - [bottomContentView addSubview:pendingStackView]; - - DWActionButton *actionButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; - actionButton.translatesAutoresizingMaskIntoConstraints = NO; - [actionButton addTarget:self action:@selector(actionButtonAction:) forControlEvents:UIControlEventTouchUpInside]; - [actionButton setTitle:NSLocalizedString(@"Pay", nil) forState:UIControlStateNormal]; - [bottomContentView addSubview:actionButton]; - _actionButton = actionButton; - - UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge]; - activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; - activityIndicatorView.color = [UIColor dw_dashBlueColor]; - [bottomContentView addSubview:activityIndicatorView]; - _activityIndicatorView = activityIndicatorView; - - UILayoutGuide *guide = self.layoutMarginsGuide; - - const CGFloat buttonPadding = 16.0; - const CGFloat spacing = 20.0; - - [detailsLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - forAxis:UILayoutConstraintAxisVertical]; - - [NSLayoutConstraint activateConstraints:@[ - [topContainerView.topAnchor constraintEqualToAnchor:self.topAnchor], - [topContainerView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], - [guide.trailingAnchor constraintEqualToAnchor:topContainerView.trailingAnchor], - - [bottomContentView.topAnchor constraintEqualToAnchor:topContainerView.bottomAnchor - constant:spacing], - [bottomContentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [self.trailingAnchor constraintEqualToAnchor:bottomContentView.trailingAnchor], - [self.bottomAnchor constraintEqualToAnchor:bottomContentView.bottomAnchor], - - [centerContentView.topAnchor constraintGreaterThanOrEqualToAnchor:topContainerView.topAnchor], - [centerContentView.leadingAnchor constraintEqualToAnchor:topContainerView.leadingAnchor], - [topContainerView.trailingAnchor constraintEqualToAnchor:centerContentView.trailingAnchor], - [topContainerView.bottomAnchor constraintGreaterThanOrEqualToAnchor:centerContentView.bottomAnchor], - [centerContentView.centerYAnchor constraintEqualToAnchor:topContainerView.centerYAnchor], - - [avatarView.topAnchor constraintEqualToAnchor:centerContentView.topAnchor], - [avatarView.centerXAnchor constraintEqualToAnchor:centerContentView.centerXAnchor], - [avatarView.widthAnchor constraintEqualToConstant:AVATAR_SIZE], - [avatarView.heightAnchor constraintEqualToConstant:AVATAR_SIZE], - - [detailsLabel.topAnchor constraintEqualToAnchor:avatarView.bottomAnchor - constant:spacing], - [detailsLabel.leadingAnchor constraintEqualToAnchor:centerContentView.leadingAnchor], - [centerContentView.trailingAnchor constraintEqualToAnchor:detailsLabel.trailingAnchor], - [centerContentView.bottomAnchor constraintEqualToAnchor:detailsLabel.bottomAnchor], - - [bottomGrayView.topAnchor constraintEqualToAnchor:actionButton.centerYAnchor], - [bottomGrayView.leadingAnchor constraintEqualToAnchor:bottomContentView.leadingAnchor], - [bottomContentView.trailingAnchor constraintEqualToAnchor:bottomGrayView.trailingAnchor], - [bottomContentView.bottomAnchor constraintEqualToAnchor:bottomGrayView.bottomAnchor], - - [pendingStackView.topAnchor constraintEqualToAnchor:bottomContentView.topAnchor], - [pendingStackView.leadingAnchor constraintEqualToAnchor:bottomContentView.leadingAnchor - constant:buttonPadding], - [bottomContentView.trailingAnchor constraintEqualToAnchor:pendingStackView.trailingAnchor - constant:buttonPadding], - - [actionButton.topAnchor constraintEqualToAnchor:pendingStackView.bottomAnchor - constant:spacing], - [actionButton.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor - constant:buttonPadding], - [guide.trailingAnchor constraintEqualToAnchor:actionButton.trailingAnchor - constant:buttonPadding], - [bottomContentView.bottomAnchor constraintEqualToAnchor:actionButton.bottomAnchor - constant:spacing], - [actionButton.heightAnchor constraintEqualToConstant:BUTTON_HEIGHT], - - [activityIndicatorView.centerYAnchor constraintEqualToAnchor:actionButton.centerYAnchor], - [activityIndicatorView.centerXAnchor constraintEqualToAnchor:bottomContentView.centerXAnchor], - ]]; - - [self mvvm_observe:DW_KEYPATH(self, model.state) - with:^(typeof(self) self, id value) { - [self updateState:self.model.state]; - }]; - - [self mvvm_observe:DW_KEYPATH(self, model.requestState) - with:^(typeof(self) self, id value) { - if (self.model.requestState == DWUserProfileModelState_Done) { - [self updateActions]; - } - }]; - } - return self; -} - -- (void)setModel:(DWUserProfileModel *)model { - _model = model; - - [self updateUsername:model.username]; -} - -- (void)setScrollingPercent:(float)percent { - if (percent < 0) { // stretching - const CGFloat scale = MIN(1.5, 1.0 + ABS(percent)); - self.centerContentView.transform = CGAffineTransformMakeScale(scale, scale); - self.centerContentView.alpha = 1.0; - } - else { - self.centerContentView.transform = CGAffineTransformIdentity; - self.centerContentView.alpha = MAX(0.0, 1.0 - percent * 2.5); // x2.5 speed - - const CGFloat threshold = 0.5; - if (percent > threshold) { - const float translatedPercent = (percent - threshold) * (1.0 / (1.0 - threshold)); - const CGFloat clampPercent = MIN(1.0, translatedPercent); - self.actionButton.alpha = 1.0 - clampPercent * 2; // 2x speed - } - else { - self.actionButton.alpha = 1.0; - } - } -} - -#pragma mark - Private - -- (void)updateState:(DWUserProfileModelState)state { - switch (state) { - case DWUserProfileModelState_None: - self.actionButton.hidden = YES; - self.pendingLabel.hidden = YES; - [self.activityIndicatorView stopAnimating]; - - break; - case DWUserProfileModelState_Error: - [self.activityIndicatorView stopAnimating]; - [self updateActions]; - - break; - case DWUserProfileModelState_Loading: - self.actionButton.hidden = YES; - self.pendingLabel.hidden = YES; - [self.activityIndicatorView startAnimating]; - - break; - case DWUserProfileModelState_Done: - [self.activityIndicatorView stopAnimating]; - [self updateActions]; - break; - } -} - -- (void)updateUsername:(NSString *)username { - self.detailsLabel.text = username; - self.avatarView.username = username; - - [self setScrollingPercent:0.0]; -} - -- (void)updateActions { - const DSBlockchainIdentityFriendshipStatus friendshipStatus = self.model.friendshipStatus; - switch (friendshipStatus) { - case DSBlockchainIdentityFriendshipStatus_Unknown: - case DSBlockchainIdentityFriendshipStatus_None: - self.actionButton.hidden = YES; - self.pendingLabel.hidden = YES; - - break; - case DSBlockchainIdentityFriendshipStatus_Outgoing: - self.actionButton.hidden = YES; - self.pendingLabel.hidden = NO; - - break; - case DSBlockchainIdentityFriendshipStatus_Incoming: - case DSBlockchainIdentityFriendshipStatus_Friends: - self.actionButton.hidden = NO; - self.pendingLabel.hidden = YES; - - break; - } -} - -- (void)actionButtonAction:(UIButton *)sender { - [self.delegate userProfileHeaderView:self actionButtonAction:sender]; -} - -+ (NSAttributedString *)pendingInfo { - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - - UIImage *image = [UIImage imageNamed:@"dp_pending_contact"]; - NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init]; - textAttachment.image = image; - textAttachment.bounds = CGRectMake(-3.0, -2.0, image.size.width, image.size.height); - - [result beginEditing]; - [result appendAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]]; - [result appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; - [result appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedString(@"Contact Request Pending", nil)]]; - [result endEditing]; - - return [result copy]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.m deleted file mode 100644 index 2339343d3..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.m +++ /dev/null @@ -1,104 +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 "DWUserProfileNavigationTitleView.h" - -#import "DWDPAvatarView.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -static CGFloat const AVATAR_SIZE = 28.0; -static CGFloat const VIEW_HEIGHT = 44.0; - -@interface DWUserProfileNavigationTitleView () - -@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; -@property (readonly, nonatomic, strong) UILabel *titleLabel; -@property (readonly, nonatomic, strong) NSLayoutConstraint *titleTopConstraint; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserProfileNavigationTitleView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor clearColor]; - self.clipsToBounds = YES; - - DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:CGRectZero]; - avatarView.translatesAutoresizingMaskIntoConstraints = NO; - avatarView.small = YES; - avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; - [self addSubview:avatarView]; - _avatarView = avatarView; - - UILabel *titleLabel = [[UILabel alloc] init]; - titleLabel.translatesAutoresizingMaskIntoConstraints = NO; - titleLabel.backgroundColor = self.backgroundColor; - titleLabel.textColor = [UIColor dw_darkTitleColor]; - titleLabel.font = [UIFont dw_navigationBarTitleFont]; - [self addSubview:titleLabel]; - _titleLabel = titleLabel; - - [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - forAxis:UILayoutConstraintAxisHorizontal]; - - [NSLayoutConstraint activateConstraints:@[ - [avatarView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [avatarView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], - [avatarView.widthAnchor constraintEqualToConstant:AVATAR_SIZE], - [avatarView.heightAnchor constraintEqualToConstant:AVATAR_SIZE], - - (_titleTopConstraint = [titleLabel.topAnchor constraintEqualToAnchor:self.topAnchor]), - [titleLabel.leadingAnchor constraintEqualToAnchor:avatarView.trailingAnchor - constant:10.0], - [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], - [titleLabel.heightAnchor constraintEqualToAnchor:self.heightAnchor], - ]]; - } - return self; -} - -- (CGSize)intrinsicContentSize { - return CGSizeMake(UIViewNoIntrinsicMetric, VIEW_HEIGHT); -} - -- (void)updateWithUsername:(NSString *)username { - self.titleLabel.text = username; - self.avatarView.username = username; - - [self setScrollingPercent:0.0]; -} - -- (void)setScrollingPercent:(float)percent { - const CGFloat threshold = 0.4; - if (percent > threshold) { - const float translatedPercent = (percent - threshold) * (1.0 / (1.0 - threshold)); - self.avatarView.alpha = MIN(1.0, translatedPercent); - self.titleTopConstraint.constant = MAX(0.0, VIEW_HEIGHT * (1.0 - translatedPercent)); - } - else { - self.avatarView.alpha = 0.0; - self.titleTopConstraint.constant = VIEW_HEIGHT; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.m deleted file mode 100644 index 9b648a2ce..000000000 --- a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.m +++ /dev/null @@ -1,336 +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 "DWUserProfileSendRequestCell.h" - -#import "DWActionButton.h" -#import "DWShadowView.h" -#import "DWUIKit.h" -#import "DWUserProfileModel.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUserProfileSendRequestCell () - -@property (readonly, nonatomic, strong) UILabel *textLabel; -@property (readonly, nonatomic, strong) DWActionButton *sendRequestButton; -@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; - -@property (nullable, nonatomic, strong) NSLayoutConstraint *contentWidthConstraint; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUserProfileSendRequestCell - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - self.contentView.backgroundColor = self.backgroundColor; - - DWShadowView *shadowView = [[DWShadowView alloc] initWithFrame:CGRectZero]; - shadowView.translatesAutoresizingMaskIntoConstraints = NO; - [self.contentView addSubview:shadowView]; - - UIView *roundedContentView = [[UIView alloc] initWithFrame:CGRectZero]; - roundedContentView.translatesAutoresizingMaskIntoConstraints = NO; - roundedContentView.backgroundColor = [UIColor dw_backgroundColor]; - roundedContentView.layer.cornerRadius = 8.0; - roundedContentView.layer.masksToBounds = YES; - [shadowView addSubview:roundedContentView]; - - UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dp_friendship"]]; - imageView.translatesAutoresizingMaskIntoConstraints = NO; - - UILabel *textLabel = [[UILabel alloc] init]; - textLabel.translatesAutoresizingMaskIntoConstraints = NO; - textLabel.numberOfLines = 0; - textLabel.textColor = [UIColor dw_darkTitleColor]; - textLabel.textAlignment = NSTextAlignmentCenter; - _textLabel = textLabel; - - DWActionButton *sendRequestButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; - sendRequestButton.translatesAutoresizingMaskIntoConstraints = NO; - [sendRequestButton setImage:[UIImage imageNamed:@"dp_send_request"] forState:UIControlStateNormal]; - [sendRequestButton setTitle:NSLocalizedString(@"Send Contact Request", nil) forState:UIControlStateNormal]; - sendRequestButton.inverted = YES; - [sendRequestButton addTarget:self - action:@selector(sendRequestButtonAction:) - forControlEvents:UIControlEventTouchUpInside]; - _sendRequestButton = sendRequestButton; - - // fire up activity indicator in advance to fix reuse issue - UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; - activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; - activityIndicatorView.color = [UIColor dw_dashBlueColor]; - [activityIndicatorView startAnimating]; - activityIndicatorView.hidesWhenStopped = NO; - _activityIndicatorView = activityIndicatorView; - - UIView *separatorView = [[UIView alloc] init]; - separatorView.translatesAutoresizingMaskIntoConstraints = NO; - separatorView.backgroundColor = [UIColor dw_separatorLineColor]; - [self.contentView addSubview:separatorView]; - - UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ imageView, textLabel, sendRequestButton, activityIndicatorView ]]; - stackView.translatesAutoresizingMaskIntoConstraints = NO; - stackView.axis = UILayoutConstraintAxisVertical; - stackView.spacing = 20.0; - stackView.alignment = UIStackViewAlignmentCenter; - [self.contentView addSubview:stackView]; - - const CGFloat verticalPadding = 5.0; - const CGFloat itemVerticalPadding = 18.0; - const CGFloat itemHorizontalPadding = verticalPadding + 10.0; - const CGFloat separatorTopPadding = itemVerticalPadding * 2; - const CGFloat stackBottomPadding = itemVerticalPadding + separatorTopPadding; - - UILayoutGuide *guide = self.contentView.layoutMarginsGuide; - [NSLayoutConstraint activateConstraints:@[ - [shadowView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor - constant:verticalPadding], - [shadowView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], - [guide.trailingAnchor constraintEqualToAnchor:shadowView.trailingAnchor], - - [separatorView.topAnchor constraintEqualToAnchor:shadowView.bottomAnchor - constant:separatorTopPadding], - [separatorView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], - [guide.trailingAnchor constraintEqualToAnchor:separatorView.trailingAnchor], - [self.contentView.bottomAnchor constraintEqualToAnchor:separatorView.bottomAnchor - constant:verticalPadding], - [separatorView.heightAnchor constraintEqualToConstant:1.0], - - [roundedContentView.topAnchor constraintEqualToAnchor:shadowView.topAnchor], - [roundedContentView.leadingAnchor constraintEqualToAnchor:shadowView.leadingAnchor], - [shadowView.trailingAnchor constraintEqualToAnchor:roundedContentView.trailingAnchor], - [shadowView.bottomAnchor constraintEqualToAnchor:roundedContentView.bottomAnchor], - - [stackView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor - constant:itemVerticalPadding], - [stackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor - constant:itemHorizontalPadding], - [guide.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor - constant:itemHorizontalPadding], - [self.contentView.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor - constant:stackBottomPadding], - (_contentWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:200]), - - [imageView.heightAnchor constraintEqualToConstant:60.0], - [sendRequestButton.heightAnchor constraintEqualToConstant:40.0], - ]]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self - selector:@selector(contentSizeCategoryDidChangeNotification) - name:UIContentSizeCategoryDidChangeNotification - object:nil]; - } - return self; -} - -- (CGFloat)contentWidth { - return self.contentWidthConstraint.constant; -} - -- (void)setContentWidth:(CGFloat)contentWidth { - self.contentWidthConstraint.constant = contentWidth; -} - -- (void)setHighlighted:(BOOL)highlighted { - [super setHighlighted:highlighted]; - - [self dw_pressedAnimation:DWPressedAnimationStrength_Light pressed:highlighted]; -} - -- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { - [super traitCollectionDidChange:previousTraitCollection]; - - [self reloadAttributedData]; -} - -- (void)prepareForReuse { - [super prepareForReuse]; - - [self.activityIndicatorView startAnimating]; -} - -- (void)setModel:(DWUserProfileModel *)model { - _model = model; - - [self reloadAttributedData]; -} - -#pragma mark - Notifications - -- (void)contentSizeCategoryDidChangeNotification { - [self reloadAttributedData]; -} - -#pragma mark - Private - -- (void)reloadAttributedData { - [self updateState:self.model.requestState]; -} - -- (void)sendRequestButtonAction:(UIButton *)sender { - [self.delegate userProfileSendRequestCell:self sendRequestButtonAction:sender]; -} - -// request state is used -- (void)updateState:(DWUserProfileModelState)state { - [self updateActions]; - - switch (state) { - case DWUserProfileModelState_None: - self.activityIndicatorView.hidden = YES; - - break; - case DWUserProfileModelState_Error: - self.activityIndicatorView.hidden = YES; - - break; - case DWUserProfileModelState_Loading: - self.sendRequestButton.hidden = YES; - self.activityIndicatorView.hidden = NO; - - break; - case DWUserProfileModelState_Done: - self.activityIndicatorView.hidden = YES; - - break; - } -} - -- (void)updateActions { - const DSBlockchainIdentityFriendshipStatus friendshipStatus = self.model.friendshipStatus; - switch (friendshipStatus) { - case DSBlockchainIdentityFriendshipStatus_Unknown: - self.sendRequestButton.hidden = YES; - self.textLabel.hidden = YES; - self.textLabel.attributedText = nil; - - break; - case DSBlockchainIdentityFriendshipStatus_None: // not a friend nor incoming request - self.sendRequestButton.hidden = NO; - self.textLabel.hidden = NO; - self.textLabel.attributedText = [self notAContactText]; - - break; - case DSBlockchainIdentityFriendshipStatus_Outgoing: // request was sent, pending - self.sendRequestButton.hidden = YES; - self.textLabel.hidden = NO; - self.textLabel.attributedText = [self pendingContactText]; - - break; - case DSBlockchainIdentityFriendshipStatus_Incoming: - case DSBlockchainIdentityFriendshipStatus_Friends: - self.sendRequestButton.hidden = YES; - self.textLabel.hidden = YES; - self.textLabel.attributedText = nil; - - // cell should be not visible - - break; - } -} - -- (NSAttributedString *)notAContactText { - if (self.model.username == nil) { - return nil; - } - - NSString *full = - [NSString stringWithFormat: - NSLocalizedString(@"Add %@ as your contact to Pay Directly to Username and Retain Mutual Transaction History", - @"Add as your contact..."), - self.model.username]; - - NSString *emphasized1 = - NSLocalizedString(@"Pay Directly to Username", - @"emphasized text in: Add as your contact to Pay Directly to Username and Retain Mutual Transaction History"); - NSString *emphasized2 = - NSLocalizedString(@"Retain Mutual Transaction History", - @"emphasized text in: Add as your contact to Pay Directly to Username and Retain Mutual Transaction History"); - - NSRange range1 = [full rangeOfString:emphasized1]; - NSRange range2 = [full rangeOfString:emphasized2]; - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:full]; - - [result beginEditing]; - - [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody]} - range:NSMakeRange(0, full.length)]; - - if (range1.location != NSNotFound) { - [result removeAttribute:NSFontAttributeName range:range1]; - - [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]} - range:range1]; - } - - if (range2.location != NSNotFound) { - [result removeAttribute:NSFontAttributeName range:range2]; - - [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]} - range:range2]; - } - - [result endEditing]; - - return [result copy]; -} - -- (NSAttributedString *)pendingContactText { - if (self.model.username == nil) { - return nil; - } - - NSString *full = - [NSString stringWithFormat: - NSLocalizedString(@"Once %@ accepts your request you can Pay Directly to Username", - @"Once accepts your request..."), - self.model.username]; - - NSString *emphasized1 = - NSLocalizedString(@"Pay Directly to Username", - @"emphasized text in: Add as your contact to Pay Directly to Username and Retain Mutual Transaction History"); - - NSRange range1 = [full rangeOfString:emphasized1]; - - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:full]; - - [result beginEditing]; - - [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody]} - range:NSMakeRange(0, full.length)]; - - if (range1.location != NSNotFound) { - [result removeAttribute:NSFontAttributeName range:range1]; - - [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]} - range:range1]; - } - - [result endEditing]; - - return [result copy]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/DWConfirmUsernameViewController.m b/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/DWConfirmUsernameViewController.m deleted file mode 100644 index 23c1250cb..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/ConfirmUsername/DWConfirmUsernameViewController.m +++ /dev/null @@ -1,75 +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 "DWConfirmUsernameViewController.h" - -#import "DWConfirmUsernameContentView.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWConfirmUsernameViewController () - -@property (nonatomic, strong) DWConfirmUsernameContentView *confirmUsernameView; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWConfirmUsernameViewController - -- (instancetype)initWithUsername:(NSString *)username { - self = [super init]; - if (self) { - _username = [username copy]; - } - return self; -} - -+ (BOOL)isActionButtonInNavigationBar { - return NO; -} - -- (NSString *)actionButtonTitle { - return NSLocalizedString(@"Confirm & Pay", nil); -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - [self setModalTitle:NSLocalizedString(@"Confirm", nil)]; - - self.actionButton.enabled = NO; - - self.confirmUsernameView = [[DWConfirmUsernameContentView alloc] initWithFrame:CGRectZero]; - self.confirmUsernameView.username = self.username; - [self.confirmUsernameView.confirmationCheckbox addTarget:self - action:@selector(confirmationCheckboxAction:) - forControlEvents:UIControlEventValueChanged]; - - [self setupModalContentView:self.confirmUsernameView]; -} - -- (void)actionButtonAction:(id)sender { - self.actionButton.enabled = NO; - [self.delegate confirmUsernameViewControllerDidConfirm:self]; -} - -- (void)confirmationCheckboxAction:(DWCheckbox *)sender { - self.actionButton.enabled = sender.isOn; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWCreateUsernameViewController.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWCreateUsernameViewController.m deleted file mode 100644 index cdc9b1650..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWCreateUsernameViewController.m +++ /dev/null @@ -1,139 +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 "DWCreateUsernameViewController.h" - -#import "DWInputUsernameViewController.h" -#import "DWUIKit.h" -#import "UIViewController+DWEmbedding.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWCreateUsernameViewController () - -@property (readonly, nonatomic, strong) id dashPayModel; - -@property (null_resettable, nonatomic, strong) UIScrollView *scrollView; -@property (null_resettable, nonatomic, strong) DWInputUsernameViewController *inputUsername; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWCreateUsernameViewController - -- (instancetype)initWithDashPayModel:(id)dashPayModel { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _dashPayModel = dashPayModel; - } - return self; -} - -- (NSAttributedString *)attributedTitle { - NSDictionary *regularAttributes = @{ - NSFontAttributeName : [UIFont dw_regularFontOfSize:22.0], - NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], - }; - - NSDictionary *emphasizedAttributes = @{ - NSFontAttributeName : [UIFont dw_mediumFontOfSize:22.0], - NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], - }; - - NSAttributedString *chooseYourString = - [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Choose your", @"Choose your Dash username") - attributes:regularAttributes]; - - NSAttributedString *spaceString = - [[NSAttributedString alloc] initWithString:@"\n" - attributes:regularAttributes]; - - NSAttributedString *dashUsernameString = - [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Dash username", nil) - attributes:emphasizedAttributes]; - - NSMutableAttributedString *resultString = [[NSMutableAttributedString alloc] init]; - [resultString beginEditing]; - [resultString appendAttributedString:chooseYourString]; - [resultString appendAttributedString:spaceString]; - [resultString appendAttributedString:dashUsernameString]; - [resultString endEditing]; - - return resultString; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.clipsToBounds = YES; - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - [self.view addSubview:self.scrollView]; - - const BOOL isLandscape = CGRectGetWidth(self.view.bounds) > CGRectGetHeight(self.view.bounds); - - [NSLayoutConstraint activateConstraints:@[ - [self.scrollView.topAnchor constraintEqualToAnchor:self.view.topAnchor], - [self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [self.view.trailingAnchor constraintEqualToAnchor:self.scrollView.trailingAnchor], - [self.view.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor], - [self.scrollView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor], - ]]; - - [self dw_embedChild:self.inputUsername inContainer:self.scrollView]; - - NSLayoutConstraint *heightConstraint = [self.inputUsername.view.heightAnchor constraintEqualToAnchor:self.scrollView.heightAnchor]; - heightConstraint.priority = UILayoutPriorityRequired - 1; - - [NSLayoutConstraint activateConstraints:@[ - heightConstraint, - [self.inputUsername.view.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor] - ]]; -} - -- (UIScrollView *)scrollView { - if (_scrollView == nil) { - _scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; - _scrollView.translatesAutoresizingMaskIntoConstraints = NO; - } - - return _scrollView; -} - -- (DWInputUsernameViewController *)inputUsername { - if (_inputUsername == nil) { - _inputUsername = [[DWInputUsernameViewController alloc] init]; - _inputUsername.delegate = self; - } - - return _inputUsername; -} - -#pragma mark - DWInputUsernameViewControllerDelegate - -- (void)inputUsernameViewControllerRegisterAction:(DWInputUsernameViewController *)inputController { - [self.delegate createUsernameViewController:self registerUsername:inputController.text]; -} - -#pragma mark - Actions - -- (void)cancelButtonAction { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWInputUsernameViewController.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWInputUsernameViewController.m deleted file mode 100644 index 1a2408ffb..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/DWInputUsernameViewController.m +++ /dev/null @@ -1,277 +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 "DWInputUsernameViewController.h" - -#import - -#import "DWActionButton.h" -#import "DWAllowedCharactersUsernameValidationRule.h" -#import "DWBaseActionButtonViewController.h" -#import "DWCheckExistenceUsernameValidationRule.h" -#import "DWDashPayConstants.h" -#import "DWMaxLengthUsernameValidationRule.h" -#import "DWMinLengthUsernameValidationRule.h" -#import "DWTextField.h" -#import "DWUIKit.h" -#import "DWUsernameValidationView.h" -#import "dashwallet-Swift.h" - -static CGFloat const SPACING = 16.0; -static CGFloat const CORNER_RADIUS = 10.0; -static CGFloat const TEXTFIELD_MAX_HEIGHT = 56.0; - -NS_ASSUME_NONNULL_BEGIN - -@interface DWInputUsernameViewController () - -@property (null_resettable, nonatomic, strong) UITextField *textField; -@property (null_resettable, nonatomic, strong) UIStackView *validationContentView; -@property (nonatomic, copy) NSArray *validationViews; -@property (null_resettable, nonatomic, strong) UIButton *registerButton; - -@property (nullable, nonatomic, strong) NSLayoutConstraint *contentBottomConstraint; - -@property (null_resettable, nonatomic, strong) DWCheckExistenceUsernameValidationRule *checkExistenceValidator; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWInputUsernameViewController - -- (NSString *)text { - return self.textField.text; -} - -- (void)setText:(NSString *)text { - self.textField.text = text; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - NSArray *validators = @[ - [[DWMinLengthUsernameValidationRule alloc] init], - [[DWAllowedCharactersUsernameValidationRule alloc] init], - [[DWMaxLengthUsernameValidationRule alloc] init], - self.checkExistenceValidator, - ]; - - NSMutableArray *validationViews = [NSMutableArray array]; - for (DWUsernameValidationRule *validationRule in validators) { - DWUsernameValidationView *validationView = [[DWUsernameValidationView alloc] initWithFrame:CGRectZero]; - validationView.translatesAutoresizingMaskIntoConstraints = NO; - validationView.rule = validationRule; - [self.validationContentView addArrangedSubview:validationView]; - [validationViews addObject:validationView]; - - [validationView.heightAnchor constraintLessThanOrEqualToConstant:26.0].active = YES; - } - self.validationViews = validationViews; - - UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ self.validationContentView ]]; - stackView.translatesAutoresizingMaskIntoConstraints = NO; - stackView.alignment = UIStackViewAlignmentTop; - - [self.view addSubview:self.textField]; - [self.view addSubview:stackView]; - [self.view addSubview:self.registerButton]; - - [self updateValidationContentViewForSize:self.view.bounds.size]; - - UILayoutGuide *marginsGuide = self.view.layoutMarginsGuide; - UILayoutGuide *safeAreaGuide = self.view.safeAreaLayoutGuide; - - const CGFloat bottomPadding = [self.class deviceSpecificBottomPadding]; - // constraint relation is inverted so we can use positive padding values - self.contentBottomConstraint = [safeAreaGuide.bottomAnchor constraintEqualToAnchor:self.registerButton.bottomAnchor - constant:bottomPadding]; - - [NSLayoutConstraint activateConstraints:@[ - [self.textField.topAnchor constraintEqualToAnchor:safeAreaGuide.topAnchor - constant:SPACING], - [self.textField.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], - [marginsGuide.trailingAnchor constraintEqualToAnchor:self.textField.trailingAnchor], - [self.textField.heightAnchor constraintLessThanOrEqualToConstant:TEXTFIELD_MAX_HEIGHT], - - [stackView.topAnchor constraintEqualToAnchor:self.textField.bottomAnchor - constant:SPACING], - [stackView.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], - [marginsGuide.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor], - - [self.registerButton.topAnchor constraintEqualToAnchor:stackView.bottomAnchor - constant:SPACING], - [self.registerButton.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], - [self.registerButton.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], - [self.registerButton.heightAnchor constraintEqualToConstant:DWBottomButtonHeight()], - self.contentBottomConstraint, - ]]; -} - -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - - [coordinator - animateAlongsideTransition:^(id context) { - [self updateValidationContentViewForSize:size]; - } - completion:^(id _Nonnull context){ - - }]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - // pre-layout view to avoid undesired animation if the keyboard is shown while appearing - [self.view layoutIfNeeded]; - [self ka_startObservingKeyboardNotifications]; -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - - [self ka_stopObservingKeyboardNotifications]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - - [self.textField becomeFirstResponder]; -} - -#pragma mark - UITextFieldDelegate - -- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { - BOOL isDone = NO; - if ([string isEqualToString:@"\n"]) { - isDone = YES; - string = @""; - } - NSString *text = [[self.textField.text stringByReplacingCharactersInRange:range withString:string] lowercaseString]; - for (DWUsernameValidationView *validationView in self.validationViews) { - DWUsernameValidationRule *validator = validationView.rule; - [validator validateText:text]; - } - self.registerButton.enabled = NO; - textField.text = text; - - return NO; -} - -#pragma mark - DWCheckExistenceUsernameValidationRuleDelegate - -- (void)checkExistenceUsernameValidationRuleDidValidate:(DWCheckExistenceUsernameValidationRule *)rule { - BOOL canRegister = YES; - for (DWUsernameValidationView *validationView in self.validationViews) { - DWUsernameValidationRule *validator = validationView.rule; - DWUsernameValidationRuleResult result = validator.validationResult; - canRegister &= result != DWUsernameValidationRuleResultInvalid && result != DWUsernameValidationRuleResultInvalidCritical && result != DWUsernameValidationRuleResultEmpty; - } - self.registerButton.enabled = canRegister; -} - -#pragma mark - Keyboard - -- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height - animationDuration:(NSTimeInterval)animationDuration - animationCurve:(UIViewAnimationCurve)animationCurve { - const CGFloat bottomPadding = [self.class deviceSpecificBottomPadding]; - self.contentBottomConstraint.constant = height + bottomPadding; - [self.view layoutIfNeeded]; -} - -#pragma mark - Private - -- (void)updateValidationContentViewForSize:(CGSize)size { - BOOL isLandscape = size.width > size.height; - if (isLandscape) { - self.validationContentView.axis = UILayoutConstraintAxisHorizontal; - } - else { - self.validationContentView.axis = UILayoutConstraintAxisVertical; - } -} - -- (UITextField *)textField { - if (_textField == nil) { - _textField = [[DWTextField alloc] initWithFrame:CGRectZero]; - _textField.translatesAutoresizingMaskIntoConstraints = NO; - _textField.delegate = self; - _textField.backgroundColor = [UIColor dw_backgroundColor]; - _textField.textColor = [UIColor dw_darkTitleColor]; - _textField.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; - _textField.adjustsFontForContentSizeCategory = YES; - _textField.placeholder = NSLocalizedString(@"eg: johndoe", @"Input username textfield placeholder"); - _textField.layer.cornerRadius = CORNER_RADIUS; - _textField.layer.masksToBounds = YES; - _textField.autocorrectionType = UITextAutocorrectionTypeNo; - _textField.autocapitalizationType = UITextAutocapitalizationTypeNone; - _textField.spellCheckingType = UITextSpellCheckingTypeNo; - _textField.smartDashesType = UITextSmartDashesTypeNo; - _textField.smartQuotesType = UITextSmartQuotesTypeNo; - _textField.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; - _textField.returnKeyType = UIReturnKeyDone; - } - - return _textField; -} - -- (UIStackView *)validationContentView { - if (_validationContentView == nil) { - _validationContentView = [[UIStackView alloc] initWithFrame:CGRectZero]; - _validationContentView.translatesAutoresizingMaskIntoConstraints = NO; - _validationContentView.axis = UILayoutConstraintAxisVertical; - _validationContentView.spacing = 6.0; - _validationContentView.distribution = UIStackViewDistributionFillEqually; - } - - return _validationContentView; -} - -- (UIButton *)registerButton { - if (_registerButton == nil) { - _registerButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; - _registerButton.translatesAutoresizingMaskIntoConstraints = NO; - [_registerButton setTitle:NSLocalizedString(@"Register", @"Button title, Register (username)") - forState:UIControlStateNormal]; - [_registerButton addTarget:self - action:@selector(registerButtonAction:) - forControlEvents:UIControlEventTouchUpInside]; - _registerButton.enabled = NO; - } - - return _registerButton; -} - -- (DWCheckExistenceUsernameValidationRule *)checkExistenceValidator { - if (_checkExistenceValidator == nil) { - _checkExistenceValidator = [[DWCheckExistenceUsernameValidationRule alloc] initWithDelegate:self]; - } - return _checkExistenceValidator; -} - -#pragma mark - Actions - -- (void)registerButtonAction:(id)sender { - [self.delegate inputUsernameViewControllerRegisterAction:self]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.h b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.h deleted file mode 100644 index 0970bb65c..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.h +++ /dev/null @@ -1,26 +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 "DWUsernameValidationRule.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWAllowedCharactersUsernameValidationRule : DWUsernameValidationRule - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.m deleted file mode 100644 index 1634a34aa..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.m +++ /dev/null @@ -1,57 +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 "DWAllowedCharactersUsernameValidationRule.h" - -#import "DWUsernameValidationRule+Protected.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWAllowedCharactersUsernameValidationRule () - -@property (readonly, strong, nonatomic) NSCharacterSet *illegalChars; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWAllowedCharactersUsernameValidationRule - -- (instancetype)init { - self = [super init]; - if (self) { - NSCharacterSet *allowedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyz0123456789"]; - _illegalChars = [allowedCharacterSet invertedSet]; - } - return self; -} - -- (NSString *)title { - return NSLocalizedString(@"Letters and numbers only", @"Validation rule"); -} - -- (void)validateText:(NSString *)text { - if (text.length == 0) { - self.validationResult = DWUsernameValidationRuleResultEmpty; - return; - } - - BOOL hasIllegalCharacter = [text rangeOfCharacterFromSet:self.illegalChars].location != NSNotFound; - self.validationResult = hasIllegalCharacter ? DWUsernameValidationRuleResultInvalid : DWUsernameValidationRuleResultValid; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.m deleted file mode 100644 index df45e8096..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.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 "DWCheckExistenceUsernameValidationRule.h" - -#import "DWDashPayConstants.h" -#import "DWEnvironment.h" -#import "DWUsernameValidationRule+Protected.h" - -NS_ASSUME_NONNULL_BEGIN - -static NSTimeInterval VALIDATION_DEBOUNCE_DELAY = 0.4; - -@interface DWCheckExistenceUsernameValidationRule () - -@property (nonatomic, copy) NSDictionary *titleByResult; -@property (nullable, nonatomic, weak) id delegate; - -@property (nullable, nonatomic, copy) NSString *username; -@property (nullable, nonatomic, strong) id request; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWCheckExistenceUsernameValidationRule - -- (instancetype)initWithDelegate:(id)delegate { - self = [super init]; - if (self) { - _delegate = delegate; - - NSMutableDictionary *titleByResult = [NSMutableDictionary dictionary]; - titleByResult[@(DWUsernameValidationRuleResultLoading)] = NSLocalizedString(@"Validating username…", nil); - titleByResult[@(DWUsernameValidationRuleResultValid)] = NSLocalizedString(@"Validating username done", nil); - titleByResult[@(DWUsernameValidationRuleResultError)] = NSLocalizedString(@"Validating username failed", nil); - titleByResult[@(DWUsernameValidationRuleResultInvalidCritical)] = NSLocalizedString(@"Username taken", nil); - self.titleByResult = titleByResult; - } - return self; -} - -- (void)setValidationResult:(DWUsernameValidationRuleResult)validationResult { - [super setValidationResult:validationResult]; - - [self.delegate checkExistenceUsernameValidationRuleDidValidate:self]; -} - -- (NSString *)title { - return self.titleByResult[@(self.validationResult)]; -} - -- (void)validateText:(NSString *)text { - [self.request cancel]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(performValidation) object:nil]; - self.username = text; - - if (text.length < DW_MIN_USERNAME_LENGTH || text.length > DW_MAX_USERNAME_LENGTH) { - self.validationResult = DWUsernameValidationRuleResultHidden; - - return; - } - - self.validationResult = DWUsernameValidationRuleResultLoading; - - [self performSelector:@selector(performValidation) withObject:nil afterDelay:VALIDATION_DEBOUNCE_DELAY]; -} - -#pragma mark - Private - -- (void)performValidation { - [self performValidationWithUsername:self.username]; -} - -- (void)performValidationWithUsername:(NSString *)username { - DSIdentitiesManager *manager = [DWEnvironment sharedInstance].currentChainManager.identitiesManager; - __weak typeof(self) weakSelf = self; - self.request = [manager - searchIdentityByDashpayUsername:username - withCompletion:^(BOOL succeess, DSBlockchainIdentity *_Nullable blockchainIdentity, NSError *_Nullable error) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - NSAssert([NSThread isMainThread], @"Main thread is assumed here"); - - // search query was changed before results arrive, ignore results - if (![strongSelf.username isEqualToString:username]) { - return; - } - - if (succeess) { - if (blockchainIdentity != nil) { - strongSelf.validationResult = DWUsernameValidationRuleResultInvalidCritical; - } - else { - strongSelf.validationResult = DWUsernameValidationRuleResultValid; - } - } - else { - strongSelf.validationResult = DWUsernameValidationRuleResultError; - } - }]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMinLengthUsernameValidationRule.h b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h similarity index 83% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMinLengthUsernameValidationRule.h rename to DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h index cb6558a4f..ec496b710 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMinLengthUsernameValidationRule.h +++ b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWMinLengthUsernameValidationRule : DWUsernameValidationRule +@interface DWFirstUsernameSymbolValidationRule : DWUsernameValidationRule @end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m new file mode 100644 index 000000000..9406fcb87 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m @@ -0,0 +1,44 @@ +// +// 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 "DWFirstUsernameSymbolValidationRule.h" + +#import "DWUsernameValidationRule+Protected.h" + +@implementation DWFirstUsernameSymbolValidationRule + +- (NSString *)title { + return NSLocalizedString(@"Must start and end with a letter or number", @"Validation rule"); +} + +- (void)validateText:(NSString *)text { + if (text.length == 0 || [text rangeOfString:@"-"].location == NSNotFound) { + self.validationResult = DWUsernameValidationRuleResultHidden; + return; + } + + // The user should be able use a hyphen anywhere in the username except the first or last characters + if ([text hasPrefix:@"-"] || [text hasSuffix:@"-"]) { + self.validationResult = DWUsernameValidationRuleResultInvalid; + return; + } + + self.validationResult = DWUsernameValidationRuleResultValid; +} + + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMaxLengthUsernameValidationRule.h b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h similarity index 91% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMaxLengthUsernameValidationRule.h rename to DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h index 333ebdf0c..b2b3f77da 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMaxLengthUsernameValidationRule.h +++ b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWMaxLengthUsernameValidationRule : DWUsernameValidationRule +@interface DWLengthUsernameValidationRule : DWUsernameValidationRule @end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMinLengthUsernameValidationRule.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m similarity index 63% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMinLengthUsernameValidationRule.m rename to DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m index c54c345a2..b631b6bba 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMinLengthUsernameValidationRule.m +++ b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m @@ -15,25 +15,27 @@ // limitations under the License. // -#import "DWMinLengthUsernameValidationRule.h" +#import "DWLengthUsernameValidationRule.h" #import "DWDashPayConstants.h" #import "DWUsernameValidationRule+Protected.h" -@implementation DWMinLengthUsernameValidationRule +@implementation DWLengthUsernameValidationRule - (NSString *)title { - return NSLocalizedString(@"Minimum 3 characters", @"Validation rule"); + return [NSString stringWithFormat:NSLocalizedString(@"Between %ld and %ld characters", @"Validation rule: Between 3 and 24 characters"), DW_MIN_USERNAME_LENGTH, DW_MAX_USERNAME_LENGTH]; } -- (void)validateText:(NSString *_Nullable)text { +- (void)validateText:(NSString *)text { const NSUInteger length = text.length; if (length == 0) { self.validationResult = DWUsernameValidationRuleResultEmpty; return; } - self.validationResult = length >= DW_MIN_USERNAME_LENGTH ? DWUsernameValidationRuleResultValid : DWUsernameValidationRuleResultInvalid; + BOOL isValid = length >= DW_MIN_USERNAME_LENGTH && length <= DW_MAX_USERNAME_LENGTH; + + self.validationResult = isValid ? DWUsernameValidationRuleResultValid : DWUsernameValidationRuleResultInvalid; } @end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMaxLengthUsernameValidationRule.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMaxLengthUsernameValidationRule.m deleted file mode 100644 index d3a38f1fd..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWMaxLengthUsernameValidationRule.m +++ /dev/null @@ -1,33 +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 "DWMaxLengthUsernameValidationRule.h" - -#import "DWDashPayConstants.h" -#import "DWUsernameValidationRule+Protected.h" - -@implementation DWMaxLengthUsernameValidationRule - -- (NSString *)title { - return [NSString stringWithFormat:NSLocalizedString(@"Maximum %ld characters", @"Validation rule: Maximum 24 characters"), DW_MAX_USERNAME_LENGTH]; -} - -- (void)validateText:(NSString *)text { - self.validationResult = text.length <= DW_MAX_USERNAME_LENGTH ? DWUsernameValidationRuleResultHidden : DWUsernameValidationRuleResultInvalid; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule+Protected.h b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule+Protected.h deleted file mode 100644 index 6f3d35139..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule+Protected.h +++ /dev/null @@ -1,28 +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 "DWUsernameValidationRule.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUsernameValidationRule () - -@property (nonatomic, assign) DWUsernameValidationRuleResult validationResult; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule.h b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule.h deleted file mode 100644 index cb2a1a94d..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWUsernameValidationRule.h +++ /dev/null @@ -1,48 +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 - -typedef NS_ENUM(NSUInteger, DWUsernameValidationRuleResult) { - /// No icon, black text - DWUsernameValidationRuleResultEmpty, - /// activity indicator, black text - DWUsernameValidationRuleResultLoading, - /// Checkmark, black text - DWUsernameValidationRuleResultValid, - /// Red cross and black text - DWUsernameValidationRuleResultInvalid, - /// Red cross and red text - DWUsernameValidationRuleResultInvalidCritical, - /// Red cross and red text - DWUsernameValidationRuleResultError, - /// View is hidden - DWUsernameValidationRuleResultHidden, -}; - -@interface DWUsernameValidationRule : NSObject - -@property (readonly, nonatomic, copy) NSString *title; -@property (readonly, nonatomic, assign) DWUsernameValidationRuleResult validationResult; - -- (void)validateText:(NSString *_Nullable)text; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWPlanetarySystemView.h b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWPlanetarySystemView.h deleted file mode 100644 index 9155cc0a3..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWPlanetarySystemView.h +++ /dev/null @@ -1,62 +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 - -#pragma mark - Model - -@interface DWPlanetObject : NSObject - -/// The planet image. -@property (nullable, nonatomic, strong) UIImage *image; -/// Custom view to display as a planet -@property (nullable, nonatomic, strong) UIView *customView; - -/// The speed of animation. -@property (nonatomic, assign) CGFloat speed; -/// The duration of the complete rotation along the orbit. -@property (nonatomic, assign) CGFloat duration; -/// The animation time offset in percentage. -@property (nonatomic, assign) CGFloat offset; -/// Size of the corresponding UIImageView. -@property (nonatomic, assign) CGSize size; -/// The number of the orbit. Must be less than `numberOfOrbits` of the view. -@property (nonatomic, assign) NSInteger orbit; -/// The rotation direction. -@property (nonatomic, assign) BOOL rotateClockwise; - -@end - -#pragma mark - View - -@interface DWPlanetarySystemView : UIView - -@property (nonatomic, assign) NSInteger numberOfOrbits; -@property (nonatomic, assign) CGFloat centerOffset; -@property (nonatomic, assign) CGFloat borderOffset; -@property (nonatomic, assign) CGFloat lineWidth; -@property (nonatomic, copy) NSArray *colors; - -@property (nullable, nonatomic, copy) NSArray *planets; - -- (void)showInitialAnimation; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWPlanetarySystemView.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWPlanetarySystemView.m deleted file mode 100644 index 28903cfb4..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWPlanetarySystemView.m +++ /dev/null @@ -1,241 +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 "DWPlanetarySystemView.h" - -#pragma mark - Model - -@implementation DWPlanetObject -@end - -#pragma mark - View - -static const CFTimeInterval ORBIT_ANIMATION_DURATION = 0.085; -static const CFTimeInterval ORBIT_ANIMATION_DELAY_BETWEEN = 0.25; - -NS_ASSUME_NONNULL_BEGIN - -@interface DWPlanetarySystemView () - -@property (strong, nonatomic) NSMutableArray *orbits; -@property (strong, nonatomic) NSMutableArray *orbitLayers; -@property (strong, nonatomic) NSMutableArray *planetViews; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWPlanetarySystemView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setup]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setup]; - } - return self; -} - -- (void)setNumberOfOrbits:(NSInteger)numberOfOrbits { - _numberOfOrbits = numberOfOrbits; - - [self rebornTheUniverse]; -} - -- (void)setCenterOffset:(CGFloat)centerOffset { - _centerOffset = centerOffset; - - [self rebornTheUniverse]; -} - -- (void)setBorderOffset:(CGFloat)borderOffset { - _borderOffset = borderOffset; - - [self rebornTheUniverse]; -} - -- (void)setLineWidth:(CGFloat)lineWidth { - _lineWidth = lineWidth; - - [self rebornTheUniverse]; -} - -- (void)setColors:(NSArray *)colors { - _colors = [colors copy]; - - [self rebornTheUniverse]; -} - -- (void)setPlanets:(NSArray *)planets { - _planets = [planets copy]; - - [self rebornTheUniverse]; -} - -- (void)showInitialAnimation { - [self showOrbitsAnimated]; - - const NSUInteger orbitsCount = self.orbits.count; - // The actual delay is: - // `orbitsCount * ORBIT_ANIMATION_DURATION + (orbitsCount - 1) * ORBIT_ANIMATION_DELAY_BETWEEN` - // But we starting to show planets a bit before orbit animation is finished. - const CFTimeInterval delay = orbitsCount * ORBIT_ANIMATION_DURATION; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self animatePlanetsWithRepeatCount:0.0]; - }); -} - -- (void)layoutSubviews { - [super layoutSubviews]; - - [self rebornTheUniverse]; -} - -#pragma mark Private - -- (void)setup { - _colors = @[]; - _orbits = [NSMutableArray array]; - _orbitLayers = [NSMutableArray array]; - _planetViews = [NSMutableArray array]; -} - -- (void)showOrbitsAnimated { - const CFTimeInterval duration = ORBIT_ANIMATION_DURATION; - const CFTimeInterval delayBetween = ORBIT_ANIMATION_DELAY_BETWEEN; - CFTimeInterval timeOffset = 0.0; - for (CAShapeLayer *shapeLayer in self.orbitLayers) { - NSString *keyPath = DW_KEYPATH(shapeLayer, opacity); - CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath]; - animation.toValue = @1.0; - animation.timeOffset = timeOffset; - animation.duration = duration; - [shapeLayer addAnimation:animation forKey:@"dw_orbit_animation"]; - shapeLayer.opacity = 1.0; - timeOffset += duration + delayBetween; - } -} - -- (void)animatePlanetsWithRepeatCount:(float)repeatCount { - if (self.planets.count != self.planetViews.count) { - [self rebornTheUniverse]; - } - - for (NSInteger i = 0; i < self.planets.count; i++) { - DWPlanetObject *planet = self.planets[i]; - UIView *planetView = self.planetViews[i]; - - NSAssert(planet.orbit < self.orbits.count, @"Internal inconsistency"); - UIBezierPath *path = self.orbits[planet.orbit]; - - // start off fading in when 1/3 of the rotation is completed - const CFTimeInterval opacityBeginTime = planet.duration / planet.speed / 3.0; - - NSString *keyPath = DW_KEYPATH(planetView.layer, opacity); - CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:keyPath]; - opacityAnimation.toValue = @1.0; - opacityAnimation.duration = planet.duration; - opacityAnimation.beginTime = opacityBeginTime; - opacityAnimation.removedOnCompletion = NO; - opacityAnimation.fillMode = kCAFillModeForwards; - - keyPath = DW_KEYPATH(planetView.layer, position); - CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:keyPath]; - positionAnimation.path = path.CGPath; - positionAnimation.repeatCount = repeatCount; - positionAnimation.calculationMode = kCAAnimationPaced; - positionAnimation.removedOnCompletion = NO; - positionAnimation.fillMode = kCAFillModeForwards; - positionAnimation.speed = (planet.rotateClockwise ? -1.0 : 1.0); - positionAnimation.timeOffset = planet.duration * planet.offset; - positionAnimation.duration = planet.duration; - - CAAnimationGroup *animation = [CAAnimationGroup animation]; - animation.animations = @[ opacityAnimation, positionAnimation ]; - animation.speed = planet.speed; - animation.duration = opacityBeginTime + planet.duration; - animation.removedOnCompletion = NO; - animation.fillMode = kCAFillModeForwards; - [planetView.layer addAnimation:animation forKey:@"dw_planet_animation"]; - } -} - -/// big bang! -- (void)rebornTheUniverse { - [self.orbits removeAllObjects]; - - [self.orbitLayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; - [self.orbitLayers removeAllObjects]; - - [self.planetViews makeObjectsPerformSelector:@selector(removeFromSuperview)]; - [self.planetViews removeAllObjects]; - - if (self.numberOfOrbits == 0 || self.numberOfOrbits != self.colors.count || self.planets.count == 0) { - return; - } - - const CGSize size = self.bounds.size; - const CGPoint center = CGPointMake(size.width / 2.0, size.height / 2.0); - const CGFloat radius = MIN(size.width, size.height) / self.numberOfOrbits / 2.0 - self.borderOffset; - - for (NSInteger i = 0; i < self.numberOfOrbits; i++) { - UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center - radius:radius * i + self.centerOffset - startAngle:0.0 - endAngle:M_PI * 2.0 - clockwise:NO]; - CAShapeLayer *shapeLayer = [CAShapeLayer layer]; - shapeLayer.path = path.CGPath; - shapeLayer.frame = self.bounds; - shapeLayer.strokeColor = self.colors[i].CGColor; - shapeLayer.lineWidth = self.lineWidth; - shapeLayer.fillColor = [UIColor clearColor].CGColor; - shapeLayer.opacity = 0.0; // initially hidden - [self.layer addSublayer:shapeLayer]; - - [self.orbits addObject:path]; - [self.orbitLayers addObject:shapeLayer]; - } - - for (DWPlanetObject *planet in self.planets) { - const CGRect rect = CGRectMake(-planet.size.width * 0.5, -planet.size.height * 0.5, - planet.size.width, planet.size.height); - - UIView *planetView = nil; - if (planet.image) { - UIImageView *imageView = [[UIImageView alloc] initWithFrame:rect]; - imageView.image = planet.image; - planetView = imageView; - } - else { - planetView = planet.customView; - } - planetView.layer.opacity = 0.0; // initially hidden - [self addSubview:planetView]; - - [self.planetViews addObject:planetView]; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWTextField.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWTextField.m deleted file mode 100644 index fea74346e..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWTextField.m +++ /dev/null @@ -1,37 +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 "DWTextField.h" - -static CGFloat const HORIZONTAL_PADDING = 30.0; -static CGFloat const VERTICAL_PADDING = 16.0; - -@implementation DWTextField - -- (CGRect)textRectForBounds:(CGRect)bounds { - return CGRectInset(bounds, HORIZONTAL_PADDING, VERTICAL_PADDING); -} - -- (CGRect)editingRectForBounds:(CGRect)bounds { - return CGRectInset(bounds, HORIZONTAL_PADDING, VERTICAL_PADDING); -} - -- (CGRect)placeholderRectForBounds:(CGRect)bounds { - return CGRectInset(bounds, HORIZONTAL_PADDING, VERTICAL_PADDING); -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameHeaderView.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameHeaderView.m deleted file mode 100644 index aca6488fb..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameHeaderView.m +++ /dev/null @@ -1,338 +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 "DWUsernameHeaderView.h" - -#import "DWDPAvatarView.h" -#import "DWPlanetarySystemView.h" -#import "DWUIKit.h" - -static CGFloat const BottomSpacing(void) { - if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { - return 4.0; - } - else { - return 16.0; - } -} - -static CGFloat SmallCircleRadius(void) { - if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { - return 106.0; - } - else { - return 78.0; - } -} - -static CGFloat PlanetarySize(void) { - if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { - return 260.0; - } - else { - const CGSize screenSize = [UIScreen mainScreen].bounds.size; - const CGFloat side = MIN(screenSize.width, screenSize.height); - return MIN(375.0, side); - } -} - -static NSArray *OrbitColors(void) { - // Luckily, DashBlueColor doesn't have DarkMode counterpart - // and we don't need to reset colors on traitCollectionDidChange: - UIColor *color = [UIColor dw_dashBlueColor]; - - if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { - return @[ - [color colorWithAlphaComponent:0.3], - [color colorWithAlphaComponent:0.1], - [color colorWithAlphaComponent:0.07], - ]; - } - else { - return @[ - [color colorWithAlphaComponent:0.5], - [color colorWithAlphaComponent:0.3], - [color colorWithAlphaComponent:0.1], - [color colorWithAlphaComponent:0.07], - ]; - } -} - -static NSArray *Planets(NSString *_Nullable username) { - CGSize size; - CGSize avatarSize; - if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { - size = CGSizeMake(28.0, 28.0); - avatarSize = CGSizeMake(46.0, 46.0); - } - else { - size = CGSizeMake(36.0, 36.0); - avatarSize = CGSizeMake(60.0, 60.0); - } - - NSMutableArray *planets = [NSMutableArray array]; - - if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { - { - DWPlanetObject *planet = [[DWPlanetObject alloc] init]; - planet.image = [UIImage imageNamed:@"dp_user_2"]; - planet.speed = 1.55; - planet.duration = 0.75; - planet.offset = 255.0 / 360.0; - planet.size = size; - planet.orbit = 0; - planet.rotateClockwise = YES; - [planets addObject:planet]; - } - - { - DWPlanetObject *planet = [[DWPlanetObject alloc] init]; - planet.image = [UIImage imageNamed:@"dp_user_3"]; - planet.speed = 1.3; - planet.duration = 0.75; - planet.offset = 230.0 / 360.0; - planet.size = size; - planet.orbit = 1; - planet.rotateClockwise = YES; - [planets addObject:planet]; - } - - { - DWPlanetObject *planet = [[DWPlanetObject alloc] init]; - if (username.length > 0) { - DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:(CGRect){{0.0, 0.0}, avatarSize}]; - avatarView.username = username; - planet.customView = avatarView; - } - else { - planet.image = [UIImage imageNamed:@"dp_user_generic"]; - } - planet.speed = 1.0; - planet.duration = 0.75; - planet.offset = 250.0 / 360.0; - planet.size = size; - planet.orbit = 2; - planet.rotateClockwise = YES; - [planets addObject:planet]; - } - } - else { - { - DWPlanetObject *planet = [[DWPlanetObject alloc] init]; - planet.image = [UIImage imageNamed:@"dp_user_1"]; - planet.speed = 2.1; - planet.duration = 0.75; - planet.offset = 245.0 / 360.0; - planet.size = size; - planet.orbit = 0; - planet.rotateClockwise = YES; - [planets addObject:planet]; - } - - { - DWPlanetObject *planet = [[DWPlanetObject alloc] init]; - planet.image = [UIImage imageNamed:@"dp_user_2"]; - planet.speed = 1.8; - planet.duration = 0.75; - planet.offset = 255.0 / 360.0; - planet.size = size; - planet.orbit = 1; - planet.rotateClockwise = YES; - [planets addObject:planet]; - } - - { - DWPlanetObject *planet = [[DWPlanetObject alloc] init]; - planet.image = [UIImage imageNamed:@"dp_user_3"]; - planet.speed = 1.55; - planet.duration = 0.75; - planet.offset = 230.0 / 360.0; - planet.size = size; - planet.orbit = 2; - planet.rotateClockwise = YES; - [planets addObject:planet]; - } - - { - DWPlanetObject *planet = [[DWPlanetObject alloc] init]; - planet.image = [UIImage imageNamed:@"dp_user_2"]; // TODO: fix image - planet.speed = 1.3; - planet.duration = 0.75; - planet.offset = 200.0 / 360.0; - planet.size = size; - planet.orbit = 3; - planet.rotateClockwise = YES; - [planets addObject:planet]; - } - - { - DWPlanetObject *planet = [[DWPlanetObject alloc] init]; - if (username.length > 0) { - DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:(CGRect){{0.0, 0.0}, avatarSize}]; - avatarView.username = username; - planet.customView = avatarView; - } - else { - planet.image = [UIImage imageNamed:@"dp_user_generic"]; - } - planet.speed = 1.0; - planet.duration = 0.75; - planet.offset = 250.0 / 360.0; - planet.size = size; - planet.orbit = 3; - planet.rotateClockwise = YES; - [planets addObject:planet]; - } - } - - return [planets copy]; -} - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUsernameHeaderView () - -@property (strong, nonatomic) DWPlanetarySystemView *planetaryView; -@property (nonatomic, strong) UILabel *titleLabel; - -@property (nonatomic, copy) NSArray *portraitConstraints; -@property (nonatomic, copy) NSArray *landscapeConstraints; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUsernameHeaderView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_backgroundColor]; - - UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeCustom]; - cancelButton.translatesAutoresizingMaskIntoConstraints = NO; - [cancelButton setImage:[UIImage imageNamed:@"payments_nav_cross"] forState:UIControlStateNormal]; - [self addSubview:cancelButton]; - _cancelButton = cancelButton; - - NSArray *colors = OrbitColors(); - - DWPlanetarySystemView *planetaryView = [[DWPlanetarySystemView alloc] initWithFrame:CGRectZero]; - planetaryView.translatesAutoresizingMaskIntoConstraints = NO; - planetaryView.centerOffset = SmallCircleRadius(); - planetaryView.colors = colors; - planetaryView.lineWidth = 1.0; - planetaryView.numberOfOrbits = colors.count; - planetaryView.planets = Planets(nil); - [self addSubview:planetaryView]; - _planetaryView = planetaryView; - - UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - titleLabel.translatesAutoresizingMaskIntoConstraints = NO; - titleLabel.backgroundColor = [UIColor clearColor]; - titleLabel.numberOfLines = 3; - [self addSubview:titleLabel]; - _titleLabel = titleLabel; - - const CGFloat buttonSize = 44.0; - const CGFloat side = PlanetarySize(); - CGPoint planetOffest = CGPointZero; - if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { - planetOffest = CGPointMake(16.0, -66.0); - } - - _landscapeConstraints = @[ - [titleLabel.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor], - [titleLabel.leadingAnchor constraintEqualToAnchor:cancelButton.trailingAnchor - constant:16.0], - ]; - - _portraitConstraints = @[ - [titleLabel.topAnchor constraintGreaterThanOrEqualToAnchor:cancelButton.bottomAnchor], - [titleLabel.leadingAnchor constraintEqualToAnchor:self.layoutMarginsGuide.leadingAnchor], - ]; - - [NSLayoutConstraint activateConstraints:_portraitConstraints]; - - [NSLayoutConstraint activateConstraints:@[ - [cancelButton.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor], - [cancelButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [cancelButton.widthAnchor constraintEqualToConstant:buttonSize], - [cancelButton.heightAnchor constraintEqualToConstant:buttonSize], - - [planetaryView.centerXAnchor constraintEqualToAnchor:self.trailingAnchor - constant:planetOffest.x], - [planetaryView.centerYAnchor constraintEqualToAnchor:self.topAnchor - constant:planetOffest.y], - [planetaryView.widthAnchor constraintEqualToConstant:side], - [planetaryView.heightAnchor constraintEqualToConstant:side], - - - [self.layoutMarginsGuide.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], - [self.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor - constant:BottomSpacing()], - ]]; - } - return self; -} - -- (void)setTitleBuilder:(DWTitleStringBuilder)titleBuilder { - _titleBuilder = [titleBuilder copy]; - - [self updateTitle]; -} - -- (void)configurePlanetsViewWithUsername:(NSString *)username { - self.planetaryView.planets = Planets(username); -} - -- (void)showInitialAnimation { - [self.planetaryView showInitialAnimation]; -} - -- (void)setLandscapeMode:(BOOL)landscapeMode { - _landscapeMode = landscapeMode; - - self.planetaryView.alpha = landscapeMode ? 0.0 : 1.0; - if (landscapeMode) { - [NSLayoutConstraint deactivateConstraints:self.portraitConstraints]; - [NSLayoutConstraint activateConstraints:self.landscapeConstraints]; - } - else { - [NSLayoutConstraint deactivateConstraints:self.landscapeConstraints]; - [NSLayoutConstraint activateConstraints:self.portraitConstraints]; - } -} - -- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { - [super traitCollectionDidChange:previousTraitCollection]; - - [self updateTitle]; -} - -#pragma mark - Private - -- (void)updateTitle { - if (self.titleBuilder) { - self.titleLabel.attributedText = self.titleBuilder(); - } - else { - self.titleLabel.attributedText = nil; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameValidationView.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameValidationView.m deleted file mode 100644 index 805a4e3cc..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Views/DWUsernameValidationView.m +++ /dev/null @@ -1,139 +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 "DWUsernameValidationView.h" - -#import "DWUIKit.h" - -static CGFloat const ICON_SIZE = 16.0; - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUsernameValidationView () - -@property (readonly, nonatomic, strong) UIImageView *iconImageView; -@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; -@property (readonly, nonatomic, strong) UILabel *titleLabel; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUsernameValidationView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - UIImageView *iconImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; - iconImageView.translatesAutoresizingMaskIntoConstraints = NO; - iconImageView.contentMode = UIViewContentModeCenter; - [self addSubview:iconImageView]; - _iconImageView = iconImageView; - - UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; - activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; - activityIndicatorView.color = [UIColor dw_darkTitleColor]; - [self addSubview:activityIndicatorView]; - _activityIndicatorView = activityIndicatorView; - - UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - titleLabel.translatesAutoresizingMaskIntoConstraints = NO; - titleLabel.backgroundColor = self.backgroundColor; - titleLabel.numberOfLines = 0; - titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCaption1]; - titleLabel.adjustsFontForContentSizeCategory = YES; - titleLabel.textColor = [UIColor dw_darkTitleColor]; - [self addSubview:titleLabel]; - _titleLabel = titleLabel; - - [NSLayoutConstraint activateConstraints:@[ - [iconImageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [iconImageView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], - [iconImageView.widthAnchor constraintEqualToConstant:ICON_SIZE], - [iconImageView.heightAnchor constraintEqualToConstant:ICON_SIZE], - - [activityIndicatorView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [activityIndicatorView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], - - [titleLabel.topAnchor constraintEqualToAnchor:self.topAnchor], - [titleLabel.leadingAnchor constraintEqualToAnchor:iconImageView.trailingAnchor - constant:5.0], - [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], - [self.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor], - ]]; - - [self mvvm_observe:DW_KEYPATH(self, rule.validationResult) - with:^(typeof(self) self, NSNumber *value) { - [self setValidationResult:self.rule.validationResult]; - }]; - } - return self; -} - -- (void)setValidationResult:(DWUsernameValidationRuleResult)validationResult { - self.titleLabel.text = self.rule.title; - - switch (validationResult) { - case DWUsernameValidationRuleResultEmpty: - self.hidden = NO; - self.iconImageView.image = nil; - self.iconImageView.tintColor = nil; - self.titleLabel.textColor = [UIColor dw_darkTitleColor]; - [self.activityIndicatorView stopAnimating]; - break; - case DWUsernameValidationRuleResultLoading: - self.hidden = NO; - self.iconImageView.image = nil; - self.iconImageView.tintColor = nil; - self.titleLabel.textColor = [UIColor dw_darkTitleColor]; - [self.activityIndicatorView startAnimating]; - break; - case DWUsernameValidationRuleResultValid: - self.hidden = NO; - self.iconImageView.image = [UIImage imageNamed:@"validation_checkmark"]; - self.iconImageView.tintColor = [UIColor dw_darkTitleColor]; - self.titleLabel.textColor = [UIColor dw_darkTitleColor]; - [self.activityIndicatorView stopAnimating]; - break; - case DWUsernameValidationRuleResultInvalid: - self.hidden = NO; - self.iconImageView.image = [UIImage imageNamed:@"validation_cross"]; - self.iconImageView.tintColor = nil; - self.titleLabel.textColor = [UIColor dw_darkTitleColor]; - [self.activityIndicatorView stopAnimating]; - break; - case DWUsernameValidationRuleResultInvalidCritical: - case DWUsernameValidationRuleResultError: - self.hidden = NO; - self.iconImageView.image = [UIImage imageNamed:@"validation_cross"]; - self.iconImageView.tintColor = nil; - self.titleLabel.textColor = [UIColor dw_redColor]; - [self.activityIndicatorView stopAnimating]; - break; - case DWUsernameValidationRuleResultHidden: - self.hidden = YES; - self.iconImageView.image = nil; - self.iconImageView.tintColor = nil; - self.titleLabel.textColor = [UIColor dw_darkTitleColor]; - [self.activityIndicatorView stopAnimating]; - break; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.m b/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.m deleted file mode 100644 index 847eb7125..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupFlowController.m +++ /dev/null @@ -1,316 +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 "DWDashPaySetupFlowController.h" - -#import "DWConfirmUsernameViewController.h" -#import "DWContainerViewController.h" -#import "DWCreateUsernameViewController.h" -#import "DWDPRegistrationStatus.h" -#import "DWDashPaySetupModel.h" -#import "DWRegistrationCompletedViewController.h" -#import "DWUIKit.h" -#import "DWUsernameHeaderView.h" -#import "DWUsernamePendingViewController.h" -#import "UIViewController+DWDisplayError.h" -#import "UIViewController+DWEmbedding.h" - -static CGFloat const HeaderHeight(void) { - if (IS_IPHONE_6 || IS_IPHONE_5_OR_LESS) { - return 135.0; - } - else { - return 231.0; - } -} - -static CGFloat const LandscapeHeaderHeight(void) { - return 158.0; -} - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDashPaySetupFlowController () - -@property (readonly, nonatomic, strong) id dashPayModel; -@property (nullable, nonatomic, readonly, strong) NSURL *invitationURL; -@property (nullable, nonatomic, readonly, copy) NSString *definedUsername; -@property (nullable, nonatomic, weak) id confirmationDelegate; - -@property (null_resettable, nonatomic, strong) DWUsernameHeaderView *headerView; -@property (null_resettable, nonatomic, strong) UIView *contentView; -@property (nonatomic, strong) NSLayoutConstraint *headerHeightConstraint; - -@property (nonatomic, strong) DWContainerViewController *containerController; -@property (null_resettable, nonatomic, strong) DWCreateUsernameViewController *createUsernameViewController; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDashPaySetupFlowController - -- (instancetype)initWithDashPayModel:(id)dashPayModel - invitation:(NSURL *)invitationURL - definedUsername:(NSString *)definedUsername { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _dashPayModel = dashPayModel; - _invitationURL = invitationURL; - _definedUsername = definedUsername; - } - return self; -} - -- (instancetype)initWithConfirmationDelegate:(id)delegate { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _dashPayModel = [[DWDashPaySetupModel alloc] init]; - _invitationURL = nil; - _confirmationDelegate = delegate; - } - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.clipsToBounds = YES; - self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; - - [self.view addSubview:self.contentView]; - [self.view addSubview:self.headerView]; - - const BOOL isLandscape = CGRectGetWidth(self.view.bounds) > CGRectGetHeight(self.view.bounds); - const CGFloat headerHeight = isLandscape ? LandscapeHeaderHeight() : HeaderHeight(); - self.headerView.landscapeMode = isLandscape; - - [NSLayoutConstraint activateConstraints:@[ - [self.headerView.topAnchor constraintEqualToAnchor:self.view.topAnchor], - [self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [self.view.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor], - (self.headerHeightConstraint = [self.headerView.heightAnchor constraintEqualToConstant:headerHeight]), - - [self.contentView.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor], - [self.contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [self.view.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], - [self.view.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor], - [self.contentView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor], - ]]; - - self.containerController = [[DWContainerViewController alloc] init]; - [self dw_embedChild:self.containerController inContainer:self.contentView]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(registrationStatusUpdatedNotification) - name:DWDashPayRegistrationStatusUpdatedNotification - object:nil]; - - [self setCurrentStateController]; -} - -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - - [coordinator - animateAlongsideTransition:^(id context) { - BOOL isLandscape = size.width > size.height; - self.headerView.landscapeMode = isLandscape; - if (isLandscape) { - self.headerHeightConstraint.constant = LandscapeHeaderHeight(); - } - else { - self.headerHeightConstraint.constant = HeaderHeight(); - } - } - completion:^(id _Nonnull context){ - - }]; -} - -- (UIStatusBarStyle)preferredStatusBarStyle { - return UIStatusBarStyleDefault; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - - [self.headerView showInitialAnimation]; -} - -#pragma mark - DWNavigationFullscreenable - -- (BOOL)requiresNoNavigationBar { - return YES; -} - -#pragma mark - Private - -- (void)registrationStatusUpdatedNotification { - if (self.dashPayModel.lastRegistrationError) { - [self dw_displayErrorModally:self.dashPayModel.lastRegistrationError]; - } - - [self setCurrentStateController]; -} - -- (void)setCurrentStateController { - if (self.definedUsername != nil) { - [self createUsername:self.definedUsername]; - return; - } - - if (self.dashPayModel.registrationStatus == nil || self.dashPayModel.registrationStatus.failed) { - [self showCreateUsernameController]; - - return; - } - - if (self.dashPayModel.registrationStatus.state != DWDPRegistrationState_Done) { - [self showPendingController:self.dashPayModel.username]; - } - else { - [self showRegistrationCompletedController:self.dashPayModel.username]; - } -} - -- (void)createUsername:(NSString *)username { - __weak typeof(self) weakSelf = self; - [self.dashPayModel createUsername:username invitation:self.invitationURL]; - [self showPendingController:username]; -} - -- (UIView *)contentView { - if (_contentView == nil) { - _contentView = [[UIView alloc] initWithFrame:CGRectZero]; - _contentView.translatesAutoresizingMaskIntoConstraints = NO; - } - return _contentView; -} - -- (DWUsernameHeaderView *)headerView { - if (_headerView == nil) { - _headerView = [[DWUsernameHeaderView alloc] initWithFrame:CGRectZero]; - _headerView.translatesAutoresizingMaskIntoConstraints = NO; - _headerView.preservesSuperviewLayoutMargins = YES; - _headerView.cancelButton.hidden = self.confirmationDelegate != nil; - [_headerView.cancelButton addTarget:self - action:@selector(cancelButtonAction) - forControlEvents:UIControlEventTouchUpInside]; - } - - return _headerView; -} - -- (DWCreateUsernameViewController *)createUsernameViewController { - if (_createUsernameViewController == nil) { - DWCreateUsernameViewController *controller = - [[DWCreateUsernameViewController alloc] initWithDashPayModel:self.dashPayModel]; - controller.delegate = self; - _createUsernameViewController = controller; - } - return _createUsernameViewController; -} - -- (void)showPendingController:(NSString *)username { - DWUsernamePendingViewController *controller = [[DWUsernamePendingViewController alloc] init]; - controller.username = username; - controller.delegate = self; - __weak DWUsernamePendingViewController *weakController = controller; - self.headerView.titleBuilder = ^NSAttributedString *_Nonnull { - return [weakController attributedTitle]; - }; - [self.containerController transitionToController:controller]; -} - -- (void)showCreateUsernameController { - DWCreateUsernameViewController *controller = self.createUsernameViewController; - __weak DWCreateUsernameViewController *weakController = controller; - self.headerView.titleBuilder = ^NSAttributedString *_Nonnull { - return [weakController attributedTitle]; - }; - [self.containerController transitionToController:controller]; -} - -- (void)showRegistrationCompletedController:(NSString *)username { - NSAssert(username.length > 1, @"Invalid username"); - - [self.headerView configurePlanetsViewWithUsername:username]; - - DWRegistrationCompletedViewController *controller = [[DWRegistrationCompletedViewController alloc] init]; - controller.username = username; - controller.delegate = self; - self.headerView.titleBuilder = ^NSAttributedString *_Nonnull { - return [[NSAttributedString alloc] init]; - }; - [self.containerController transitionToController:controller]; -} - -#pragma mark - Actions - -- (void)cancelButtonAction { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -#pragma mark - DWCreateUsernameViewControllerDelegate - -- (void)createUsernameViewController:(DWCreateUsernameViewController *)controller - registerUsername:(NSString *)username { - if ([self.dashPayModel shouldPresentRegistrationPaymentConfirmation]) { - DWConfirmUsernameViewController *confirmController = [[DWConfirmUsernameViewController alloc] initWithUsername:username]; - confirmController.delegate = self; - [self presentViewController:confirmController animated:YES completion:nil]; - } - else { - [self createUsername:username]; - } -} - -#pragma mark - DWConfirmUsernameViewControllerDelegate - -- (void)confirmUsernameViewControllerDidConfirm:(DWConfirmUsernameViewController *)controller { - NSString *username = controller.username; - [controller dismissViewControllerAnimated:YES - completion:^{ - if (self.confirmationDelegate) { - [self.confirmationDelegate dashPaySetupFlowController:self didConfirmUsername:username]; - } - else { - // initiate creation process once confirmation is dismissed because - // DashSync will be showing pin request modally - [self createUsername:username]; - } - }]; -} - -#pragma mark - DWUsernamePendingViewControllerDelegate - -- (void)usernamePendingViewControllerAction:(UIViewController *)controller { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -#pragma mark - DWRegistrationCompletedViewControllerDelegate - -- (void)registrationCompletedViewControllerAction:(UIViewController *)controller { - [self.dashPayModel completeRegistration]; - [self dismissViewControllerAnimated:YES completion:nil]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupModel.h b/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupModel.h new file mode 100644 index 000000000..ac876f39d --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupModel.h @@ -0,0 +1,26 @@ +// +// 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 "DWDashPayProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDashPaySetupModel : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupModel.m b/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupModel.m new file mode 100644 index 000000000..1b9a6c8dd --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Setup/DWDashPaySetupModel.m @@ -0,0 +1,71 @@ +// +// 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 "DWDashPaySetupModel.h" + +@implementation DWDashPaySetupModel + +@synthesize blockchainIdentity; + +@synthesize lastRegistrationError; + +@synthesize registrationCompleted; + +@synthesize registrationStatus; + +@synthesize unreadNotificationsCount; + +@synthesize username; + +@synthesize userProfile; + +- (BOOL)canRetry { + return NO; +} + +- (void)completeRegistration { + // nop +} + +- (void)createUsername:(nonnull NSString *)username invitation:(nonnull NSURL *)invitation { + NSAssert(NO, @"Should not be called"); + // nop +} + +- (void)retry { + NSAssert(NO, @"Should not be called"); + // nop +} + +- (void)setHasEnoughBalanceForInvitationNotification:(BOOL)value { + // nop +} + +- (BOOL)shouldPresentRegistrationPaymentConfirmation { + return YES; +} + +- (void)updateUsernameStatus { + // nop +} + +- (void)verifyDeeplink:(nonnull NSURL *)url completion:(nonnull void (^)(BOOL, NSString *_Nullable, NSString *_Nullable))completion { + NSAssert(NO, @"Should not be called"); + // nop +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.m b/DashWallet/Sources/UI/DashPay/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.m deleted file mode 100644 index c582009f3..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.m +++ /dev/null @@ -1,156 +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 "DWRegistrationCompletedViewController.h" - -#import "DWActionButton.h" -#import "DWBaseActionButtonViewController.h" -#import "DWUIKit.h" -#import "dashwallet-Swift.h" - -@interface DWRegistrationCompletedViewController () - -@property (null_resettable, nonatomic, strong) UIImageView *iconImageView; -@property (null_resettable, nonatomic, strong) UILabel *descriptionLabel; -@property (null_resettable, strong, nonatomic) UIButton *actionButton; - -@end - -@implementation DWRegistrationCompletedViewController - -- (void)setUsername:(NSString *)username { - _username = username; - [self updateDetailLabel]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor dw_backgroundColor]; - - [self.view addSubview:self.iconImageView]; - [self.view addSubview:self.actionButton]; - - UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ self.descriptionLabel ]]; - stackView.translatesAutoresizingMaskIntoConstraints = NO; - stackView.alignment = UIStackViewAlignmentTop; - [self.view addSubview:stackView]; - - UILayoutGuide *marginsGuide = self.view.layoutMarginsGuide; - UILayoutGuide *safeAreaGuide = self.view.safeAreaLayoutGuide; - - [self.iconImageView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - [self.iconImageView setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - [stackView setContentHuggingPriority:UILayoutPriorityDefaultLow - 1 forAxis:UILayoutConstraintAxisVertical]; - - const CGFloat bottomPadding = [DWBaseActionButtonViewController deviceSpecificBottomPadding]; - [NSLayoutConstraint activateConstraints:@[ - [self.iconImageView.topAnchor constraintEqualToAnchor:marginsGuide.topAnchor], - [self.iconImageView.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], - - [stackView.topAnchor constraintEqualToAnchor:self.iconImageView.bottomAnchor - constant:24.0], - [stackView.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], - [stackView.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], - - [self.actionButton.topAnchor constraintEqualToAnchor:stackView.bottomAnchor], - [self.actionButton.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], - [self.actionButton.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], - [safeAreaGuide.bottomAnchor constraintEqualToAnchor:self.actionButton.bottomAnchor - constant:bottomPadding], - [self.actionButton.heightAnchor constraintEqualToConstant:DWBottomButtonHeight()], - ]]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self - selector:@selector(contentSizeCategoryDidChangeNotification) - name:UIContentSizeCategoryDidChangeNotification - object:nil]; -} - -#pragma mark - Private - -- (UIImageView *)iconImageView { - if (_iconImageView == nil) { - UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dp_registration_done_icon"]]; - imageView.translatesAutoresizingMaskIntoConstraints = NO; - _iconImageView = imageView; - } - return _iconImageView; -} - -- (UILabel *)descriptionLabel { - if (_descriptionLabel == nil) { - UILabel *label = [[UILabel alloc] init]; - label.translatesAutoresizingMaskIntoConstraints = NO; - label.numberOfLines = 0; - label.adjustsFontSizeToFitWidth = YES; - label.minimumScaleFactor = 0.5; - label.contentMode = UIViewContentModeTop; - _descriptionLabel = label; - } - return _descriptionLabel; -} - -- (UIButton *)actionButton { - if (_actionButton == nil) { - DWActionButton *button = [[DWActionButton alloc] initWithFrame:CGRectZero]; - button.translatesAutoresizingMaskIntoConstraints = NO; - [button setTitle:NSLocalizedString(@"Continue", nil) forState:UIControlStateNormal]; - [button addTarget:self action:@selector(actionButtonAction) forControlEvents:UIControlEventTouchUpInside]; - _actionButton = button; - } - return _actionButton; -} - -- (void)contentSizeCategoryDidChangeNotification { - [self updateDetailLabel]; -} - -- (void)updateDetailLabel { - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - - [result beginEditing]; - - NSString *formattedString = [NSString - stringWithFormat:NSLocalizedString(@"Your username %@ has been successfully created on the Dash Network", nil), - self.username]; - NSAttributedString *detail = [[NSAttributedString alloc] - initWithString:formattedString - attributes:@{ - NSFontAttributeName : [UIFont dw_regularFontOfSize:26], - NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], - }]; - [result appendAttributedString:detail]; - - NSRange usernameRange = [formattedString rangeOfString:self.username]; - [result setAttributes:@{ - NSFontAttributeName : [UIFont dw_mediumFontOfSize:26], - NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], - } - range:usernameRange]; - - [result endEditing]; - - self.descriptionLabel.attributedText = result; -} - -- (void)actionButtonAction { - [self.delegate registrationCompletedViewControllerAction:self]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/UsernamePending/DWUsernamePendingViewController.m b/DashWallet/Sources/UI/DashPay/Setup/UsernamePending/DWUsernamePendingViewController.m deleted file mode 100644 index 88fe268e0..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/UsernamePending/DWUsernamePendingViewController.m +++ /dev/null @@ -1,205 +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 "DWUsernamePendingViewController.h" - -#import "DWBaseActionButton.h" -#import "DWBaseActionButtonViewController.h" -#import "DWDashPayAnimationView.h" -#import "DWUIKit.h" -#import "dashwallet-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWUsernamePendingViewController () - -@property (null_resettable, strong, nonatomic) UIView *contentView; -@property (null_resettable, strong, nonatomic) DWDashPayAnimationView *progressView; -@property (null_resettable, strong, nonatomic) UILabel *detailLabel; -@property (null_resettable, strong, nonatomic) UIButton *actionButton; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWUsernamePendingViewController - -- (NSAttributedString *)attributedTitle { - NSDictionary *regularAttributes = @{ - NSFontAttributeName : [UIFont dw_regularFontOfSize:22.0], - NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], - }; - - NSAttributedString *pleaseString = - [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Please wait", nil) - attributes:regularAttributes]; - return pleaseString; -} - -- (void)setUsername:(NSString *)username { - _username = username; - [self updateDetailLabel]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor dw_dashBlueColor]; - - UIView *centeredView = [[UIView alloc] init]; - centeredView.translatesAutoresizingMaskIntoConstraints = NO; - [centeredView addSubview:self.progressView]; - [centeredView addSubview:self.detailLabel]; - - [self.contentView addSubview:centeredView]; - [self.view addSubview:self.contentView]; - [self.view addSubview:self.actionButton]; - - UILayoutGuide *marginsGuide = self.view.layoutMarginsGuide; - UILayoutGuide *safeAreaGuide = self.view.safeAreaLayoutGuide; - - [self.progressView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - [self.detailLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - [centeredView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - - const CGFloat bottomPadding = [DWBaseActionButtonViewController deviceSpecificBottomPadding]; - [NSLayoutConstraint activateConstraints:@[ - [self.contentView.topAnchor constraintEqualToAnchor:safeAreaGuide.topAnchor], - [self.contentView.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], - [self.contentView.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], - - [self.progressView.topAnchor constraintEqualToAnchor:centeredView.topAnchor], - [self.progressView.centerXAnchor constraintEqualToAnchor:centeredView.centerXAnchor], - - [self.detailLabel.topAnchor constraintEqualToAnchor:self.progressView.bottomAnchor - constant:10.0], - [self.detailLabel.leadingAnchor constraintEqualToAnchor:centeredView.leadingAnchor], - [self.detailLabel.trailingAnchor constraintEqualToAnchor:centeredView.trailingAnchor], - [self.detailLabel.bottomAnchor constraintEqualToAnchor:centeredView.bottomAnchor], - - [centeredView.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor], - [centeredView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], - [centeredView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], - - [self.actionButton.topAnchor constraintEqualToAnchor:self.contentView.bottomAnchor], - [self.actionButton.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], - [self.actionButton.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], - [safeAreaGuide.bottomAnchor constraintEqualToAnchor:self.actionButton.bottomAnchor - constant:bottomPadding], - [self.actionButton.heightAnchor constraintEqualToConstant:DWBottomButtonHeight()], - ]]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self - selector:@selector(contentSizeCategoryDidChangeNotification) - name:UIContentSizeCategoryDidChangeNotification - object:nil]; -} - -- (UIStatusBarStyle)preferredStatusBarStyle { - return UIStatusBarStyleLightContent; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - - [self.progressView startAnimating]; -} - -#pragma mark - Private - -- (UIView *)contentView { - if (_contentView == nil) { - UIView *contentView = [[UIView alloc] init]; - contentView.translatesAutoresizingMaskIntoConstraints = NO; - contentView.backgroundColor = [UIColor dw_dashBlueColor]; - _contentView = contentView; - } - return _contentView; -} - -- (DWDashPayAnimationView *)progressView { - if (_progressView == nil) { - DWDashPayAnimationView *progressView = [[DWDashPayAnimationView alloc] initWithFrame:CGRectZero]; - progressView.translatesAutoresizingMaskIntoConstraints = NO; - _progressView = progressView; - } - return _progressView; -} - -- (UILabel *)detailLabel { - if (_detailLabel == nil) { - UILabel *detailLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - detailLabel.translatesAutoresizingMaskIntoConstraints = NO; - detailLabel.numberOfLines = 0; - detailLabel.adjustsFontSizeToFitWidth = YES; - detailLabel.minimumScaleFactor = 0.5; - detailLabel.textAlignment = NSTextAlignmentCenter; - _detailLabel = detailLabel; - } - return _detailLabel; -} - -- (UIButton *)actionButton { - if (_actionButton == nil) { - DWBaseActionButton *actionButton = [[DWBaseActionButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 54.0)]; - actionButton.translatesAutoresizingMaskIntoConstraints = NO; - actionButton.layer.cornerRadius = 8; - actionButton.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; - [actionButton setBackgroundColor:[UIColor whiteColor] forState:UIControlStateNormal]; - [actionButton setTitleColor:[UIColor dw_dashBlueColor] forState:UIControlStateNormal]; - [actionButton setTitleColor:[[UIColor dw_dashBlueColor] colorWithAlphaComponent:0.5] forState:UIControlStateHighlighted]; - [actionButton setTitle:NSLocalizedString(@"Let me know when it’s done", nil) - forState:UIControlStateNormal]; - [actionButton addTarget:self - action:@selector(actionButtonAction:) - forControlEvents:UIControlEventTouchUpInside]; - _actionButton = actionButton; - } - return _actionButton; -} - -- (void)actionButtonAction:(id)sender { - [self.delegate usernamePendingViewControllerAction:self]; -} - -- (void)contentSizeCategoryDidChangeNotification { - [self updateDetailLabel]; -} - -- (void)updateDetailLabel { - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; - - [result beginEditing]; - - NSString *formattedString = [NSString - stringWithFormat:NSLocalizedString(@"Your username %@ is being created on the Dash Network", nil), - self.username]; - NSAttributedString *detail = [[NSAttributedString alloc] - initWithString:formattedString - attributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : [UIColor dw_lightTitleColor]}]; - [result appendAttributedString:detail]; - - NSRange usernameRange = [formattedString rangeOfString:self.username]; - [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline], NSForegroundColorAttributeName : [UIColor dw_lightTitleColor]} range:usernameRange]; - - [result endEditing]; - - self.detailLabel.attributedText = result; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDPAvatarView.h b/DashWallet/Sources/UI/DashPay/Views/DWDPAvatarView.h index bc95b7972..515e8eccb 100644 --- a/DashWallet/Sources/UI/DashPay/Views/DWDPAvatarView.h +++ b/DashWallet/Sources/UI/DashPay/Views/DWDPAvatarView.h @@ -19,6 +19,10 @@ NS_ASSUME_NONNULL_BEGIN +extern NSString *const DPCropParameterName; + +@class DSBlockchainIdentity; + typedef NS_ENUM(NSUInteger, DWDPAvatarBackgroundMode) { DWDPAvatarBackgroundMode_DashBlue, DWDPAvatarBackgroundMode_Random, @@ -27,9 +31,12 @@ typedef NS_ENUM(NSUInteger, DWDPAvatarBackgroundMode) { @interface DWDPAvatarView : UIView @property (nonatomic, assign) DWDPAvatarBackgroundMode backgroundMode; -@property (nullable, nonatomic, copy) NSString *username; +@property (nullable, nonatomic, copy) DSBlockchainIdentity *blockchainIdentity; @property (nonatomic, assign, getter=isSmall) BOOL small; +- (void)setAsDashPlaceholder; +- (void)configureWithUsername:(NSString *)username; + @end NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDPAvatarView.m b/DashWallet/Sources/UI/DashPay/Views/DWDPAvatarView.m index 9562b4caf..fac3338f6 100644 --- a/DashWallet/Sources/UI/DashPay/Views/DWDPAvatarView.m +++ b/DashWallet/Sources/UI/DashPay/Views/DWDPAvatarView.m @@ -17,14 +17,23 @@ #import "DWDPAvatarView.h" +#import "DWEnvironment.h" #import "DWUIKit.h" #import "UIColor+DWDashPay.h" +#import "UIImageView+DWDPAvatar.h" +#import +#import + NS_ASSUME_NONNULL_BEGIN +NSString *const DPCropParameterName = @"dashpay-profile-pic-zoom"; + @interface DWDPAvatarView () +@property (readonly, nonatomic, strong) UIImageView *imageView; @property (readonly, nonatomic, strong) UILabel *letterLabel; +@property (nonatomic, assign) CGSize imageSize; @end @@ -53,6 +62,7 @@ - (void)layoutSubviews { self.layer.cornerRadius = CGRectGetWidth(self.bounds) / 2.0; + self.imageView.frame = self.bounds; self.letterLabel.frame = self.bounds; } @@ -62,7 +72,45 @@ - (void)setBackgroundMode:(DWDPAvatarBackgroundMode)backgroundMode { [self updateBackgroundColor]; } +- (void)setBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + _blockchainIdentity = blockchainIdentity; + + [self.imageView sd_cancelCurrentImageLoad]; + + NSString *username = blockchainIdentity.currentDashpayUsername; + NSString *avatarUrlString = [blockchainIdentity.avatarPath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + + [self setUsername:username]; + + __block typeof(self) weakSelf = self; + + [self.imageView dw_setAvatarWithURLString:avatarUrlString + completion:^(UIImage *_Nullable image) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (image) { + strongSelf.imageView.hidden = NO; + strongSelf.letterLabel.hidden = YES; + strongSelf.imageView.image = image; + } + else { + [strongSelf setUsername:username]; + } + }]; + +} + +- (void)configureWithUsername:(NSString *)username { + [self setUsername:username]; +} + - (void)setUsername:(NSString *)username { + self.letterLabel.hidden = NO; + self.imageView.hidden = YES; + if (username.length >= 1) { NSString *firstLetter = [username substringToIndex:1]; self.letterLabel.text = [firstLetter uppercaseString]; @@ -75,6 +123,17 @@ - (void)setUsername:(NSString *)username { } } +- (void)setAsDashPlaceholder { + self.letterLabel.hidden = YES; + self.imageView.hidden = NO; + + self.layer.backgroundColor = [UIColor dw_dashBlueColor].CGColor; + + self.imageView.tintColor = [UIColor whiteColor]; + self.imageView.contentMode = UIViewContentModeCenter; + self.imageView.image = [UIImage imageNamed:@"icon_dash_small"]; +} + - (void)setSmall:(BOOL)small { _small = small; @@ -92,12 +151,18 @@ - (void)setup { self.layer.backgroundColor = [UIColor dw_dashBlueColor].CGColor; self.layer.masksToBounds = YES; + UIImageView *imageView = [[UIImageView alloc] init]; + [self addSubview:imageView]; + _imageView = imageView; + UILabel *letterLabel = [[UILabel alloc] init]; letterLabel.font = [UIFont dw_regularFontOfSize:30]; letterLabel.textAlignment = NSTextAlignmentCenter; letterLabel.textColor = [UIColor dw_lightTitleColor]; [self addSubview:letterLabel]; _letterLabel = letterLabel; + + _imageSize = CGSizeMake(256, 256); } - (void)updateBackgroundColor { diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.m b/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.m deleted file mode 100644 index c0dee4d0e..000000000 --- a/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.m +++ /dev/null @@ -1,96 +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 "DWDPSmallContactView.h" - -#import "DWDPAvatarView.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWDPSmallContactView () - -@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; -@property (readonly, nonatomic, strong) UILabel *titleLabel; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDPSmallContactView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setup_smallContactView]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setup_smallContactView]; - } - return self; -} - -- (void)setup_smallContactView { - DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:CGRectZero]; - avatarView.translatesAutoresizingMaskIntoConstraints = NO; - avatarView.small = YES; - avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; - [self addSubview:avatarView]; - _avatarView = avatarView; - - UILabel *titleLabel = [[UILabel alloc] init]; - titleLabel.translatesAutoresizingMaskIntoConstraints = NO; - titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; - titleLabel.adjustsFontForContentSizeCategory = YES; - titleLabel.textColor = [UIColor dw_darkTitleColor]; - titleLabel.numberOfLines = 0; - titleLabel.adjustsFontSizeToFitWidth = YES; - titleLabel.minimumScaleFactor = 0.5; - [self addSubview:titleLabel]; - _titleLabel = titleLabel; - - const CGFloat avatarSize = 30.0; - - [NSLayoutConstraint activateConstraints:@[ - [avatarView.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.leadingAnchor], - [avatarView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], - [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:avatarView.bottomAnchor], - [avatarView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], - [avatarView.widthAnchor constraintEqualToConstant:avatarSize], - [avatarView.heightAnchor constraintEqualToConstant:avatarSize], - - [titleLabel.topAnchor constraintEqualToAnchor:self.topAnchor], - [titleLabel.leadingAnchor constraintEqualToAnchor:avatarView.trailingAnchor - constant:12.0], - [self.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor], - [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], - ]]; -} - -- (void)setItem:(id)item { - _item = item; - - self.avatarView.username = item.username; - self.titleLabel.text = item.displayName ?: item.username; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.m b/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.m deleted file mode 100644 index 725ea42f2..000000000 --- a/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.m +++ /dev/null @@ -1,368 +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 "DWDashPayAnimationView.h" - -#import "CALayer+MBAnimationPersistence.h" -#import "DWAnimatedShapeLayer.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -static UIBezierPath *LeftSidePath(void) { - UIBezierPath *path = [UIBezierPath bezierPath]; - [path moveToPoint:CGPointMake(0, 12)]; - [path addLineToPoint:CGPointMake(22, 23)]; - [path addLineToPoint:CGPointMake(22, 41)]; - [path addLineToPoint:CGPointMake(22, 45)]; - [path addLineToPoint:CGPointMake(0, 34)]; - [path addLineToPoint:CGPointMake(0, 12)]; - [path closePath]; - return path; -} - -static UIBezierPath *TopSidePath(void) { - UIBezierPath *bezierPath = [UIBezierPath bezierPath]; - [bezierPath moveToPoint:CGPointMake(0.5, 11)]; - [bezierPath addLineToPoint:CGPointMake(22.5, 0)]; - [bezierPath addLineToPoint:CGPointMake(44.5, 11)]; - [bezierPath addLineToPoint:CGPointMake(22.5, 22)]; - [bezierPath addLineToPoint:CGPointMake(0.5, 11)]; - [bezierPath closePath]; - return bezierPath; -} - -static UIBezierPath *RightSidePath(void) { - UIBezierPath *path = [UIBezierPath bezierPath]; - [path moveToPoint:CGPointMake(45, 12)]; - [path addLineToPoint:CGPointMake(23, 23)]; - [path addLineToPoint:CGPointMake(23, 41)]; - [path addLineToPoint:CGPointMake(23, 45)]; - [path addLineToPoint:CGPointMake(45, 34)]; - [path addLineToPoint:CGPointMake(45, 12)]; - [path closePath]; - return path; -} - -static CGFloat const SHIFT = 20.0; -static CGFloat const SCALE = 0.1; - -static CATransform3D LeftSideTransform(void) { - return CATransform3DScale(CATransform3DMakeTranslation(-SHIFT, SHIFT, 0), SCALE, SCALE, SCALE); -} - -static CATransform3D RightSideTransform(void) { - return CATransform3DScale(CATransform3DMakeTranslation(SHIFT, SHIFT, 0), SCALE, SCALE, SCALE); -} - -static CATransform3D TopSideTransform(void) { - return CATransform3DScale(CATransform3DMakeTranslation(0, -SHIFT, 0), SCALE, SCALE, SCALE); -} - -@interface DWDashPayAnimationView () - -@property (readonly, strong, nonatomic) DWAnimatedShapeLayer *leftSideLayer; -@property (readonly, strong, nonatomic) DWAnimatedShapeLayer *topSideLayer; -@property (readonly, strong, nonatomic) DWAnimatedShapeLayer *rightSideLayer; - -@property (readonly, nonatomic, strong) CALayer *leftHeadLayer; -@property (readonly, nonatomic, strong) CALayer *leftBodyLayer; -@property (readonly, nonatomic, strong) CALayer *rightHeadLayer; -@property (readonly, nonatomic, strong) CALayer *rightBodyLayer; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWDashPayAnimationView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setupView]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setupView]; - } - return self; -} - -- (void)layoutSublayersOfLayer:(CALayer *)layer { - [super layoutSublayersOfLayer:layer]; - - if (self.layer == layer) { - const CGRect frame = self.layer.bounds; - self.leftSideLayer.frame = frame; - self.rightSideLayer.frame = frame; - self.topSideLayer.frame = frame; - - const CGSize size = frame.size; - const CGSize headSize = CGSizeMake(6, 6); - const CGSize bodySize = CGSizeMake(12, 4); - const CGFloat spacing = 1.0; - - CGFloat y = (size.height - headSize.height - spacing - bodySize.height) / 2.0; - const CGRect headFrame = CGRectMake((size.width - headSize.width) / 2.0, y, headSize.width, headSize.height); - y += spacing + headSize.height; - const CGRect bodyFrame = CGRectMake((size.width - bodySize.width) / 2.0, y, bodySize.width, bodySize.height); - - self.leftHeadLayer.frame = headFrame; - self.leftBodyLayer.frame = bodyFrame; - self.rightHeadLayer.frame = headFrame; - self.rightBodyLayer.frame = bodyFrame; - - const CGFloat userpicShift = 12; - CATransform3D leftTransform = CATransform3DMakeTranslation(-userpicShift, 0, 0); - leftTransform = CATransform3DRotate(leftTransform, -M_PI_4, 0, 1, 0); - leftTransform = CATransform3DRotate(leftTransform, 20 * M_PI / 180.0, 0, 0, 1); - self.leftHeadLayer.transform = leftTransform; - self.leftBodyLayer.transform = leftTransform; - - CATransform3D rightTransform = CATransform3DMakeTranslation(userpicShift, 0, 0); - rightTransform = CATransform3DRotate(rightTransform, M_PI_4, 0, 1, 0); - rightTransform = CATransform3DRotate(rightTransform, -20 * M_PI / 180.0, 0, 0, 1); - self.rightHeadLayer.transform = rightTransform; - self.rightBodyLayer.transform = rightTransform; - } -} - -- (CGSize)intrinsicContentSize { - return CGSizeMake(45, 56); -} - -- (void)startAnimating { - [self stopAllAnimations]; - [self startAllAnimations]; -} - -- (void)stopAnimating { - [self stopAllAnimations]; -} - -#pragma mark - Private - -- (void)setupView { - DWAnimatedShapeLayer *leftSideLayer = [DWAnimatedShapeLayer layer]; - leftSideLayer.fillColor = [UIColor dw_darkBlueColor].CGColor; - leftSideLayer.path = LeftSidePath().CGPath; - leftSideLayer.opacity = 0; - leftSideLayer.transform = LeftSideTransform(); - [self.layer addSublayer:leftSideLayer]; - _leftSideLayer = leftSideLayer; - - DWAnimatedShapeLayer *rightSideLayer = [DWAnimatedShapeLayer layer]; - rightSideLayer.fillColor = [UIColor dw_darkBlueColor].CGColor; - rightSideLayer.path = RightSidePath().CGPath; - rightSideLayer.opacity = 0; - rightSideLayer.transform = RightSideTransform(); - [self.layer addSublayer:rightSideLayer]; - _rightSideLayer = rightSideLayer; - - DWAnimatedShapeLayer *topSideLayer = [DWAnimatedShapeLayer layer]; - topSideLayer.fillColor = [UIColor colorWithRed:166.0 / 255.0 green:215.0 / 255.0 blue:245.0 / 255.0 alpha:1.0].CGColor; - topSideLayer.path = TopSidePath().CGPath; - topSideLayer.opacity = 0; - topSideLayer.transform = TopSideTransform(); - [self.layer addSublayer:topSideLayer]; - _topSideLayer = topSideLayer; - - UIImage *headImage = [UIImage imageNamed:@"dp_animation_head"]; - CALayer *leftHeadLayer = [CALayer layer]; - leftHeadLayer.contents = (id)headImage.CGImage; - leftHeadLayer.zPosition = 10; - leftHeadLayer.opacity = 0; - [self.layer addSublayer:leftHeadLayer]; - _leftHeadLayer = leftHeadLayer; - - UIImage *bodyImage = [UIImage imageNamed:@"dp_animation_body"]; - CALayer *leftBodyLayer = [CALayer layer]; - leftBodyLayer.contents = (id)bodyImage.CGImage; - leftBodyLayer.zPosition = 10; - leftBodyLayer.opacity = 0; - [self.layer addSublayer:leftBodyLayer]; - _leftBodyLayer = leftBodyLayer; - - CALayer *rightHeadLayer = [CALayer layer]; - rightHeadLayer.contents = (id)headImage.CGImage; - rightHeadLayer.zPosition = 10; - rightHeadLayer.opacity = 0; - [self.layer addSublayer:rightHeadLayer]; - _rightHeadLayer = rightHeadLayer; - - CALayer *rightBodyLayer = [CALayer layer]; - rightBodyLayer.contents = (id)bodyImage.CGImage; - rightBodyLayer.zPosition = 10; - rightBodyLayer.opacity = 0; - [self.layer addSublayer:rightBodyLayer]; - _rightBodyLayer = rightBodyLayer; -} - -- (void)stopAllAnimations { - NSArray *layers = @[ self.leftSideLayer, self.rightSideLayer, self.topSideLayer ]; - [layers makeObjectsPerformSelector:@selector(removeAllAnimations)]; - - NSArray *usersLayers = @[ self.leftHeadLayer, self.leftBodyLayer, self.rightHeadLayer, self.rightBodyLayer ]; - [usersLayers makeObjectsPerformSelector:@selector(removeAllAnimations)]; -} - -- (void)startAllAnimations { - NSArray *layers = @[ self.leftSideLayer, self.rightSideLayer, self.topSideLayer ]; - NSArray *transforms = @[ @(LeftSideTransform()), @(RightSideTransform()), @(TopSideTransform()) ]; - NSAssert(layers.count == transforms.count, @"Internal inconsistency"); - const NSUInteger count = layers.count; - - [layers makeObjectsPerformSelector:@selector(removeAllAnimations)]; - - NSValue *identityTransform = @(CATransform3DIdentity); - - const CFTimeInterval step = 0.4; - - const CFTimeInterval wait = step * count; - - for (NSInteger i = 0; i < count; i++) { - CALayer *layer = layers[i]; - NSValue *transform = transforms[i]; - [self animateCubeSide:i identityTransform:identityTransform layer:layer step:step transform:transform wait:wait]; - } - - NSArray *usersLayers = @[ self.leftHeadLayer, self.leftBodyLayer, self.rightHeadLayer, self.rightBodyLayer ]; - [usersLayers makeObjectsPerformSelector:@selector(removeAllAnimations)]; - - const CFTimeInterval halfStep = step / 2.0; - const CFTimeInterval totalDuration = step * 8; // 1 (show) + 3 (wait) + 1 (hide) + 3 (wait) - - [self animateUserpic:self.leftHeadLayer step:step beginTime:step totalDuration:totalDuration]; - [self animateUserpic:self.leftBodyLayer step:step beginTime:step + halfStep totalDuration:totalDuration]; - [self animateUserpic:self.rightHeadLayer step:step beginTime:step * 2 totalDuration:totalDuration]; - [self animateUserpic:self.rightBodyLayer step:step beginTime:step * 2 + halfStep totalDuration:totalDuration]; -} - -- (void)animateCubeSide:(NSInteger)i - identityTransform:(NSValue *)identityTransform - layer:(CALayer *)layer - step:(CFTimeInterval)step - transform:(NSValue *)transform - wait:(CFTimeInterval)wait { - CABasicAnimation *opacity1 = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacity1.fromValue = @0; - opacity1.toValue = @1; - opacity1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; - opacity1.duration = step; - - CABasicAnimation *transform1 = [CABasicAnimation animationWithKeyPath:@"transform"]; - transform1.toValue = identityTransform; - transform1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; - transform1.duration = step; - - CABasicAnimation *opacity2 = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacity2.fromValue = @1; - opacity2.toValue = @1; - opacity2.duration = wait; - opacity2.beginTime = opacity1.duration; - - CABasicAnimation *transform2 = [CABasicAnimation animationWithKeyPath:@"transform"]; - transform2.fromValue = identityTransform; - transform2.toValue = identityTransform; - transform2.duration = wait; - transform2.beginTime = transform1.duration; - - CABasicAnimation *opacity3 = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacity3.fromValue = @1; - opacity3.toValue = @0; - opacity3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; - opacity3.duration = step; - opacity3.beginTime = opacity2.beginTime + opacity2.duration; - - CABasicAnimation *transform3 = [CABasicAnimation animationWithKeyPath:@"transform"]; - transform3.fromValue = identityTransform; - transform3.toValue = transform; - transform3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; - transform3.duration = step; - transform3.beginTime = transform2.beginTime + transform2.duration; - - CABasicAnimation *opacity4 = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacity4.fromValue = @0; - opacity4.toValue = @0; - opacity4.duration = wait; - opacity4.beginTime = opacity3.beginTime + opacity3.duration; - - CABasicAnimation *transform4 = [CABasicAnimation animationWithKeyPath:@"transform"]; - transform4.fromValue = transform; - transform4.toValue = transform; - transform4.duration = wait; - transform4.beginTime = transform3.beginTime + transform3.duration; - - NSAssert((opacity4.beginTime + opacity4.duration) == (transform4.beginTime + transform4.duration), - @"Invalid animation"); - - CAAnimationGroup *group = [CAAnimationGroup animation]; - group.animations = @[ opacity1, transform1, opacity2, transform2, opacity3, transform3, opacity4, transform4 ]; - group.beginTime = CACurrentMediaTime() + i * step; - group.duration = opacity4.beginTime + opacity4.duration; - group.repeatCount = HUGE_VALF; - - [layer addAnimation:group forKey:@"dw_dp_layer_group"]; - [layer MB_setCurrentAnimationsPersistent]; -} - -- (void)animateUserpic:(CALayer *)layer - step:(CFTimeInterval)step - beginTime:(CFTimeInterval)beginTime - totalDuration:(CFTimeInterval)totalDuration { - const CFTimeInterval halfStep = step / 2.0; - - CABasicAnimation *opacity_s = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacity_s.fromValue = @0; - opacity_s.toValue = @1; - opacity_s.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; - opacity_s.duration = halfStep; - - CABasicAnimation *opacity_sw = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacity_sw.fromValue = @1; - opacity_sw.toValue = @1; - opacity_sw.duration = step * 2; - opacity_sw.beginTime = opacity_s.beginTime + opacity_s.duration; - - CABasicAnimation *opacity_h = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacity_h.fromValue = @1; - opacity_h.toValue = @0; - opacity_h.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; - opacity_h.duration = halfStep; - opacity_h.beginTime = opacity_sw.beginTime + opacity_sw.duration; - - CABasicAnimation *opacity_hw = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacity_hw.fromValue = @0; - opacity_hw.toValue = @0; - opacity_hw.duration = totalDuration - (opacity_h.beginTime + opacity_h.duration); - opacity_hw.beginTime = opacity_h.beginTime + opacity_h.duration; - - CAAnimationGroup *group = [CAAnimationGroup animation]; - group.animations = @[ opacity_s, opacity_sw, opacity_h, opacity_hw ]; - group.beginTime = CACurrentMediaTime() + beginTime; - group.duration = totalDuration; - group.repeatCount = HUGE_VALF; - - [layer addAnimation:group forKey:@"dw_dp_user_animation"]; - [layer MB_setCurrentAnimationsPersistent]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.h b/DashWallet/Sources/UI/DashPay/Views/DWNetworkUnavailableView.h similarity index 87% rename from DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.h rename to DashWallet/Sources/UI/DashPay/Views/DWNetworkUnavailableView.h index a190dd566..d285cbb63 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.h +++ b/DashWallet/Sources/UI/DashPay/Views/DWNetworkUnavailableView.h @@ -19,10 +19,9 @@ NS_ASSUME_NONNULL_BEGIN -@interface UIFont (DWDPItem) +@interface DWNetworkUnavailableView : UIView -+ (UIFont *)dw_itemTitleFont; -+ (UIFont *)dw_itemSubtitleFont; +@property (nullable, copy, nonatomic) NSString *error; @end diff --git a/DashWallet/Sources/UI/DashPay/Views/DWNetworkUnavailableView.m b/DashWallet/Sources/UI/DashPay/Views/DWNetworkUnavailableView.m new file mode 100644 index 000000000..0307345d5 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Views/DWNetworkUnavailableView.m @@ -0,0 +1,89 @@ +// +// 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 "DWNetworkUnavailableView.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWNetworkUnavailableView () + +@property (readonly, strong, nonatomic) UILabel *textLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWNetworkUnavailableView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"network_unavailable"]]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:imageView]; + + UILabel *textLabel = [[UILabel alloc] init]; + textLabel.translatesAutoresizingMaskIntoConstraints = NO; + textLabel.textAlignment = NSTextAlignmentCenter; + textLabel.numberOfLines = 0; + [self addSubview:textLabel]; + _textLabel = textLabel; + + [NSLayoutConstraint activateConstraints:@[ + [imageView.topAnchor constraintEqualToAnchor:self.topAnchor], + [imageView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + + [textLabel.topAnchor constraintEqualToAnchor:imageView.bottomAnchor + constant:12.0], + [textLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:textLabel.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:textLabel.bottomAnchor], + ]]; + } + return self; +} + +- (void)setError:(NSString *)error { + _error = error; + + NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init]; + [string beginEditing]; + + [string appendAttributedString: + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Network Unavailable", nil) + attributes:@{ + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline], + }]]; + + [string appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; + + [string appendAttributedString: + [[NSAttributedString alloc] initWithString:error + attributes:@{ + NSForegroundColorAttributeName : [UIColor dw_secondaryTextColor], + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleCallout], + }]]; + + [string endEditing]; + + self.textLabel.attributedText = string; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.m b/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.m deleted file mode 100644 index 6b36956c9..000000000 --- a/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.m +++ /dev/null @@ -1,40 +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 "UIColor+DWDashPay.h" - -@implementation UIColor (DWDashPay) - -+ (UIColor *)dw_colorWithUsername:(NSString *)username { - if (username.length > 0) { - NSString *letter = [username substringToIndex:1]; - unichar charCode = [letter characterAtIndex:0]; - CGFloat hue; - if (charCode <= 57) { // is digit - hue = (charCode - 48) / 36.0; // 48 == '0', 36 == total count of supported characters - } - else { - hue = (charCode - 65 + 10) / 36.0; // 65 == 'A', 10 == count of digits - } - return [UIColor colorWithHue:hue saturation:0.3 brightness:0.6 alpha:1.0]; - } - else { - return [UIColor blackColor]; - } -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeCollectionViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeCollectionViewController.h new file mode 100644 index 000000000..a16d9362f --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeCollectionViewController.h @@ -0,0 +1,29 @@ +// +// 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 + +@interface DWDPWelcomeCollectionViewController : UIViewController + +- (BOOL)canSwitchToNext; +- (void)switchToNext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeCollectionViewController.m b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeCollectionViewController.m new file mode 100644 index 000000000..2c5d9c91d --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeCollectionViewController.m @@ -0,0 +1,228 @@ +// +// 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 "DWDPWelcomeCollectionViewController.h" + +#import "DWControllerCollectionView.h" +#import "DWDPWelcomePageViewController.h" +#import "DWPassthroughStackView.h" +#import "DWPassthroughView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const SPACE_BEFORE_PAGE = 225; + +@interface DWDPWelcomeCollectionViewController () + +@property (null_resettable, nonatomic, strong) DWControllerCollectionView *controllerCollectionView; +@property (null_resettable, nonatomic, strong) NSArray *controllers; +@property (nullable, nonatomic, strong) NSIndexPath *prevIndexPathAtCenter; +@property (null_resettable, nonatomic, strong) UIPageControl *pageControl; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPWelcomeCollectionViewController + +- (BOOL)canSwitchToNext { + return [self currentIndexPath].item < self.controllers.count - 1; +} + +- (void)switchToNext { + NSAssert([self canSwitchToNext], @"Inconsistent state"); + + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[self currentIndexPath].item + 1 inSection:0]; + [self scrollToIndexPath:indexPath animated:YES]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_backgroundColor]; + + [self.view addSubview:self.controllerCollectionView]; + + DWPassthroughView *imageLikeView = [[DWPassthroughView alloc] init]; + imageLikeView.translatesAutoresizingMaskIntoConstraints = NO; + + DWPassthroughStackView *stack = [[DWPassthroughStackView alloc] initWithArrangedSubviews:@[ imageLikeView, self.pageControl ]]; + stack.translatesAutoresizingMaskIntoConstraints = NO; + stack.spacing = 35; + stack.axis = UILayoutConstraintAxisVertical; + [self.view addSubview:stack]; + + UIView *parent = self.view; + CGFloat padding = 20; + [NSLayoutConstraint activateConstraints:@[ + [self.controllerCollectionView.topAnchor constraintEqualToAnchor:parent.topAnchor], + [self.controllerCollectionView.leadingAnchor constraintEqualToAnchor:parent.leadingAnchor], + [parent.trailingAnchor constraintEqualToAnchor:self.controllerCollectionView.trailingAnchor], + [parent.bottomAnchor constraintEqualToAnchor:self.controllerCollectionView.bottomAnchor], + + [stack.topAnchor constraintGreaterThanOrEqualToAnchor:parent.topAnchor], + [parent.bottomAnchor constraintGreaterThanOrEqualToAnchor:stack.bottomAnchor], + [stack.centerYAnchor constraintEqualToAnchor:parent.centerYAnchor], + + [stack.leadingAnchor constraintEqualToAnchor:parent.leadingAnchor + constant:padding], + [parent.trailingAnchor constraintEqualToAnchor:stack.trailingAnchor + constant:padding], + + [imageLikeView.heightAnchor constraintEqualToConstant:SPACE_BEFORE_PAGE], + ]]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleDefault; +} + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + + self.prevIndexPathAtCenter = [self currentIndexPath]; + + [coordinator + animateAlongsideTransition:nil + completion:^(id _Nonnull context) { + [self.controllerCollectionView.collectionViewLayout invalidateLayout]; + [self scrollToIndexPath:self.prevIndexPathAtCenter animated:NO]; + }]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self.controllerCollectionView reloadData]; +} + +#pragma mark UICollectionViewDelegateFlowLayout + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + return collectionView.bounds.size; +} + +- (CGPoint)collectionView:(UICollectionView *)collectionView targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset { + NSIndexPath *indexPath = self.prevIndexPathAtCenter; + if (!indexPath) { + return proposedContentOffset; + } + + UICollectionViewLayoutAttributes *attributes = + [collectionView layoutAttributesForItemAtIndexPath:indexPath]; + if (!attributes) { + return proposedContentOffset; + } + + const CGPoint newOriginForOldCenter = attributes.frame.origin; + return newOriginForOldCenter; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + const CGFloat offset = scrollView.contentOffset.x; + const CGFloat pageWidth = CGRectGetWidth(scrollView.bounds); + if (pageWidth == 0.0) { + return; + } + const NSInteger pageCount = self.pageControl.numberOfPages; + const NSInteger page = floor((offset - pageWidth / pageCount) / pageWidth) + 1; + self.pageControl.currentPage = page; +} + +#pragma mark - Private + +- (nullable NSIndexPath *)currentIndexPath { + const CGPoint center = [self.view convertPoint:self.controllerCollectionView.center toView:self.controllerCollectionView]; + NSIndexPath *indexPath = [self.controllerCollectionView indexPathForItemAtPoint:center]; + + return indexPath; +} + +- (void)scrollToIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated { + NSParameterAssert(indexPath); + if (!indexPath) { + return; + } + + [self.controllerCollectionView scrollToItemAtIndexPath:indexPath + atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally + animated:animated]; +} + +#pragma mark - DWControllerCollectionViewDataSource + +- (NSInteger)numberOfItemsInControllerCollectionView:(DWControllerCollectionView *)view { + return self.controllers.count; +} + +- (UIViewController *)controllerCollectionView:(DWControllerCollectionView *)view + controllerForIndexPath:(NSIndexPath *)indexPath { + return self.controllers[indexPath.item]; +} + +- (DWControllerCollectionView *)controllerCollectionView { + if (_controllerCollectionView == nil) { + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; + layout.sectionInset = UIEdgeInsetsZero; + layout.minimumLineSpacing = 0; + layout.minimumInteritemSpacing = 0; + + DWControllerCollectionView *collectionView = + [[DWControllerCollectionView alloc] initWithFrame:self.view.bounds + collectionViewLayout:layout]; + collectionView.translatesAutoresizingMaskIntoConstraints = NO; + collectionView.controllerDataSource = self; + collectionView.backgroundColor = [UIColor dw_backgroundColor]; + collectionView.delegate = self; + collectionView.pagingEnabled = YES; + collectionView.containerViewController = self; + collectionView.showsHorizontalScrollIndicator = NO; + _controllerCollectionView = collectionView; + } + return _controllerCollectionView; +} + +- (UIPageControl *)pageControl { + if (_pageControl == nil) { + _pageControl = [[UIPageControl alloc] init]; + _pageControl.translatesAutoresizingMaskIntoConstraints = NO; + _pageControl.numberOfPages = self.controllers.count; + _pageControl.currentPage = 0; + _pageControl.currentPageIndicatorTintColor = [UIColor dw_dashBlueColor]; + _pageControl.pageIndicatorTintColor = [UIColor dw_lightBlueColor]; + _pageControl.userInteractionEnabled = NO; + } + return _pageControl; +} + +- (NSArray *)controllers { + if (_controllers == nil) { + _controllers = @[ + [[DWDPWelcomePageViewController alloc] initWithIndex:0], + [[DWDPWelcomePageViewController alloc] initWithIndex:1], + [[DWDPWelcomePageViewController alloc] initWithIndex:2], + ]; + } + return _controllers; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomePageViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomePageViewController.h new file mode 100644 index 000000000..e2827c78a --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomePageViewController.h @@ -0,0 +1,28 @@ +// +// 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 + +@interface DWDPWelcomePageViewController : UIViewController + +- (instancetype)initWithIndex:(NSUInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomePageViewController.m b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomePageViewController.m new file mode 100644 index 000000000..7bc9bb068 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomePageViewController.m @@ -0,0 +1,134 @@ +// +// 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 "DWDPWelcomePageViewController.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPWelcomePageViewController () + +@property (readonly, assign, nonatomic) NSUInteger index; + +@property (null_resettable, strong, nonatomic) UIImageView *imageView; +@property (null_resettable, strong, nonatomic) UILabel *titleLabel; +@property (null_resettable, strong, nonatomic) UILabel *descLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPWelcomePageViewController + +- (instancetype)initWithIndex:(NSUInteger)index { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _index = index; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_backgroundColor]; + + UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[ + self.imageView, + self.titleLabel, + self.descLabel, + ]]; + stack.translatesAutoresizingMaskIntoConstraints = NO; + stack.axis = UILayoutConstraintAxisVertical; + stack.spacing = 8; + [stack setCustomSpacing:68 afterView:self.imageView]; + [self.view addSubview:stack]; + + UIView *parent = self.view; + CGFloat padding = 20; + [NSLayoutConstraint activateConstraints:@[ + [stack.topAnchor constraintGreaterThanOrEqualToAnchor:parent.topAnchor], + [parent.bottomAnchor constraintGreaterThanOrEqualToAnchor:stack.bottomAnchor], + [stack.centerYAnchor constraintEqualToAnchor:parent.centerYAnchor], + + [stack.leadingAnchor constraintEqualToAnchor:parent.leadingAnchor + constant:padding], + [parent.trailingAnchor constraintEqualToAnchor:stack.trailingAnchor + constant:padding], + ]]; +} + +- (UIImageView *)imageView { + if (_imageView == nil) { + UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"dp_welcome_%ld", self.index]]; + _imageView = [[UIImageView alloc] initWithImage:image]; + _imageView.translatesAutoresizingMaskIntoConstraints = NO; + _imageView.contentMode = UIViewContentModeCenter; + } + return _imageView; +} + +- (UILabel *)titleLabel { + if (_titleLabel == nil) { + _titleLabel = [[UILabel alloc] init]; + _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + _titleLabel.font = [UIFont dw_mediumFontOfSize:20]; + _titleLabel.textColor = [UIColor dw_darkTitleColor]; + _titleLabel.textAlignment = NSTextAlignmentCenter; + _titleLabel.numberOfLines = 0; + _titleLabel.adjustsFontForContentSizeCategory = YES; + NSString *text = nil; + if (self.index == 0) { + text = NSLocalizedString(@"Get your Username", nil); + } + else if (self.index == 1) { + text = NSLocalizedString(@"Add your Friends & Family", nil); + } + else { + text = NSLocalizedString(@"Personalize", nil); + } + _titleLabel.text = text; + } + return _titleLabel; +} + +- (UILabel *)descLabel { + if (_descLabel == nil) { + _descLabel = [[UILabel alloc] init]; + _descLabel.translatesAutoresizingMaskIntoConstraints = NO; + _descLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; + _descLabel.textColor = [UIColor dw_subheaderTextColor]; + _descLabel.textAlignment = NSTextAlignmentCenter; + _descLabel.numberOfLines = 0; + _descLabel.adjustsFontForContentSizeCategory = YES; + NSString *text = nil; + if (self.index == 0) { + text = NSLocalizedString(@"Pay to usernames. No more alphanumeric addresses", nil); + } + else if (self.index == 1) { + text = NSLocalizedString(@"Invite your family, find your friends by searching their usernames", nil); + } + else { + text = NSLocalizedString(@"Upload your picture, personalize your identity", nil); + } + _descLabel.text = text; + } + return _descLabel; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.h new file mode 100644 index 000000000..e6e396659 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.h @@ -0,0 +1,38 @@ +// +// 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 "DWBaseActionButtonViewController.h" + +#import "DWNavigationFullscreenable.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWDPWelcomeViewController; + +@protocol DWDPWelcomeViewControllerDelegate + +- (void)welcomeViewControllerDidFinish:(DWDPWelcomeViewController *)controller; + +@end + +@interface DWDPWelcomeViewController : DWBaseActionButtonViewController + +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.m b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.m new file mode 100644 index 000000000..7ca8f3f94 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.m @@ -0,0 +1,73 @@ +// +// 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 "DWDPWelcomeViewController.h" + +#import "DWDPWelcomeCollectionViewController.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPWelcomeViewController () + +@property (nonatomic, strong) DWDPWelcomeCollectionViewController *collection; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPWelcomeViewController + ++ (BOOL)isActionButtonInNavigationBar { + return NO; +} + +- (NSString *)actionButtonTitle { + return NSLocalizedString(@"Continue", nil); +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.actionButton.enabled = YES; + self.view.backgroundColor = [UIColor dw_backgroundColor]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + [self setupContentView:contentView]; + + DWDPWelcomeCollectionViewController *collection = [[DWDPWelcomeCollectionViewController alloc] init]; + [self dw_embedChild:collection inContainer:contentView]; + self.collection = collection; +} + +- (void)actionButtonAction:(id)sender { + if ([self.collection canSwitchToNext]) { + [self.collection switchToNext]; + } + else { + [self.delegate welcomeViewControllerDidFinish:self]; + } +} + +#pragma mark - DWNavigationFullscreenable + +- (BOOL)requiresNoNavigationBar { + return YES; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.h new file mode 100644 index 000000000..9891a141c --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.h @@ -0,0 +1,38 @@ +// +// 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 "DWNavigationFullscreenable.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWInvitationFlowViewController; + +@protocol DWInvitationFlowViewControllerDelegate + +- (void)invitationFlowViewControllerDidFinish:(DWInvitationFlowViewController *)controller; + +@end + +@interface DWInvitationFlowViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.m b/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.m new file mode 100644 index 000000000..1520318e6 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.m @@ -0,0 +1,82 @@ +// +// 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 "DWInvitationFlowViewController.h" + +#import "DWDPWelcomeViewController.h" +#import "DWGetStartedViewController.h" +#import "DWNavigationController.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWInvitationFlowViewController () + +@property (nonatomic, strong) DWNavigationController *navController; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWInvitationFlowViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_backgroundColor]; + + DWDPWelcomeViewController *welcomeController = [[DWDPWelcomeViewController alloc] init]; + welcomeController.delegate = self; + DWNavigationController *navController = [[DWNavigationController alloc] initWithRootViewController:welcomeController]; + [self dw_embedChild:navController]; + self.navController = navController; +} + +#pragma mark - DWNavigationFullscreenable + +- (BOOL)requiresNoNavigationBar { + return YES; +} + +#pragma mark - DWDPWelcomeViewControllerDelegate + +- (void)welcomeViewControllerDidFinish:(DWDPWelcomeViewController *)controller { + DWGetStartedViewController *getStarted = [[DWGetStartedViewController alloc] initWithPage:DWGetStartedPage_1]; + getStarted.delegate = self; + [self.navController setViewControllers:@[ getStarted ] animated:YES]; +} + +#pragma mark - DWGetStartedViewControllerDelegate + +- (void)getStartedViewControllerDidContinue:(DWGetStartedViewController *)controller { + if (controller.page == DWGetStartedPage_1) { + DWGetStartedViewController *getStarted = [[DWGetStartedViewController alloc] initWithPage:DWGetStartedPage_2]; + getStarted.delegate = self; + [self.navController setViewControllers:@[ getStarted ] animated:YES]; + } + else if (controller.page == DWGetStartedPage_2) { + DWGetStartedViewController *getStarted = [[DWGetStartedViewController alloc] initWithPage:DWGetStartedPage_3]; + getStarted.delegate = self; + [self.navController setViewControllers:@[ getStarted ] animated:YES]; + } + else { + [self.delegate invitationFlowViewControllerDidFinish:self]; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStarted.h similarity index 69% rename from DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.h rename to DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStarted.h index f88e39f1a..97caab04d 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.h +++ b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStarted.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,12 +15,14 @@ // limitations under the License. // -#import "DWBaseContactsContentViewController.h" +#ifndef DWGetStarted_h +#define DWGetStarted_h -NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(NSUInteger, DWGetStartedPage) { + DWGetStartedPage_1, + DWGetStartedPage_2, + DWGetStartedPage_3, +}; -@interface DWRequestsContentViewController : DWBaseContactsContentViewController -@end - -NS_ASSUME_NONNULL_END +#endif /* DWGetStarted_h */ diff --git a/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedContentViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedContentViewController.h new file mode 100644 index 000000000..bbeeea5e9 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedContentViewController.h @@ -0,0 +1,32 @@ +// +// 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 "DWGetStarted.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWGetStartedContentViewController : UIViewController + +@property (readonly, nonatomic, assign) DWGetStartedPage page; + +- (instancetype)initWithPage:(DWGetStartedPage)page; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedContentViewController.m b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedContentViewController.m new file mode 100644 index 000000000..5f3dcb4b8 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedContentViewController.m @@ -0,0 +1,171 @@ +// +// 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 "DWGetStartedContentViewController.h" + +#import "DWGetStartedItemView.h" +#import "DWUIKit.h" + +@implementation DWGetStartedContentViewController + +- (instancetype)initWithPage:(DWGetStartedPage)page { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _page = page; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + titleLabel.textColor = [UIColor dw_secondaryTextColor]; + titleLabel.text = NSLocalizedString(@"Welcome to DashPay", nil); + titleLabel.numberOfLines = 0; + titleLabel.adjustsFontForContentSizeCategory = YES; + [self.view addSubview:titleLabel]; + + UILabel *subtitleLabel = [[UILabel alloc] init]; + subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; + subtitleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleLargeTitle]; + subtitleLabel.textColor = [UIColor dw_darkTitleColor]; + subtitleLabel.text = NSLocalizedString(@"Let’s Get Started", nil); + subtitleLabel.numberOfLines = 0; + subtitleLabel.adjustsFontForContentSizeCategory = YES; + [self.view addSubview:subtitleLabel]; + + UIView *lineView = [[UIView alloc] init]; + lineView.translatesAutoresizingMaskIntoConstraints = NO; + lineView.backgroundColor = [UIColor dw_tertiaryTextColor]; + [self.view addSubview:lineView]; + + + UIView *blueView = [[UIView alloc] init]; + blueView.translatesAutoresizingMaskIntoConstraints = NO; + blueView.backgroundColor = [UIColor dw_lightBlueColor]; + [self.view addSubview:blueView]; + + NSMutableArray *itemViews = [NSMutableArray array]; + NSArray *items = [self items]; + NSArray *completedItems = [self completedItems]; + for (NSUInteger i = 0; i < 3; i++) { + NSNumber *item = items[i]; + NSNumber *completed = completedItems[i]; + + if (i == 1) { + blueView.hidden = !completed.boolValue; + } + + DWGetStartedItemView *itemView = + [[DWGetStartedItemView alloc] initWithItemType:item.unsignedIntegerValue + completed:completed.boolValue]; + itemView.translatesAutoresizingMaskIntoConstraints = NO; + [itemViews addObject:itemView]; + } + + UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:itemViews]; + stack.translatesAutoresizingMaskIntoConstraints = NO; + stack.spacing = 60; + stack.axis = UILayoutConstraintAxisVertical; + [self.view addSubview:stack]; + + UIView *parent = self.view; + [NSLayoutConstraint activateConstraints:@[ + [titleLabel.topAnchor constraintEqualToAnchor:parent.topAnchor + constant:44], + [titleLabel.leadingAnchor constraintEqualToAnchor:parent.leadingAnchor], + [parent.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + + [subtitleLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor], + [subtitleLabel.leadingAnchor constraintEqualToAnchor:parent.leadingAnchor], + [parent.trailingAnchor constraintEqualToAnchor:subtitleLabel.trailingAnchor], + + [stack.topAnchor constraintEqualToAnchor:subtitleLabel.bottomAnchor + constant:40], + [stack.leadingAnchor constraintEqualToAnchor:parent.leadingAnchor], + [parent.trailingAnchor constraintEqualToAnchor:stack.trailingAnchor], + + [lineView.topAnchor constraintEqualToAnchor:stack.topAnchor], + [stack.bottomAnchor constraintEqualToAnchor:lineView.bottomAnchor], + [lineView.leadingAnchor constraintEqualToAnchor:parent.leadingAnchor + constant:30], + [lineView.widthAnchor constraintEqualToConstant:3], + + [blueView.topAnchor constraintEqualToAnchor:lineView.topAnchor], + [blueView.leadingAnchor constraintEqualToAnchor:lineView.leadingAnchor], + [blueView.widthAnchor constraintEqualToConstant:3], + [blueView.heightAnchor constraintEqualToAnchor:lineView.heightAnchor + multiplier:0.5], + ]]; +} + +- (NSArray *)items { + switch (self.page) { + case DWGetStartedPage_1: + return @[ + @(DWGetStartedItemType_1), + @(DWGetStartedItemType_Inactive2), + @(DWGetStartedItemType_Inactive3), + ]; + + case DWGetStartedPage_2: + return @[ + @(DWGetStartedItemType_1), + @(DWGetStartedItemType_Active2), + @(DWGetStartedItemType_Inactive3), + ]; + + case DWGetStartedPage_3: + return @[ + @(DWGetStartedItemType_1), + @(DWGetStartedItemType_Active2), + @(DWGetStartedItemType_Active3), + ]; + } +} + +- (NSArray *)completedItems { + switch (self.page) { + case DWGetStartedPage_1: + return @[ + @NO, + @NO, + @NO, + ]; + + case DWGetStartedPage_2: + return @[ + @YES, + @NO, + @NO, + ]; + + case DWGetStartedPage_3: + return @[ + @YES, + @YES, + @NO, + ]; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.h new file mode 100644 index 000000000..7e56b9cc2 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.h @@ -0,0 +1,42 @@ +// +// 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 "DWBaseActionButtonViewController.h" + +#import "DWGetStarted.h" +#import "DWNavigationFullscreenable.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWGetStartedViewController; + +@protocol DWGetStartedViewControllerDelegate + +- (void)getStartedViewControllerDidContinue:(DWGetStartedViewController *)controller; + +@end + +@interface DWGetStartedViewController : DWBaseActionButtonViewController + +@property (readonly, nonatomic, assign) DWGetStartedPage page; +@property (nullable, nonatomic, weak) id delegate; + +- (instancetype)initWithPage:(DWGetStartedPage)page; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.m b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.m new file mode 100644 index 000000000..583fd64e1 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.m @@ -0,0 +1,68 @@ +// +// 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 "DWGetStartedViewController.h" + +#import "DWGetStartedContentViewController.h" +#import "DWUIKit.h" + +@interface DWGetStartedViewController () + +@end + +@implementation DWGetStartedViewController + ++ (BOOL)isActionButtonInNavigationBar { + return NO; +} + +- (NSString *)actionButtonTitle { + return NSLocalizedString(@"Continue", nil); +} + +- (BOOL)requiresNoNavigationBar { + return YES; +} + +- (instancetype)initWithPage:(DWGetStartedPage)page { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _page = page; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.actionButton.enabled = YES; + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + [self setupContentView:contentView]; + + DWGetStartedContentViewController *content = [[DWGetStartedContentViewController alloc] initWithPage:self.page]; + [self dw_embedChild:content inContainer:contentView]; +} + +- (void)actionButtonAction:(id)sender { + [self.delegate getStartedViewControllerDidContinue:self]; +} + + +@end diff --git a/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/Views/DWGetStartedItemView.h b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/Views/DWGetStartedItemView.h new file mode 100644 index 000000000..82d43fc36 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/Views/DWGetStartedItemView.h @@ -0,0 +1,36 @@ +// +// 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 + +typedef NS_ENUM(NSUInteger, DWGetStartedItemType) { + DWGetStartedItemType_1, + DWGetStartedItemType_Inactive2, + DWGetStartedItemType_Active2, + DWGetStartedItemType_Inactive3, + DWGetStartedItemType_Active3, +}; + +@interface DWGetStartedItemView : UIView + +- (instancetype)initWithItemType:(DWGetStartedItemType)itemType completed:(BOOL)completed; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/Views/DWGetStartedItemView.m b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/Views/DWGetStartedItemView.m new file mode 100644 index 000000000..c9105cfff --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/Views/DWGetStartedItemView.m @@ -0,0 +1,173 @@ +// +// 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 "DWGetStartedItemView.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWGetStartedItemView () + +@end + +static UIImage *ItemTypeImage(DWGetStartedItemType itemType) { + switch (itemType) { + case DWGetStartedItemType_1: + return [UIImage imageNamed:@"dp_get_started_1"]; + case DWGetStartedItemType_Inactive2: + return [UIImage imageNamed:@"dp_get_started_2_dim"]; + case DWGetStartedItemType_Active2: + return [UIImage imageNamed:@"dp_get_started_2"]; + case DWGetStartedItemType_Inactive3: + return [UIImage imageNamed:@"dp_get_started_3_dim"]; + case DWGetStartedItemType_Active3: + return [UIImage imageNamed:@"dp_get_started_3"]; + } +} + +static NSAttributedString *StepText(DWGetStartedItemType itemType, BOOL completed) { + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + NSUInteger step; + switch (itemType) { + case DWGetStartedItemType_1: + step = 1; + break; + case DWGetStartedItemType_Inactive2: + case DWGetStartedItemType_Active2: + step = 2; + break; + case DWGetStartedItemType_Inactive3: + case DWGetStartedItemType_Active3: + step = 3; + break; + } + NSString *prefix = [NSString stringWithFormat:NSLocalizedString(@"Step %ld", @"Step 1"), step]; + NSAttributedString *attPrefix = [[NSAttributedString alloc] initWithString:prefix]; + [result appendAttributedString:attPrefix]; + if (completed) { + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + attachment.image = [UIImage imageNamed:@"dp_get_started_check"]; + attachment.bounds = CGRectMake(0, -4, 16, 16); + [result appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; + [result appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; + } + + return result; +} + +static NSString *NameText(DWGetStartedItemType itemType) { + switch (itemType) { + case DWGetStartedItemType_1: + return NSLocalizedString(@"Choose Your Username", nil); + case DWGetStartedItemType_Inactive2: + case DWGetStartedItemType_Active2: + return NSLocalizedString(@"Set Your PIN", nil); + case DWGetStartedItemType_Inactive3: + case DWGetStartedItemType_Active3: + return NSLocalizedString(@"Secure Your Wallet", nil); + } +} + +static UIColor *NameColor(DWGetStartedItemType itemType) { + switch (itemType) { + case DWGetStartedItemType_1: + return [UIColor dw_darkTitleColor]; + case DWGetStartedItemType_Inactive2: + return [UIColor dw_tertiaryTextColor]; + case DWGetStartedItemType_Active2: + return [UIColor dw_darkTitleColor]; + case DWGetStartedItemType_Inactive3: + return [UIColor dw_tertiaryTextColor]; + case DWGetStartedItemType_Active3: + return [UIColor dw_darkTitleColor]; + } +} + +NS_ASSUME_NONNULL_END + +@implementation DWGetStartedItemView + +- (instancetype)initWithItemType:(DWGetStartedItemType)itemType completed:(BOOL)completed { + self = [super initWithFrame:CGRectZero]; + if (self) { + UIView *rectView = [[UIView alloc] init]; + rectView.translatesAutoresizingMaskIntoConstraints = NO; + rectView.backgroundColor = [UIColor dw_backgroundColor]; + rectView.layer.cornerRadius = 8; + rectView.layer.masksToBounds = YES; + [self addSubview:rectView]; + + UIImageView *icon = [[UIImageView alloc] initWithImage:ItemTypeImage(itemType)]; + icon.translatesAutoresizingMaskIntoConstraints = NO; + icon.contentMode = UIViewContentModeCenter; + [rectView addSubview:icon]; + + UIView *container = [[UIView alloc] init]; + container.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:container]; + + UILabel *stepLabel = [[UILabel alloc] init]; + stepLabel.translatesAutoresizingMaskIntoConstraints = NO; + stepLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; + stepLabel.textColor = [UIColor dw_tertiaryTextColor]; + stepLabel.attributedText = StepText(itemType, completed); + [container addSubview:stepLabel]; + + UILabel *nameLabel = [[UILabel alloc] init]; + nameLabel.translatesAutoresizingMaskIntoConstraints = NO; + nameLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + nameLabel.textColor = NameColor(itemType); + nameLabel.adjustsFontForContentSizeCategory = YES; + nameLabel.numberOfLines = 0; + nameLabel.text = NameText(itemType); + [container addSubview:nameLabel]; + + [NSLayoutConstraint activateConstraints:@[ + [rectView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [rectView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + [rectView.widthAnchor constraintEqualToConstant:60], + [rectView.heightAnchor constraintEqualToConstant:60], + [rectView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], + [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:rectView.bottomAnchor], + + [icon.topAnchor constraintEqualToAnchor:rectView.topAnchor], + [icon.leadingAnchor constraintEqualToAnchor:rectView.leadingAnchor], + [rectView.trailingAnchor constraintEqualToAnchor:icon.trailingAnchor], + [rectView.bottomAnchor constraintEqualToAnchor:icon.bottomAnchor], + + [stepLabel.topAnchor constraintEqualToAnchor:container.topAnchor], + [stepLabel.leadingAnchor constraintEqualToAnchor:container.leadingAnchor], + [container.trailingAnchor constraintEqualToAnchor:stepLabel.trailingAnchor], + + [nameLabel.topAnchor constraintEqualToAnchor:stepLabel.bottomAnchor], + [nameLabel.leadingAnchor constraintEqualToAnchor:container.leadingAnchor], + [container.trailingAnchor constraintEqualToAnchor:nameLabel.trailingAnchor], + [container.bottomAnchor constraintEqualToAnchor:nameLabel.bottomAnchor], + + [container.leadingAnchor constraintEqualToAnchor:rectView.trailingAnchor + constant:12.0], + [self.trailingAnchor constraintEqualToAnchor:container.trailingAnchor], + [container.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + [container.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], + [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:container.bottomAnchor], + ]]; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.h b/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughStackView.h similarity index 82% rename from DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.h rename to DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughStackView.h index 8cc123a3e..746ac2a51 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.h +++ b/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughStackView.h @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,11 +15,11 @@ // limitations under the License. // -#import "DWDPBasicCell.h" +#import NS_ASSUME_NONNULL_BEGIN -@interface DWDPTextStatusCell : DWDPBasicCell +@interface DWPassthroughStackView : UIStackView @end diff --git a/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughStackView.m b/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughStackView.m new file mode 100644 index 000000000..9608bf933 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughStackView.m @@ -0,0 +1,27 @@ +// +// 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 "DWPassthroughStackView.h" + +@implementation DWPassthroughStackView + +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + UIView *view = [super hitTest:point withEvent:event]; + return view == self ? nil : view; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughView.h b/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughView.h new file mode 100644 index 000000000..009ebe7fa --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughView.h @@ -0,0 +1,26 @@ +// +// 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 + +@interface DWPassthroughView : UIView + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.m b/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughView.m similarity index 67% rename from DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.m rename to DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughView.m index 88875fa62..de82ee5cb 100644 --- a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.m +++ b/DashWallet/Sources/UI/DashPay/Welcome/Views/DWPassthroughView.m @@ -1,6 +1,6 @@ // // Created by Andrew Podkovyrin -// Copyright © 2020 Dash Core Group. All rights reserved. +// 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. @@ -15,14 +15,13 @@ // limitations under the License. // -#import "DWDPPendingRequestObject.h" +#import "DWPassthroughView.h" -#import +@implementation DWPassthroughView -@implementation DWDPPendingRequestObject - -- (DSFriendRequestEntity *)friendRequestToPay { - return nil; +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + UIView *view = [super hitTest:point withEvent:event]; + return view == self ? nil : view; } @end diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController.m b/DashWallet/Sources/UI/Home/DWHomeViewController.m index d1b7ce007..0a3ce118b 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController.m @@ -106,7 +106,7 @@ - (void)homeView:(DWHomeView *)homeView showSyncingStatus:(UIView *)sender { } - (void)homeView:(DWHomeView *)homeView profileButtonAction:(UIControl *)sender { - DWNotificationsViewController *controller = [[DWNotificationsViewController alloc] init]; + DWNotificationsViewController *controller = [[DWNotificationsViewController alloc] initWithPayModel:self.payModel dataProvider:self.dataProvider]; [self.navigationController pushViewController:controller animated:YES]; } @@ -206,8 +206,8 @@ - (void)showReclassifyYourTransactionsIfPossibleWithTransaction:(DSTransaction * } - (void)presentTransactionDetails:(DSTransaction *)transaction { - TxDetailModel *model = [[TxDetailModel alloc] initWithTransaction:transaction]; - TXDetailViewController *controller = [[TXDetailViewController alloc] initWithModel:model]; + DWTxDetailModel *model = [[DWTxDetailModel alloc] initWithTransaction:transaction]; + DWTxDetailViewController *controller = [[DWTxDetailViewController alloc] initWithModel:model]; DWNavigationController *nvc = [[DWNavigationController alloc] initWithRootViewController:controller]; [self presentViewController:nvc animated:YES completion:nil]; diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.h b/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.h index b484e0f96..5aa8abf88 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.h +++ b/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.h @@ -16,6 +16,7 @@ // #import +#import "BaseCollectionReusableView.h" NS_ASSUME_NONNULL_BEGIN @@ -24,10 +25,11 @@ NS_ASSUME_NONNULL_BEGIN @protocol DWFilterHeaderViewDelegate - (void)filterHeaderView:(DWFilterHeaderView *)view filterButtonAction:(UIView *)sender; +- (void)filterHeaderView:(DWFilterHeaderView *)view infoButtonAction:(UIView *)sender; @end -@interface DWFilterHeaderView : UICollectionReusableView +@interface DWFilterHeaderView : BaseCollectionReusableView @property (strong, nonatomic) IBOutlet UILabel *titleLabel; @property (strong, nonatomic) IBOutlet UIButton *filterButton; @@ -36,6 +38,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, weak) id delegate; +@property (strong, nonatomic) IBOutlet UIButton *infoButton; + @end NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.m b/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.m index 50e2005ac..aaa1a873c 100644 --- a/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.m +++ b/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.m @@ -65,6 +65,8 @@ - (void)commonInit { self.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; self.filterButton.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; + + [self.infoButton setHidden:YES]; } - (void)setPadding:(CGFloat)padding { @@ -79,6 +81,10 @@ - (IBAction)filterButtonAction:(UIButton *)sender { [self.delegate filterHeaderView:self filterButtonAction:sender]; } +- (IBAction)infoButtonAction:(UIButton *)sender { + [self.delegate filterHeaderView:self infoButtonAction:sender]; +} + @end NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m index 0bf0baf2e..cc298f559 100644 --- a/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m +++ b/DashWallet/Sources/UI/Menu/Main/DWMainMenuViewController.m @@ -31,6 +31,10 @@ #import "SFSafariViewController+DashWallet.h" #import "dashwallet-Swift.h" +#ifdef DASHPAY +#import "DWMainMenuViewController+DashPay.h" +#endif + NS_ASSUME_NONNULL_BEGIN @interface DWMainMenuViewController () 'https://github.com/ameingast/cocoaimagehashing.git', :commit => 'ad01eee' pod 'SDWebImage', '5.13.2' pod 'Moya', '~> 15.0' + pod 'TOCropViewController', '2.6.1' + # Debugging purposes # pod 'Reveal-SDK', :configurations => ['Debug'] diff --git a/Podfile.lock b/Podfile.lock index 34a53fe9c..0f5be350d 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) @@ -714,6 +714,7 @@ PODS: - tinycbor (0.5.4-alpha1) - TinyCborObjc (0.4.6): - tinycbor (= 0.5.4-alpha1) + - TOCropViewController (2.6.1) - UIViewController-KeyboardAdditions (1.2.1) DEPENDENCIES: @@ -731,6 +732,7 @@ DEPENDENCIES: - SQLite.swift (~> 0.13.3) - SQLiteMigrationManager.swift - SSZipArchive + - TOCropViewController (= 2.6.1) - UIViewController-KeyboardAdditions (= 1.2.1) SPEC REPOS: @@ -770,6 +772,7 @@ SPEC REPOS: - SSZipArchive - tinycbor - TinyCborObjc + - TOCropViewController - UIViewController-KeyboardAdditions EXTERNAL SOURCES: @@ -822,15 +825,16 @@ SPEC CHECKSUMS: Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - Protobuf: 3dd214698ddb7afd832da281c777b2e9facef401 + Protobuf: 5df4e9d2abd535fb675c68d171f6bc87983d27a6 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 SQLite.swift: 903bfa3bc9ab06345fdfbb578e34f47cfcf417da SQLiteMigrationManager.swift: 5383578f5bc8955c06695e8bf04835ee0e6673a8 SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef tinycbor: d4d71dddda1f8392fbb4249f63faf8552f327590 TinyCborObjc: 5204540fb90ff0c40fb22d408fa51bab79d78a80 + TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 UIViewController-KeyboardAdditions: a691dc7e63a49854d341455a778ee8497dfc4662 -PODFILE CHECKSUM: 923bcf58ceb233fa904192267a41641c010ae7f9 +PODFILE CHECKSUM: c83209e59b05badf5f6be34c40c5780bbdd9ebb2 COCOAPODS: 1.12.1 From 8f443f6ff1526e632bc93663bac95cf296b10a61 Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 20 Jun 2023 00:16:38 +0400 Subject: [PATCH 006/123] refactor: Re-org DashPay structure --- .../Error/DWNetworkErrorViewController.h | 39 ++ .../Error/DWNetworkErrorViewController.m | 107 +++++ .../Home}/Cells/DWFilterHeaderView.h | 0 .../Home}/Cells/DWFilterHeaderView.m | 0 .../Home}/Cells/DWFilterHeaderView.xib | 0 .../DWDPRegistrationDoneTableViewCell.h | 0 .../DWDPRegistrationDoneTableViewCell.m | 0 .../DWDPRegistrationDoneTableViewCell.xib | 0 .../DWDPRegistrationErrorRetryDelegate.h | 0 .../DWDPRegistrationErrorTableViewCell.h | 0 .../DWDPRegistrationErrorTableViewCell.m | 0 .../DWDPRegistrationErrorTableViewCell.xib | 0 .../DWDPRegistrationStatusTableViewCell.h | 0 .../DWDPRegistrationStatusTableViewCell.m | 0 .../DWDPRegistrationStatusTableViewCell.xib | 0 .../DWConfirmUsernameViewController.h | 40 ++ .../DWConfirmUsernameViewController.m | 75 ++++ .../Views/DWConfirmUsernameContentView.h | 32 ++ .../Views/DWConfirmUsernameContentView.m | 142 +++++++ .../Views/DWConfirmUsernameContentView.xib | 77 ++++ .../DWCreateUsernameViewController.h | 48 +++ .../DWCreateUsernameViewController.m | 116 ++++++ .../DWInputUsernameViewController.h | 37 ++ .../DWInputUsernameViewController.m | 285 +++++++++++++ ...WAllowedCharactersUsernameValidationRule.h | 26 ++ ...WAllowedCharactersUsernameValidationRule.m | 57 +++ .../DWCheckExistenceUsernameValidationRule.h | 39 ++ .../DWCheckExistenceUsernameValidationRule.m | 140 +++++++ .../DWFirstUsernameSymbolValidationRule.h | 26 ++ .../DWFirstUsernameSymbolValidationRule.m | 44 ++ .../Models/DWLengthUsernameValidationRule.h | 26 ++ .../Models/DWLengthUsernameValidationRule.m | 41 ++ .../DWUsernameValidationRule+Protected.h | 28 ++ .../Models/DWUsernameValidationRule.h | 48 +++ .../Models/DWUsernameValidationRule.m | 34 ++ .../Views/DWPlanetarySystemView.h | 62 +++ .../Views/DWPlanetarySystemView.m | 241 +++++++++++ .../Setup/CreateUsername/Views/DWTextField.h | 31 ++ .../Setup/CreateUsername/Views/DWTextField.m | 43 ++ .../Views/DWUsernameHeaderView.h | 40 ++ .../Views/DWUsernameHeaderView.m | 338 ++++++++++++++++ .../Views/DWUsernameValidationView.h | 34 ++ .../Views/DWUsernameValidationView.m | 139 +++++++ .../Setup/DWDashPaySetupFlowController.h | 49 +++ .../Setup/DWDashPaySetupFlowController.m | 316 +++++++++++++++ .../DWRegistrationCompletedViewController.h | 35 ++ .../DWRegistrationCompletedViewController.m | 156 ++++++++ .../DWUsernamePendingViewController.h | 37 ++ .../DWUsernamePendingViewController.m | 205 ++++++++++ DashWallet.xcodeproj/project.pbxproj | 376 +++++++++--------- 50 files changed, 3420 insertions(+), 189 deletions(-) create mode 100644 DashPay/Presentation/Error/DWNetworkErrorViewController.h create mode 100644 DashPay/Presentation/Error/DWNetworkErrorViewController.m rename {DashWallet/Sources/UI/Home/Views => DashPay/Presentation/Home}/Cells/DWFilterHeaderView.h (100%) rename {DashWallet/Sources/UI/Home/Views => DashPay/Presentation/Home}/Cells/DWFilterHeaderView.m (100%) rename {DashWallet/Sources/UI/Home/Views => DashPay/Presentation/Home}/Cells/DWFilterHeaderView.xib (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationDoneTableViewCell.h (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationDoneTableViewCell.m (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationDoneTableViewCell.xib (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationErrorRetryDelegate.h (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationErrorTableViewCell.h (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationErrorTableViewCell.m (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationErrorTableViewCell.xib (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationStatusTableViewCell.h (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationStatusTableViewCell.m (100%) rename {DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus => DashPay/Presentation/Home/Cells/Registration Status}/DWDPRegistrationStatusTableViewCell.xib (100%) create mode 100644 DashPay/Presentation/Setup/ConfirmUsername/DWConfirmUsernameViewController.h create mode 100644 DashPay/Presentation/Setup/ConfirmUsername/DWConfirmUsernameViewController.m create mode 100644 DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.h create mode 100644 DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.m create mode 100644 DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.xib create mode 100644 DashPay/Presentation/Setup/CreateUsername/DWCreateUsernameViewController.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/DWCreateUsernameViewController.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/DWInputUsernameViewController.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/DWInputUsernameViewController.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule+Protected.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/Views/DWPlanetarySystemView.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Views/DWPlanetarySystemView.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/Views/DWTextField.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Views/DWTextField.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameHeaderView.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameHeaderView.m create mode 100644 DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameValidationView.h create mode 100644 DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameValidationView.m create mode 100644 DashPay/Presentation/Setup/DWDashPaySetupFlowController.h create mode 100644 DashPay/Presentation/Setup/DWDashPaySetupFlowController.m create mode 100644 DashPay/Presentation/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.h create mode 100644 DashPay/Presentation/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.m create mode 100644 DashPay/Presentation/Setup/UsernamePending/DWUsernamePendingViewController.h create mode 100644 DashPay/Presentation/Setup/UsernamePending/DWUsernamePendingViewController.m diff --git a/DashPay/Presentation/Error/DWNetworkErrorViewController.h b/DashPay/Presentation/Error/DWNetworkErrorViewController.h new file mode 100644 index 000000000..6fb0c9902 --- /dev/null +++ b/DashPay/Presentation/Error/DWNetworkErrorViewController.h @@ -0,0 +1,39 @@ +// +// 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 + +typedef NS_ENUM(NSUInteger, DWErrorDescriptionType) { + DWErrorDescriptionType_Profile, + DWErrorDescriptionType_AcceptContactRequest, + DWErrorDescriptionType_SendContactRequest, +}; + +@interface DWNetworkErrorViewController : UIViewController + +- (instancetype)initWithType:(DWErrorDescriptionType)type; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; +- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Error/DWNetworkErrorViewController.m b/DashPay/Presentation/Error/DWNetworkErrorViewController.m new file mode 100644 index 000000000..b7b859021 --- /dev/null +++ b/DashPay/Presentation/Error/DWNetworkErrorViewController.m @@ -0,0 +1,107 @@ +// +// 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 "DWNetworkErrorViewController.h" + +#import "DWModalPopupTransition.h" +#import "DWNetworkUnavailableView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWNetworkErrorViewController () + +@property (nonatomic, strong) DWModalPopupTransition *modalTransition; +@property (nonatomic, assign) DWErrorDescriptionType type; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWNetworkErrorViewController + +- (instancetype)initWithType:(DWErrorDescriptionType)type { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _type = type; + + _modalTransition = [[DWModalPopupTransition alloc] initWithInteractiveTransitionAllowed:NO]; + + self.transitioningDelegate = self.modalTransition; + self.modalPresentationStyle = UIModalPresentationCustom; + } + return self; +} + +- (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]; + + DWNetworkUnavailableView *errorView = [[DWNetworkUnavailableView alloc] initWithFrame:CGRectZero]; + errorView.translatesAutoresizingMaskIntoConstraints = NO; + switch (self.type) { + case DWErrorDescriptionType_Profile: + errorView.error = NSLocalizedString(@"Unable to fetch contact details", nil); + break; + case DWErrorDescriptionType_AcceptContactRequest: + errorView.error = NSLocalizedString(@"Unable to accept contact request", nil); + break; + case DWErrorDescriptionType_SendContactRequest: + errorView.error = NSLocalizedString(@"Unable to send contact request", nil); + break; + } + [contentView addSubview:errorView]; + + UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeSystem]; + closeButton.translatesAutoresizingMaskIntoConstraints = NO; + [closeButton setTitle:NSLocalizedString(@"Close", nil) forState:UIControlStateNormal]; + [closeButton addTarget:self action:@selector(closeButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + [contentView addSubview:closeButton]; + + + [NSLayoutConstraint activateConstraints:@[ + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + [contentView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], + + [errorView.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:32.0], + [errorView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [errorView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + + [closeButton.topAnchor constraintEqualToAnchor:errorView.bottomAnchor + constant:32.0], + [closeButton.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:closeButton.bottomAnchor + constant:16.0], + [closeButton.heightAnchor constraintGreaterThanOrEqualToConstant:44.0], + ]]; +} + +- (void)closeButtonAction:(UIButton *)sender { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.h b/DashPay/Presentation/Home/Cells/DWFilterHeaderView.h similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.h rename to DashPay/Presentation/Home/Cells/DWFilterHeaderView.h diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.m b/DashPay/Presentation/Home/Cells/DWFilterHeaderView.m similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.m rename to DashPay/Presentation/Home/Cells/DWFilterHeaderView.m diff --git a/DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.xib b/DashPay/Presentation/Home/Cells/DWFilterHeaderView.xib similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/DWFilterHeaderView.xib rename to DashPay/Presentation/Home/Cells/DWFilterHeaderView.xib diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationDoneTableViewCell.h b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationDoneTableViewCell.h similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationDoneTableViewCell.h rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationDoneTableViewCell.h diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationDoneTableViewCell.m b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationDoneTableViewCell.m similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationDoneTableViewCell.m rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationDoneTableViewCell.m diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationDoneTableViewCell.xib b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationDoneTableViewCell.xib similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationDoneTableViewCell.xib rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationDoneTableViewCell.xib diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationErrorRetryDelegate.h b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationErrorRetryDelegate.h similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationErrorRetryDelegate.h rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationErrorRetryDelegate.h diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationErrorTableViewCell.h b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationErrorTableViewCell.h similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationErrorTableViewCell.h rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationErrorTableViewCell.h diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationErrorTableViewCell.m b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationErrorTableViewCell.m similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationErrorTableViewCell.m rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationErrorTableViewCell.m diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationErrorTableViewCell.xib b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationErrorTableViewCell.xib similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationErrorTableViewCell.xib rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationErrorTableViewCell.xib diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationStatusTableViewCell.h b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationStatusTableViewCell.h similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationStatusTableViewCell.h rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationStatusTableViewCell.h diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationStatusTableViewCell.m b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationStatusTableViewCell.m similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationStatusTableViewCell.m rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationStatusTableViewCell.m diff --git a/DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationStatusTableViewCell.xib b/DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationStatusTableViewCell.xib similarity index 100% rename from DashWallet/Sources/UI/Home/Views/Cells/RegistrationStatus/DWDPRegistrationStatusTableViewCell.xib rename to DashPay/Presentation/Home/Cells/Registration Status/DWDPRegistrationStatusTableViewCell.xib diff --git a/DashPay/Presentation/Setup/ConfirmUsername/DWConfirmUsernameViewController.h b/DashPay/Presentation/Setup/ConfirmUsername/DWConfirmUsernameViewController.h new file mode 100644 index 000000000..2a1dd2fb4 --- /dev/null +++ b/DashPay/Presentation/Setup/ConfirmUsername/DWConfirmUsernameViewController.h @@ -0,0 +1,40 @@ +// +// 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 "DWBaseModalViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWConfirmUsernameViewController; + +@protocol DWConfirmUsernameViewControllerDelegate + +- (void)confirmUsernameViewControllerDidConfirm:(DWConfirmUsernameViewController *)controller; + +@end + +@interface DWConfirmUsernameViewController : DWBaseModalViewController + +@property (readonly, nonatomic, copy) NSString *username; + +@property (nullable, nonatomic, weak) id delegate; + +- (instancetype)initWithUsername:(NSString *)username; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/ConfirmUsername/DWConfirmUsernameViewController.m b/DashPay/Presentation/Setup/ConfirmUsername/DWConfirmUsernameViewController.m new file mode 100644 index 000000000..6881fa893 --- /dev/null +++ b/DashPay/Presentation/Setup/ConfirmUsername/DWConfirmUsernameViewController.m @@ -0,0 +1,75 @@ +// +// 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 "DWConfirmUsernameViewController.h" + +#import "DWConfirmUsernameContentView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWConfirmUsernameViewController () + +@property (nonatomic, strong) DWConfirmUsernameContentView *confirmUsernameView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWConfirmUsernameViewController + +- (instancetype)initWithUsername:(NSString *)username { + self = [super init]; + if (self) { + _username = [username copy]; + } + return self; +} + ++ (BOOL)isActionButtonInNavigationBar { + return NO; +} + +- (NSString *)actionButtonTitle { + return NSLocalizedString(@"Confirm", nil); +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self setModalTitle:NSLocalizedString(@"Confirm", nil)]; + + self.actionButton.enabled = NO; + + self.confirmUsernameView = [[DWConfirmUsernameContentView alloc] initWithFrame:CGRectZero]; + self.confirmUsernameView.username = self.username; + [self.confirmUsernameView.confirmationCheckbox addTarget:self + action:@selector(confirmationCheckboxAction:) + forControlEvents:UIControlEventValueChanged]; + + [self setupModalContentView:self.confirmUsernameView]; +} + +- (void)actionButtonAction:(id)sender { + self.actionButton.enabled = NO; + [self.delegate confirmUsernameViewControllerDidConfirm:self]; +} + +- (void)confirmationCheckboxAction:(DWCheckbox *)sender { + self.actionButton.enabled = sender.isOn; +} + +@end diff --git a/DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.h b/DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.h new file mode 100644 index 000000000..121e321f0 --- /dev/null +++ b/DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.h @@ -0,0 +1,32 @@ +// +// 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 + +#import "DWCheckbox.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWConfirmUsernameContentView : UIView + +@property (readonly, nonatomic, weak) DWCheckbox *confirmationCheckbox; + +@property (nullable, nonatomic, copy) NSString *username; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.m b/DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.m new file mode 100644 index 000000000..f78bb854a --- /dev/null +++ b/DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.m @@ -0,0 +1,142 @@ +// +// 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 "DWConfirmUsernameContentView.h" + +#import "DWCheckbox.h" +#import "DWUIKit.h" +#import "dashwallet-Swift.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +static CGSize const DashSymbolMainSize = {35.0, 27.0}; + +@interface DWConfirmUsernameContentView () + +@property (strong, nonatomic) IBOutlet UIView *contentView; +@property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; +@property (weak, nonatomic) IBOutlet DWCheckbox *confirmationCheckbox; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWConfirmUsernameContentView + +- (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_backgroundColor]; + + self.confirmationCheckbox.title = NSLocalizedString(@"I Accept", nil); + self.confirmationCheckbox.backgroundColor = self.backgroundColor; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(contentSizeCategoryDidChangeNotification) + name:UIContentSizeCategoryDidChangeNotification + object:nil]; +} + +- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + [self reloadAttributedData]; +} + +- (void)setUsername:(NSString *)username { + _username = username; + + [self reloadAttributedData]; +} + +#pragma mark - Private + +- (void)reloadAttributedData { + if (self.username.length == 0) { + self.descriptionLabel.attributedText = nil; + return; + } + + UIColor *color = [UIColor dw_secondaryTextColor]; + NSString *format = NSLocalizedString(@"You have chose \"%@\" as your username. Username cannot be changed once it is registered.", nil); + NSString *text = [NSString stringWithFormat:format, self.username]; + + NSDictionary *attributes = @{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote], + NSForegroundColorAttributeName : color, + }; + + NSRange usernameRange = [text rangeOfString:self.username]; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:text attributes:attributes]; + [result beginEditing]; + [result removeAttribute:NSFontAttributeName range:usernameRange]; + [result addAttribute:NSFontAttributeName value:[UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline] range:usernameRange]; + [result endEditing]; + + self.descriptionLabel.attributedText = result; +} + +#pragma mark - Notifications + +- (void)contentSizeCategoryDidChangeNotification { + [self reloadAttributedData]; +} + +#pragma mark - Private + +- (NSAttributedString *)mainAmountAttributedStringForAmount:(uint64_t)amount { + return [NSAttributedString dw_dashAttributedStringForAmount:amount + tintColor:[UIColor dw_darkTitleColor] + symbolSize:DashSymbolMainSize]; +} + +- (NSString *)supplementaryAmountStringForAmount:(uint64_t)amount { + DSPriceManager *priceManager = [DSPriceManager sharedInstance]; + NSString *supplementaryAmount = [priceManager localCurrencyStringForDashAmount:amount]; + + return supplementaryAmount; +} + +@end diff --git a/DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.xib b/DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.xib new file mode 100644 index 000000000..d65b13d3a --- /dev/null +++ b/DashPay/Presentation/Setup/ConfirmUsername/Views/DWConfirmUsernameContentView.xib @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DashPay/Presentation/Setup/CreateUsername/DWCreateUsernameViewController.h b/DashPay/Presentation/Setup/CreateUsername/DWCreateUsernameViewController.h new file mode 100644 index 000000000..5199baa0f --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/DWCreateUsernameViewController.h @@ -0,0 +1,48 @@ +// +// 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 + +#import "DWDashPayProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWCreateUsernameViewController; + +@protocol DWCreateUsernameViewControllerDelegate + +- (void)createUsernameViewController:(DWCreateUsernameViewController *)controller + registerUsername:(NSString *)username; + +@end + +@interface DWCreateUsernameViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +- (NSAttributedString *)attributedTitle; + +- (instancetype)initWithDashPayModel:(id)dashPayModel; + +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/DWCreateUsernameViewController.m b/DashPay/Presentation/Setup/CreateUsername/DWCreateUsernameViewController.m new file mode 100644 index 000000000..ba8fe1b31 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/DWCreateUsernameViewController.m @@ -0,0 +1,116 @@ +// +// 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 "DWCreateUsernameViewController.h" + +#import "DWInputUsernameViewController.h" +#import "DWUIKit.h" +#import "UIViewController+DWEmbedding.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWCreateUsernameViewController () + +@property (readonly, nonatomic, strong) id dashPayModel; +@property (null_resettable, nonatomic, strong) DWInputUsernameViewController *inputUsername; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWCreateUsernameViewController + +- (instancetype)initWithDashPayModel:(id)dashPayModel { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _dashPayModel = dashPayModel; + } + return self; +} + +- (NSAttributedString *)attributedTitle { + NSDictionary *regularAttributes = @{ + NSFontAttributeName : [UIFont dw_regularFontOfSize:22.0], + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + }; + + NSDictionary *emphasizedAttributes = @{ + NSFontAttributeName : [UIFont dw_mediumFontOfSize:22.0], + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + }; + + NSAttributedString *chooseYourString = + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Choose your", @"Choose your Dash username") + attributes:regularAttributes]; + + NSAttributedString *spaceString = + [[NSAttributedString alloc] initWithString:@"\n" + attributes:regularAttributes]; + + NSAttributedString *dashUsernameString = + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Dash username", nil) + attributes:emphasizedAttributes]; + + NSMutableAttributedString *resultString = [[NSMutableAttributedString alloc] init]; + [resultString beginEditing]; + [resultString appendAttributedString:chooseYourString]; + [resultString appendAttributedString:spaceString]; + [resultString appendAttributedString:dashUsernameString]; + [resultString endEditing]; + + return resultString; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.clipsToBounds = YES; + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + [self dw_embedChild:self.inputUsername inContainer:self.view]; + + NSLayoutConstraint *heightConstraint = [self.inputUsername.view.heightAnchor constraintEqualToAnchor:self.view.heightAnchor]; + heightConstraint.priority = UILayoutPriorityRequired - 1; + + [NSLayoutConstraint activateConstraints:@[ + heightConstraint, + [self.inputUsername.view.widthAnchor constraintEqualToAnchor:self.view.widthAnchor] + ]]; +} + +- (DWInputUsernameViewController *)inputUsername { + if (_inputUsername == nil) { + _inputUsername = [[DWInputUsernameViewController alloc] init]; + _inputUsername.delegate = self; + } + + return _inputUsername; +} + +#pragma mark - DWInputUsernameViewControllerDelegate + +- (void)inputUsernameViewControllerRegisterAction:(DWInputUsernameViewController *)inputController { + [self.delegate createUsernameViewController:self registerUsername:inputController.text]; +} + +#pragma mark - Actions + +- (void)cancelButtonAction { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/DWInputUsernameViewController.h b/DashPay/Presentation/Setup/CreateUsername/DWInputUsernameViewController.h new file mode 100644 index 000000000..ff280c6a1 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/DWInputUsernameViewController.h @@ -0,0 +1,37 @@ +// +// 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 "DWBaseViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWInputUsernameViewController; + +@protocol DWInputUsernameViewControllerDelegate + +- (void)inputUsernameViewControllerRegisterAction:(DWInputUsernameViewController *)controller; + +@end + +@interface DWInputUsernameViewController : DWBaseViewController + +@property (nullable, nonatomic, copy) NSString *text; +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/DWInputUsernameViewController.m b/DashPay/Presentation/Setup/CreateUsername/DWInputUsernameViewController.m new file mode 100644 index 000000000..f23acf734 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/DWInputUsernameViewController.m @@ -0,0 +1,285 @@ +// +// 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 "DWInputUsernameViewController.h" + +#import + +#import "DWActionButton.h" +#import "DWAllowedCharactersUsernameValidationRule.h" +#import "DWBaseActionButtonViewController.h" +#import "DWCheckExistenceUsernameValidationRule.h" +#import "DWDashPayConstants.h" +#import "DWFirstUsernameSymbolValidationRule.h" +#import "DWLengthUsernameValidationRule.h" +#import "DWTextField.h" +#import "DWUIKit.h" +#import "DWUsernameValidationView.h" +#import "dashwallet-Swift.h" + +static CGFloat const SPACING = 16.0; +static CGFloat const CORNER_RADIUS = 10.0; +static CGFloat const TEXTFIELD_MAX_HEIGHT = 56.0; + +NS_ASSUME_NONNULL_BEGIN + +@interface DWInputUsernameViewController () + +@property (null_resettable, nonatomic, strong) UIStackView *stackView; +@property (null_resettable, nonatomic, strong) UITextField *textField; +@property (null_resettable, nonatomic, strong) UIStackView *validationContentView; +@property (nonatomic, copy) NSArray *validationViews; +@property (null_resettable, nonatomic, strong) UIButton *registerButton; + +@property (nullable, nonatomic, strong) NSLayoutConstraint *contentBottomConstraint; + +@property (null_resettable, nonatomic, strong) DWCheckExistenceUsernameValidationRule *checkExistenceValidator; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWInputUsernameViewController + +- (NSString *)text { + return self.textField.text; +} + +- (void)setText:(NSString *)text { + self.textField.text = text; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + NSArray *validators = @[ + [[DWLengthUsernameValidationRule alloc] init], + [[DWAllowedCharactersUsernameValidationRule alloc] init], + [[DWFirstUsernameSymbolValidationRule alloc] init], + self.checkExistenceValidator, + ]; + + NSMutableArray *validationViews = [NSMutableArray array]; + for (DWUsernameValidationRule *validationRule in validators) { + DWUsernameValidationView *validationView = [[DWUsernameValidationView alloc] initWithFrame:CGRectZero]; + validationView.translatesAutoresizingMaskIntoConstraints = NO; + validationView.rule = validationRule; + [self.validationContentView addArrangedSubview:validationView]; + [validationViews addObject:validationView]; + + [validationView.heightAnchor constraintLessThanOrEqualToConstant:26.0].active = YES; + } + self.validationViews = validationViews; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ self.validationContentView ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.alignment = UIStackViewAlignmentTop; + + [self.view addSubview:self.stackView]; + + [self.stackView addArrangedSubview: self.textField]; + [self.stackView addArrangedSubview: stackView]; + [self.stackView addArrangedSubview: [UIView new]]; + [self.stackView addArrangedSubview: self.registerButton]; + + [self updateValidationContentViewForSize:self.view.bounds.size]; + + UILayoutGuide *marginsGuide = self.view.layoutMarginsGuide; + UILayoutGuide *safeAreaGuide = self.view.safeAreaLayoutGuide; + + const CGFloat bottomPadding = [self.class deviceSpecificBottomPadding]; + // constraint relation is inverted so we can use positive padding values + self.contentBottomConstraint = [safeAreaGuide.bottomAnchor constraintEqualToAnchor:self.stackView.bottomAnchor + constant:bottomPadding]; + + [NSLayoutConstraint activateConstraints:@[ + [self.textField.heightAnchor constraintLessThanOrEqualToConstant:TEXTFIELD_MAX_HEIGHT], + [self.registerButton.heightAnchor constraintEqualToConstant:DWBottomButtonHeight()], + + [self.stackView.topAnchor constraintEqualToAnchor:safeAreaGuide.topAnchor + constant:SPACING], + [self.stackView.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], + [self.stackView.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], + //[self.stackView.bottomAnchor constraintEqualToAnchor:safeAreaGuide.bottomAnchor], + self.contentBottomConstraint + ]]; +} + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + + [coordinator + animateAlongsideTransition:^(id context) { + [self updateValidationContentViewForSize:size]; + } + completion:^(id _Nonnull context){ + + }]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + // pre-layout view to avoid undesired animation if the keyboard is shown while appearing + [self.view layoutIfNeeded]; + [self ka_startObservingKeyboardNotifications]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [self ka_stopObservingKeyboardNotifications]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [self.textField becomeFirstResponder]; +} + +#pragma mark - UITextFieldDelegate + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { + BOOL isDone = NO; + if ([string isEqualToString:@"\n"]) { + isDone = YES; + string = @""; + } + NSString *text = [self.textField.text stringByReplacingCharactersInRange:range withString:string]; + for (DWUsernameValidationView *validationView in self.validationViews) { + DWUsernameValidationRule *validator = validationView.rule; + [validator validateText:text]; + } + self.registerButton.enabled = NO; + textField.text = text; + + return NO; +} + +#pragma mark - DWCheckExistenceUsernameValidationRuleDelegate + +- (void)checkExistenceUsernameValidationRuleDidValidate:(DWCheckExistenceUsernameValidationRule *)rule { + BOOL canRegister = YES; + for (DWUsernameValidationView *validationView in self.validationViews) { + DWUsernameValidationRule *validator = validationView.rule; + DWUsernameValidationRuleResult result = validator.validationResult; + canRegister &= result != DWUsernameValidationRuleResultInvalid && result != DWUsernameValidationRuleResultInvalidCritical && result != DWUsernameValidationRuleResultEmpty; + } + self.registerButton.enabled = canRegister; +} + +#pragma mark - Keyboard + +- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height + animationDuration:(NSTimeInterval)animationDuration + animationCurve:(UIViewAnimationCurve)animationCurve { + const CGFloat bottomPadding = [self.class deviceSpecificBottomPadding]; + self.contentBottomConstraint.constant = height + bottomPadding; + [self.view layoutIfNeeded]; +} + +#pragma mark - Private + + +- (void)updateValidationContentViewForSize:(CGSize)size { + BOOL isLandscape = size.width > size.height; + if (isLandscape) { + self.validationContentView.axis = UILayoutConstraintAxisHorizontal; + } + else { + self.validationContentView.axis = UILayoutConstraintAxisVertical; + } +} + +- (UIScrollView *)stackView { + if (_stackView == nil) { + _stackView = [[UIStackView alloc] initWithFrame:CGRectZero]; + _stackView.translatesAutoresizingMaskIntoConstraints = NO; + _stackView.axis = UILayoutConstraintAxisVertical; + _stackView.spacing = SPACING; + } + + return _stackView; +} + +- (UITextField *)textField { + if (_textField == nil) { + _textField = [[DWTextField alloc] initWithFrame:CGRectZero]; + _textField.translatesAutoresizingMaskIntoConstraints = NO; + _textField.delegate = self; + _textField.backgroundColor = [UIColor dw_backgroundColor]; + _textField.textColor = [UIColor dw_darkTitleColor]; + _textField.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + _textField.adjustsFontForContentSizeCategory = YES; + _textField.placeholder = NSLocalizedString(@"eg: johndoe", @"Input username textfield placeholder"); + _textField.layer.cornerRadius = CORNER_RADIUS; + _textField.layer.masksToBounds = YES; + _textField.autocorrectionType = UITextAutocorrectionTypeNo; + _textField.autocapitalizationType = UITextAutocapitalizationTypeNone; + _textField.spellCheckingType = UITextSpellCheckingTypeNo; + _textField.smartDashesType = UITextSmartDashesTypeNo; + _textField.smartQuotesType = UITextSmartQuotesTypeNo; + _textField.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; + _textField.returnKeyType = UIReturnKeyDone; + } + + return _textField; +} + +- (UIStackView *)validationContentView { + if (_validationContentView == nil) { + _validationContentView = [[UIStackView alloc] initWithFrame:CGRectZero]; + _validationContentView.translatesAutoresizingMaskIntoConstraints = NO; + _validationContentView.axis = UILayoutConstraintAxisVertical; + _validationContentView.spacing = 6.0; + _validationContentView.distribution = UIStackViewDistributionFillEqually; + } + + return _validationContentView; +} + +- (UIButton *)registerButton { + if (_registerButton == nil) { + _registerButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + _registerButton.translatesAutoresizingMaskIntoConstraints = NO; + [_registerButton setTitle:NSLocalizedString(@"Register", @"Button title, Register (username)") + forState:UIControlStateNormal]; + [_registerButton addTarget:self + action:@selector(registerButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + _registerButton.enabled = NO; + } + + return _registerButton; +} + +- (DWCheckExistenceUsernameValidationRule *)checkExistenceValidator { + if (_checkExistenceValidator == nil) { + _checkExistenceValidator = [[DWCheckExistenceUsernameValidationRule alloc] initWithDelegate:self]; + } + return _checkExistenceValidator; +} + +#pragma mark - Actions + +- (void)registerButtonAction:(id)sender { + [self.delegate inputUsernameViewControllerRegisterAction:self]; +} + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.h b/DashPay/Presentation/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.h new file mode 100644 index 000000000..0970bb65c --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.h @@ -0,0 +1,26 @@ +// +// 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 "DWUsernameValidationRule.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWAllowedCharactersUsernameValidationRule : DWUsernameValidationRule + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.m b/DashPay/Presentation/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.m new file mode 100644 index 000000000..d6269b439 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWAllowedCharactersUsernameValidationRule.m @@ -0,0 +1,57 @@ +// +// 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 "DWAllowedCharactersUsernameValidationRule.h" + +#import "DWUsernameValidationRule+Protected.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWAllowedCharactersUsernameValidationRule () + +@property (readonly, strong, nonatomic) NSCharacterSet *illegalChars; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWAllowedCharactersUsernameValidationRule + +- (instancetype)init { + self = [super init]; + if (self) { + NSCharacterSet *allowedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"]; + _illegalChars = [allowedCharacterSet invertedSet]; + } + return self; +} + +- (NSString *)title { + return NSLocalizedString(@"Letters, numbers and hyphens only", @"Validation rule"); +} + +- (void)validateText:(NSString *)text { + if (text.length == 0) { + self.validationResult = DWUsernameValidationRuleResultEmpty; + return; + } + + BOOL hasIllegalCharacter = [text rangeOfCharacterFromSet:self.illegalChars].location != NSNotFound; + self.validationResult = hasIllegalCharacter ? DWUsernameValidationRuleResultInvalid : DWUsernameValidationRuleResultValid; +} + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.h b/DashPay/Presentation/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.h new file mode 100644 index 000000000..af579bdca --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.h @@ -0,0 +1,39 @@ +// +// 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 "DWUsernameValidationRule.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWCheckExistenceUsernameValidationRule; + +@protocol DWCheckExistenceUsernameValidationRuleDelegate + +- (void)checkExistenceUsernameValidationRuleDidValidate:(DWCheckExistenceUsernameValidationRule *)rule; + +@end + +@interface DWCheckExistenceUsernameValidationRule : DWUsernameValidationRule + +- (instancetype)initWithDelegate:(id)delegate; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.m b/DashPay/Presentation/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.m new file mode 100644 index 000000000..b380797e4 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWCheckExistenceUsernameValidationRule.m @@ -0,0 +1,140 @@ +// +// 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 "DWCheckExistenceUsernameValidationRule.h" + +#import "DWAllowedCharactersUsernameValidationRule.h" +#import "DWDashPayConstants.h" +#import "DWEnvironment.h" +#import "DWFirstUsernameSymbolValidationRule.h" +#import "DWLengthUsernameValidationRule.h" +#import "DWUsernameValidationRule+Protected.h" + +NS_ASSUME_NONNULL_BEGIN + +static NSTimeInterval VALIDATION_DEBOUNCE_DELAY = 0.4; + +@interface DWCheckExistenceUsernameValidationRule () + +@property (nonatomic, copy) NSDictionary *titleByResult; +@property (nullable, nonatomic, weak) id delegate; + +@property (nullable, nonatomic, copy) NSString *username; +@property (nullable, nonatomic, strong) id request; +@property (readonly, nonatomic, copy) NSArray *validators; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWCheckExistenceUsernameValidationRule + +- (instancetype)initWithDelegate:(id)delegate { + self = [super init]; + if (self) { + _delegate = delegate; + + _validators = @[ + [[DWLengthUsernameValidationRule alloc] init], + [[DWAllowedCharactersUsernameValidationRule alloc] init], + [[DWFirstUsernameSymbolValidationRule alloc] init], + ]; + + NSMutableDictionary *titleByResult = [NSMutableDictionary dictionary]; + titleByResult[@(DWUsernameValidationRuleResultLoading)] = NSLocalizedString(@"Validating username…", nil); + titleByResult[@(DWUsernameValidationRuleResultValid)] = NSLocalizedString(@"Username available", nil); + titleByResult[@(DWUsernameValidationRuleResultError)] = NSLocalizedString(@"Validating username failed", nil); + titleByResult[@(DWUsernameValidationRuleResultInvalidCritical)] = NSLocalizedString(@"Username taken", nil); + self.titleByResult = titleByResult; + } + return self; +} + +- (void)setValidationResult:(DWUsernameValidationRuleResult)validationResult { + [super setValidationResult:validationResult]; + + [self.delegate checkExistenceUsernameValidationRuleDidValidate:self]; +} + +- (NSString *)title { + return self.titleByResult[@(self.validationResult)]; +} + +- (void)validateText:(NSString *)text { + [self.request cancel]; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(performValidation) object:nil]; + self.username = text; + + [self.validators makeObjectsPerformSelector:@selector(validateText:) withObject:text]; + + BOOL hasInvalid = NO; + for (DWUsernameValidationRule *rule in self.validators) { + if (rule.validationResult == DWUsernameValidationRuleResultInvalid) { + hasInvalid = YES; + break; + } + } + + if (hasInvalid || text.length == 0) { + self.validationResult = DWUsernameValidationRuleResultHidden; + return; + } + + self.validationResult = DWUsernameValidationRuleResultLoading; + + [self performSelector:@selector(performValidation) withObject:nil afterDelay:VALIDATION_DEBOUNCE_DELAY]; +} + +#pragma mark - Private + +- (void)performValidation { + [self performValidationWithUsername:self.username]; +} + +- (void)performValidationWithUsername:(NSString *)username { + DSIdentitiesManager *manager = [DWEnvironment sharedInstance].currentChainManager.identitiesManager; + __weak typeof(self) weakSelf = self; + self.request = [manager + searchIdentityByDashpayUsername:username + withCompletion:^(BOOL succeess, DSBlockchainIdentity *_Nullable blockchainIdentity, NSError *_Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + // search query was changed before results arrive, ignore results + if (![strongSelf.username isEqualToString:username]) { + return; + } + + if (succeess) { + if (blockchainIdentity != nil) { + strongSelf.validationResult = DWUsernameValidationRuleResultInvalidCritical; + } + else { + strongSelf.validationResult = DWUsernameValidationRuleResultValid; + } + } + else { + strongSelf.validationResult = DWUsernameValidationRuleResultError; + } + }]; +} + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h b/DashPay/Presentation/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h new file mode 100644 index 000000000..ec496b710 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h @@ -0,0 +1,26 @@ +// +// 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 "DWUsernameValidationRule.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWFirstUsernameSymbolValidationRule : DWUsernameValidationRule + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m b/DashPay/Presentation/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m new file mode 100644 index 000000000..9406fcb87 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m @@ -0,0 +1,44 @@ +// +// 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 "DWFirstUsernameSymbolValidationRule.h" + +#import "DWUsernameValidationRule+Protected.h" + +@implementation DWFirstUsernameSymbolValidationRule + +- (NSString *)title { + return NSLocalizedString(@"Must start and end with a letter or number", @"Validation rule"); +} + +- (void)validateText:(NSString *)text { + if (text.length == 0 || [text rangeOfString:@"-"].location == NSNotFound) { + self.validationResult = DWUsernameValidationRuleResultHidden; + return; + } + + // The user should be able use a hyphen anywhere in the username except the first or last characters + if ([text hasPrefix:@"-"] || [text hasSuffix:@"-"]) { + self.validationResult = DWUsernameValidationRuleResultInvalid; + return; + } + + self.validationResult = DWUsernameValidationRuleResultValid; +} + + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h b/DashPay/Presentation/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h new file mode 100644 index 000000000..b2b3f77da --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h @@ -0,0 +1,26 @@ +// +// 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 "DWUsernameValidationRule.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWLengthUsernameValidationRule : DWUsernameValidationRule + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m b/DashPay/Presentation/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m new file mode 100644 index 000000000..b631b6bba --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m @@ -0,0 +1,41 @@ +// +// 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 "DWLengthUsernameValidationRule.h" + +#import "DWDashPayConstants.h" +#import "DWUsernameValidationRule+Protected.h" + +@implementation DWLengthUsernameValidationRule + +- (NSString *)title { + return [NSString stringWithFormat:NSLocalizedString(@"Between %ld and %ld characters", @"Validation rule: Between 3 and 24 characters"), DW_MIN_USERNAME_LENGTH, DW_MAX_USERNAME_LENGTH]; +} + +- (void)validateText:(NSString *)text { + const NSUInteger length = text.length; + if (length == 0) { + self.validationResult = DWUsernameValidationRuleResultEmpty; + return; + } + + BOOL isValid = length >= DW_MIN_USERNAME_LENGTH && length <= DW_MAX_USERNAME_LENGTH; + + self.validationResult = isValid ? DWUsernameValidationRuleResultValid : DWUsernameValidationRuleResultInvalid; +} + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule+Protected.h b/DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule+Protected.h new file mode 100644 index 000000000..6f3d35139 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule+Protected.h @@ -0,0 +1,28 @@ +// +// 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 "DWUsernameValidationRule.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUsernameValidationRule () + +@property (nonatomic, assign) DWUsernameValidationRuleResult validationResult; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule.h b/DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule.h new file mode 100644 index 000000000..cb2a1a94d --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule.h @@ -0,0 +1,48 @@ +// +// 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 + +typedef NS_ENUM(NSUInteger, DWUsernameValidationRuleResult) { + /// No icon, black text + DWUsernameValidationRuleResultEmpty, + /// activity indicator, black text + DWUsernameValidationRuleResultLoading, + /// Checkmark, black text + DWUsernameValidationRuleResultValid, + /// Red cross and black text + DWUsernameValidationRuleResultInvalid, + /// Red cross and red text + DWUsernameValidationRuleResultInvalidCritical, + /// Red cross and red text + DWUsernameValidationRuleResultError, + /// View is hidden + DWUsernameValidationRuleResultHidden, +}; + +@interface DWUsernameValidationRule : NSObject + +@property (readonly, nonatomic, copy) NSString *title; +@property (readonly, nonatomic, assign) DWUsernameValidationRuleResult validationResult; + +- (void)validateText:(NSString *_Nullable)text; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule.m b/DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule.m new file mode 100644 index 000000000..f7871f29f --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Models/DWUsernameValidationRule.m @@ -0,0 +1,34 @@ +// +// 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 "DWUsernameValidationRule+Protected.h" + +@implementation DWUsernameValidationRule + +- (instancetype)init { + self = [super init]; + if (self) { + [self validateText:nil]; + } + return self; +} + +- (void)validateText:(NSString *_Nullable)text { + NSAssert(NO, @"To be overriden"); +} + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/Views/DWPlanetarySystemView.h b/DashPay/Presentation/Setup/CreateUsername/Views/DWPlanetarySystemView.h new file mode 100644 index 000000000..9155cc0a3 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Views/DWPlanetarySystemView.h @@ -0,0 +1,62 @@ +// +// 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 + +#pragma mark - Model + +@interface DWPlanetObject : NSObject + +/// The planet image. +@property (nullable, nonatomic, strong) UIImage *image; +/// Custom view to display as a planet +@property (nullable, nonatomic, strong) UIView *customView; + +/// The speed of animation. +@property (nonatomic, assign) CGFloat speed; +/// The duration of the complete rotation along the orbit. +@property (nonatomic, assign) CGFloat duration; +/// The animation time offset in percentage. +@property (nonatomic, assign) CGFloat offset; +/// Size of the corresponding UIImageView. +@property (nonatomic, assign) CGSize size; +/// The number of the orbit. Must be less than `numberOfOrbits` of the view. +@property (nonatomic, assign) NSInteger orbit; +/// The rotation direction. +@property (nonatomic, assign) BOOL rotateClockwise; + +@end + +#pragma mark - View + +@interface DWPlanetarySystemView : UIView + +@property (nonatomic, assign) NSInteger numberOfOrbits; +@property (nonatomic, assign) CGFloat centerOffset; +@property (nonatomic, assign) CGFloat borderOffset; +@property (nonatomic, assign) CGFloat lineWidth; +@property (nonatomic, copy) NSArray *colors; + +@property (nullable, nonatomic, copy) NSArray *planets; + +- (void)showInitialAnimation; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Views/DWPlanetarySystemView.m b/DashPay/Presentation/Setup/CreateUsername/Views/DWPlanetarySystemView.m new file mode 100644 index 000000000..28903cfb4 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Views/DWPlanetarySystemView.m @@ -0,0 +1,241 @@ +// +// 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 "DWPlanetarySystemView.h" + +#pragma mark - Model + +@implementation DWPlanetObject +@end + +#pragma mark - View + +static const CFTimeInterval ORBIT_ANIMATION_DURATION = 0.085; +static const CFTimeInterval ORBIT_ANIMATION_DELAY_BETWEEN = 0.25; + +NS_ASSUME_NONNULL_BEGIN + +@interface DWPlanetarySystemView () + +@property (strong, nonatomic) NSMutableArray *orbits; +@property (strong, nonatomic) NSMutableArray *orbitLayers; +@property (strong, nonatomic) NSMutableArray *planetViews; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWPlanetarySystemView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setup]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self setup]; + } + return self; +} + +- (void)setNumberOfOrbits:(NSInteger)numberOfOrbits { + _numberOfOrbits = numberOfOrbits; + + [self rebornTheUniverse]; +} + +- (void)setCenterOffset:(CGFloat)centerOffset { + _centerOffset = centerOffset; + + [self rebornTheUniverse]; +} + +- (void)setBorderOffset:(CGFloat)borderOffset { + _borderOffset = borderOffset; + + [self rebornTheUniverse]; +} + +- (void)setLineWidth:(CGFloat)lineWidth { + _lineWidth = lineWidth; + + [self rebornTheUniverse]; +} + +- (void)setColors:(NSArray *)colors { + _colors = [colors copy]; + + [self rebornTheUniverse]; +} + +- (void)setPlanets:(NSArray *)planets { + _planets = [planets copy]; + + [self rebornTheUniverse]; +} + +- (void)showInitialAnimation { + [self showOrbitsAnimated]; + + const NSUInteger orbitsCount = self.orbits.count; + // The actual delay is: + // `orbitsCount * ORBIT_ANIMATION_DURATION + (orbitsCount - 1) * ORBIT_ANIMATION_DELAY_BETWEEN` + // But we starting to show planets a bit before orbit animation is finished. + const CFTimeInterval delay = orbitsCount * ORBIT_ANIMATION_DURATION; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self animatePlanetsWithRepeatCount:0.0]; + }); +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + [self rebornTheUniverse]; +} + +#pragma mark Private + +- (void)setup { + _colors = @[]; + _orbits = [NSMutableArray array]; + _orbitLayers = [NSMutableArray array]; + _planetViews = [NSMutableArray array]; +} + +- (void)showOrbitsAnimated { + const CFTimeInterval duration = ORBIT_ANIMATION_DURATION; + const CFTimeInterval delayBetween = ORBIT_ANIMATION_DELAY_BETWEEN; + CFTimeInterval timeOffset = 0.0; + for (CAShapeLayer *shapeLayer in self.orbitLayers) { + NSString *keyPath = DW_KEYPATH(shapeLayer, opacity); + CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath]; + animation.toValue = @1.0; + animation.timeOffset = timeOffset; + animation.duration = duration; + [shapeLayer addAnimation:animation forKey:@"dw_orbit_animation"]; + shapeLayer.opacity = 1.0; + timeOffset += duration + delayBetween; + } +} + +- (void)animatePlanetsWithRepeatCount:(float)repeatCount { + if (self.planets.count != self.planetViews.count) { + [self rebornTheUniverse]; + } + + for (NSInteger i = 0; i < self.planets.count; i++) { + DWPlanetObject *planet = self.planets[i]; + UIView *planetView = self.planetViews[i]; + + NSAssert(planet.orbit < self.orbits.count, @"Internal inconsistency"); + UIBezierPath *path = self.orbits[planet.orbit]; + + // start off fading in when 1/3 of the rotation is completed + const CFTimeInterval opacityBeginTime = planet.duration / planet.speed / 3.0; + + NSString *keyPath = DW_KEYPATH(planetView.layer, opacity); + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:keyPath]; + opacityAnimation.toValue = @1.0; + opacityAnimation.duration = planet.duration; + opacityAnimation.beginTime = opacityBeginTime; + opacityAnimation.removedOnCompletion = NO; + opacityAnimation.fillMode = kCAFillModeForwards; + + keyPath = DW_KEYPATH(planetView.layer, position); + CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:keyPath]; + positionAnimation.path = path.CGPath; + positionAnimation.repeatCount = repeatCount; + positionAnimation.calculationMode = kCAAnimationPaced; + positionAnimation.removedOnCompletion = NO; + positionAnimation.fillMode = kCAFillModeForwards; + positionAnimation.speed = (planet.rotateClockwise ? -1.0 : 1.0); + positionAnimation.timeOffset = planet.duration * planet.offset; + positionAnimation.duration = planet.duration; + + CAAnimationGroup *animation = [CAAnimationGroup animation]; + animation.animations = @[ opacityAnimation, positionAnimation ]; + animation.speed = planet.speed; + animation.duration = opacityBeginTime + planet.duration; + animation.removedOnCompletion = NO; + animation.fillMode = kCAFillModeForwards; + [planetView.layer addAnimation:animation forKey:@"dw_planet_animation"]; + } +} + +/// big bang! +- (void)rebornTheUniverse { + [self.orbits removeAllObjects]; + + [self.orbitLayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; + [self.orbitLayers removeAllObjects]; + + [self.planetViews makeObjectsPerformSelector:@selector(removeFromSuperview)]; + [self.planetViews removeAllObjects]; + + if (self.numberOfOrbits == 0 || self.numberOfOrbits != self.colors.count || self.planets.count == 0) { + return; + } + + const CGSize size = self.bounds.size; + const CGPoint center = CGPointMake(size.width / 2.0, size.height / 2.0); + const CGFloat radius = MIN(size.width, size.height) / self.numberOfOrbits / 2.0 - self.borderOffset; + + for (NSInteger i = 0; i < self.numberOfOrbits; i++) { + UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center + radius:radius * i + self.centerOffset + startAngle:0.0 + endAngle:M_PI * 2.0 + clockwise:NO]; + CAShapeLayer *shapeLayer = [CAShapeLayer layer]; + shapeLayer.path = path.CGPath; + shapeLayer.frame = self.bounds; + shapeLayer.strokeColor = self.colors[i].CGColor; + shapeLayer.lineWidth = self.lineWidth; + shapeLayer.fillColor = [UIColor clearColor].CGColor; + shapeLayer.opacity = 0.0; // initially hidden + [self.layer addSublayer:shapeLayer]; + + [self.orbits addObject:path]; + [self.orbitLayers addObject:shapeLayer]; + } + + for (DWPlanetObject *planet in self.planets) { + const CGRect rect = CGRectMake(-planet.size.width * 0.5, -planet.size.height * 0.5, + planet.size.width, planet.size.height); + + UIView *planetView = nil; + if (planet.image) { + UIImageView *imageView = [[UIImageView alloc] initWithFrame:rect]; + imageView.image = planet.image; + planetView = imageView; + } + else { + planetView = planet.customView; + } + planetView.layer.opacity = 0.0; // initially hidden + [self addSubview:planetView]; + + [self.planetViews addObject:planetView]; + } +} + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/Views/DWTextField.h b/DashPay/Presentation/Setup/CreateUsername/Views/DWTextField.h new file mode 100644 index 000000000..88134fb91 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Views/DWTextField.h @@ -0,0 +1,31 @@ +// +// 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 DWTextField : UITextField + +/// Defaults to 30 +@property (nonatomic, assign) CGFloat horizontalPadding; +/// Defaults to 16 +@property (nonatomic, assign) CGFloat verticalPadding; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Views/DWTextField.m b/DashPay/Presentation/Setup/CreateUsername/Views/DWTextField.m new file mode 100644 index 000000000..ec80a3eac --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Views/DWTextField.m @@ -0,0 +1,43 @@ +// +// 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 "DWTextField.h" + +@implementation DWTextField + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + _horizontalPadding = 30; + _verticalPadding = 16; + } + return self; +} + +- (CGRect)textRectForBounds:(CGRect)bounds { + return CGRectInset(bounds, self.horizontalPadding, self.verticalPadding); +} + +- (CGRect)editingRectForBounds:(CGRect)bounds { + return CGRectInset(bounds, self.horizontalPadding, self.verticalPadding); +} + +- (CGRect)placeholderRectForBounds:(CGRect)bounds { + return CGRectInset(bounds, self.horizontalPadding, self.verticalPadding); +} + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameHeaderView.h b/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameHeaderView.h new file mode 100644 index 000000000..29bc75ae5 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameHeaderView.h @@ -0,0 +1,40 @@ +// +// 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 + +typedef NSAttributedString *_Nonnull (^DWTitleStringBuilder)(void); + +@interface DWUsernameHeaderView : UIView + +@property (readonly, nonatomic, strong) UIButton *cancelButton; +@property (nullable, nonatomic, copy) DWTitleStringBuilder titleBuilder; +@property (nonatomic, assign) BOOL landscapeMode; + +- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (void)configurePlanetsViewWithUsername:(NSString *)username; + +- (void)showInitialAnimation; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameHeaderView.m b/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameHeaderView.m new file mode 100644 index 000000000..890c168b1 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameHeaderView.m @@ -0,0 +1,338 @@ +// +// 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 "DWUsernameHeaderView.h" + +#import "DWDPAvatarView.h" +#import "DWPlanetarySystemView.h" +#import "DWUIKit.h" + +static CGFloat const BottomSpacing(void) { + if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { + return 4.0; + } + else { + return 16.0; + } +} + +static CGFloat SmallCircleRadius(void) { + if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { + return 106.0; + } + else { + return 78.0; + } +} + +static CGFloat PlanetarySize(void) { + if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { + return 260.0; + } + else { + const CGSize screenSize = [UIScreen mainScreen].bounds.size; + const CGFloat side = MIN(screenSize.width, screenSize.height); + return MIN(375.0, side); + } +} + +static NSArray *OrbitColors(void) { + // Luckily, DashBlueColor doesn't have DarkMode counterpart + // and we don't need to reset colors on traitCollectionDidChange: + UIColor *color = [UIColor dw_dashBlueColor]; + + if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { + return @[ + [color colorWithAlphaComponent:0.3], + [color colorWithAlphaComponent:0.1], + [color colorWithAlphaComponent:0.07], + ]; + } + else { + return @[ + [color colorWithAlphaComponent:0.5], + [color colorWithAlphaComponent:0.3], + [color colorWithAlphaComponent:0.1], + [color colorWithAlphaComponent:0.07], + ]; + } +} + +static NSArray *Planets(NSString *_Nullable username) { + CGSize size; + CGSize avatarSize; + if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { + size = CGSizeMake(28.0, 28.0); + avatarSize = CGSizeMake(46.0, 46.0); + } + else { + size = CGSizeMake(36.0, 36.0); + avatarSize = CGSizeMake(60.0, 60.0); + } + + NSMutableArray *planets = [NSMutableArray array]; + + if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { + { + DWPlanetObject *planet = [[DWPlanetObject alloc] init]; + planet.image = [UIImage imageNamed:@"dp_user_2"]; + planet.speed = 1.55; + planet.duration = 0.75; + planet.offset = 255.0 / 360.0; + planet.size = size; + planet.orbit = 0; + planet.rotateClockwise = YES; + [planets addObject:planet]; + } + + { + DWPlanetObject *planet = [[DWPlanetObject alloc] init]; + planet.image = [UIImage imageNamed:@"dp_user_3"]; + planet.speed = 1.3; + planet.duration = 0.75; + planet.offset = 230.0 / 360.0; + planet.size = size; + planet.orbit = 1; + planet.rotateClockwise = YES; + [planets addObject:planet]; + } + + { + DWPlanetObject *planet = [[DWPlanetObject alloc] init]; + if (username.length > 0) { + DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:(CGRect){{0.0, 0.0}, avatarSize}]; + [avatarView configureWithUsername:username]; + planet.customView = avatarView; + } + else { + planet.image = [UIImage imageNamed:@"dp_user_generic"]; + } + planet.speed = 1.0; + planet.duration = 0.75; + planet.offset = 250.0 / 360.0; + planet.size = size; + planet.orbit = 2; + planet.rotateClockwise = YES; + [planets addObject:planet]; + } + } + else { + { + DWPlanetObject *planet = [[DWPlanetObject alloc] init]; + planet.image = [UIImage imageNamed:@"dp_user_1"]; + planet.speed = 2.1; + planet.duration = 0.75; + planet.offset = 245.0 / 360.0; + planet.size = size; + planet.orbit = 0; + planet.rotateClockwise = YES; + [planets addObject:planet]; + } + + { + DWPlanetObject *planet = [[DWPlanetObject alloc] init]; + planet.image = [UIImage imageNamed:@"dp_user_2"]; + planet.speed = 1.8; + planet.duration = 0.75; + planet.offset = 255.0 / 360.0; + planet.size = size; + planet.orbit = 1; + planet.rotateClockwise = YES; + [planets addObject:planet]; + } + + { + DWPlanetObject *planet = [[DWPlanetObject alloc] init]; + planet.image = [UIImage imageNamed:@"dp_user_3"]; + planet.speed = 1.55; + planet.duration = 0.75; + planet.offset = 230.0 / 360.0; + planet.size = size; + planet.orbit = 2; + planet.rotateClockwise = YES; + [planets addObject:planet]; + } + + { + DWPlanetObject *planet = [[DWPlanetObject alloc] init]; + planet.image = [UIImage imageNamed:@"dp_user_2"]; // TODO: fix image + planet.speed = 1.3; + planet.duration = 0.75; + planet.offset = 200.0 / 360.0; + planet.size = size; + planet.orbit = 3; + planet.rotateClockwise = YES; + [planets addObject:planet]; + } + + { + DWPlanetObject *planet = [[DWPlanetObject alloc] init]; + if (username.length > 0) { + DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:(CGRect){{0.0, 0.0}, avatarSize}]; + [avatarView configureWithUsername:username]; + planet.customView = avatarView; + } + else { + planet.image = [UIImage imageNamed:@"dp_user_generic"]; + } + planet.speed = 1.0; + planet.duration = 0.75; + planet.offset = 250.0 / 360.0; + planet.size = size; + planet.orbit = 3; + planet.rotateClockwise = YES; + [planets addObject:planet]; + } + } + + return [planets copy]; +} + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUsernameHeaderView () + +@property (strong, nonatomic) DWPlanetarySystemView *planetaryView; +@property (nonatomic, strong) UILabel *titleLabel; + +@property (nonatomic, copy) NSArray *portraitConstraints; +@property (nonatomic, copy) NSArray *landscapeConstraints; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUsernameHeaderView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeCustom]; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + [cancelButton setImage:[UIImage imageNamed:@"payments_nav_cross"] forState:UIControlStateNormal]; + [self addSubview:cancelButton]; + _cancelButton = cancelButton; + + NSArray *colors = OrbitColors(); + + DWPlanetarySystemView *planetaryView = [[DWPlanetarySystemView alloc] initWithFrame:CGRectZero]; + planetaryView.translatesAutoresizingMaskIntoConstraints = NO; + planetaryView.centerOffset = SmallCircleRadius(); + planetaryView.colors = colors; + planetaryView.lineWidth = 1.0; + planetaryView.numberOfOrbits = colors.count; + planetaryView.planets = Planets(nil); + [self addSubview:planetaryView]; + _planetaryView = planetaryView; + + UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.backgroundColor = [UIColor clearColor]; + titleLabel.numberOfLines = 3; + [self addSubview:titleLabel]; + _titleLabel = titleLabel; + + const CGFloat buttonSize = 44.0; + const CGFloat side = PlanetarySize(); + CGPoint planetOffest = CGPointZero; + if (IS_IPHONE_5_OR_LESS || IS_IPHONE_6) { + planetOffest = CGPointMake(16.0, -66.0); + } + + _landscapeConstraints = @[ + [titleLabel.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor], + [titleLabel.leadingAnchor constraintEqualToAnchor:cancelButton.trailingAnchor + constant:16.0], + ]; + + _portraitConstraints = @[ + [titleLabel.topAnchor constraintGreaterThanOrEqualToAnchor:cancelButton.bottomAnchor], + [titleLabel.leadingAnchor constraintEqualToAnchor:self.layoutMarginsGuide.leadingAnchor], + ]; + + [NSLayoutConstraint activateConstraints:_portraitConstraints]; + + [NSLayoutConstraint activateConstraints:@[ + [cancelButton.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor], + [cancelButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [cancelButton.widthAnchor constraintEqualToConstant:buttonSize], + [cancelButton.heightAnchor constraintEqualToConstant:buttonSize], + + [planetaryView.centerXAnchor constraintEqualToAnchor:self.trailingAnchor + constant:planetOffest.x], + [planetaryView.centerYAnchor constraintEqualToAnchor:self.topAnchor + constant:planetOffest.y], + [planetaryView.widthAnchor constraintEqualToConstant:side], + [planetaryView.heightAnchor constraintEqualToConstant:side], + + + [self.layoutMarginsGuide.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:BottomSpacing()], + ]]; + } + return self; +} + +- (void)setTitleBuilder:(DWTitleStringBuilder)titleBuilder { + _titleBuilder = [titleBuilder copy]; + + [self updateTitle]; +} + +- (void)configurePlanetsViewWithUsername:(NSString *)username { + self.planetaryView.planets = Planets(username); +} + +- (void)showInitialAnimation { + [self.planetaryView showInitialAnimation]; +} + +- (void)setLandscapeMode:(BOOL)landscapeMode { + _landscapeMode = landscapeMode; + + self.planetaryView.alpha = landscapeMode ? 0.0 : 1.0; + if (landscapeMode) { + [NSLayoutConstraint deactivateConstraints:self.portraitConstraints]; + [NSLayoutConstraint activateConstraints:self.landscapeConstraints]; + } + else { + [NSLayoutConstraint deactivateConstraints:self.landscapeConstraints]; + [NSLayoutConstraint activateConstraints:self.portraitConstraints]; + } +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + [self updateTitle]; +} + +#pragma mark - Private + +- (void)updateTitle { + if (self.titleBuilder) { + self.titleLabel.attributedText = self.titleBuilder(); + } + else { + self.titleLabel.attributedText = nil; + } +} + +@end diff --git a/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameValidationView.h b/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameValidationView.h new file mode 100644 index 000000000..4c00e97c9 --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameValidationView.h @@ -0,0 +1,34 @@ +// +// 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 + +#import "DWUsernameValidationRule.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUsernameValidationView : KVOUIView + +@property (nullable, nonatomic, strong) DWUsernameValidationRule *rule; + +- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameValidationView.m b/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameValidationView.m new file mode 100644 index 000000000..2059f8e5a --- /dev/null +++ b/DashPay/Presentation/Setup/CreateUsername/Views/DWUsernameValidationView.m @@ -0,0 +1,139 @@ +// +// 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 "DWUsernameValidationView.h" + +#import "DWUIKit.h" + +static CGFloat const ICON_SIZE = 16.0; + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUsernameValidationView () + +@property (readonly, nonatomic, strong) UIImageView *iconImageView; +@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUsernameValidationView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UIImageView *iconImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + iconImageView.translatesAutoresizingMaskIntoConstraints = NO; + iconImageView.contentMode = UIViewContentModeCenter; + [self addSubview:iconImageView]; + _iconImageView = iconImageView; + + UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; + activityIndicatorView.color = [UIColor dw_darkTitleColor]; + [self addSubview:activityIndicatorView]; + _activityIndicatorView = activityIndicatorView; + + UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.backgroundColor = self.backgroundColor; + titleLabel.numberOfLines = 0; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCaption1]; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + [self addSubview:titleLabel]; + _titleLabel = titleLabel; + + [NSLayoutConstraint activateConstraints:@[ + [iconImageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [iconImageView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + [iconImageView.widthAnchor constraintEqualToConstant:ICON_SIZE], + [iconImageView.heightAnchor constraintEqualToConstant:ICON_SIZE], + + [activityIndicatorView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [activityIndicatorView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:self.topAnchor], + [titleLabel.leadingAnchor constraintEqualToAnchor:iconImageView.trailingAnchor + constant:5.0], + [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor], + ]]; + + [self mvvm_observe:DW_KEYPATH(self, rule.validationResult) + with:^(typeof(self) self, NSNumber *value) { + [self setValidationResult:self.rule.validationResult]; + }]; + } + return self; +} + +- (void)setValidationResult:(DWUsernameValidationRuleResult)validationResult { + self.titleLabel.text = self.rule.title; + + switch (validationResult) { + case DWUsernameValidationRuleResultEmpty: + self.hidden = NO; + self.iconImageView.image = nil; + self.iconImageView.tintColor = nil; + self.titleLabel.textColor = [UIColor dw_darkTitleColor]; + [self.activityIndicatorView stopAnimating]; + break; + case DWUsernameValidationRuleResultLoading: + self.hidden = NO; + self.iconImageView.image = nil; + self.iconImageView.tintColor = nil; + self.titleLabel.textColor = [UIColor dw_darkTitleColor]; + [self.activityIndicatorView startAnimating]; + break; + case DWUsernameValidationRuleResultValid: + self.hidden = NO; + self.iconImageView.image = [UIImage imageNamed:@"validation_checkmark"]; + self.iconImageView.tintColor = [UIColor dw_greenColor]; + self.titleLabel.textColor = [UIColor dw_darkTitleColor]; + [self.activityIndicatorView stopAnimating]; + break; + case DWUsernameValidationRuleResultInvalid: + self.hidden = NO; + self.iconImageView.image = [UIImage imageNamed:@"validation_cross"]; + self.iconImageView.tintColor = nil; + self.titleLabel.textColor = [UIColor dw_darkTitleColor]; + [self.activityIndicatorView stopAnimating]; + break; + case DWUsernameValidationRuleResultInvalidCritical: + case DWUsernameValidationRuleResultError: + self.hidden = NO; + self.iconImageView.image = [UIImage imageNamed:@"validation_cross"]; + self.iconImageView.tintColor = nil; + self.titleLabel.textColor = [UIColor dw_redColor]; + [self.activityIndicatorView stopAnimating]; + break; + case DWUsernameValidationRuleResultHidden: + self.hidden = YES; + self.iconImageView.image = nil; + self.iconImageView.tintColor = nil; + self.titleLabel.textColor = [UIColor dw_darkTitleColor]; + [self.activityIndicatorView stopAnimating]; + break; + } +} + +@end diff --git a/DashPay/Presentation/Setup/DWDashPaySetupFlowController.h b/DashPay/Presentation/Setup/DWDashPaySetupFlowController.h new file mode 100644 index 000000000..181377b05 --- /dev/null +++ b/DashPay/Presentation/Setup/DWDashPaySetupFlowController.h @@ -0,0 +1,49 @@ +// +// 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 + +#import "DWDashPayProtocol.h" +#import "dashwallet-Swift.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWDashPaySetupFlowController; + +@protocol DWDashPaySetupFlowControllerDelegate + +- (void)dashPaySetupFlowController:(DWDashPaySetupFlowController *)controller + didConfirmUsername:(NSString *)username; + +@end + +@interface DWDashPaySetupFlowController : UIViewController + +- (instancetype)initWithDashPayModel:(id)dashPayModel + invitation:(nullable NSURL *)invitationURL + definedUsername:(nullable NSString *)definedUsername; + +- (instancetype)initWithConfirmationDelegate:(id)delegate; + +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/DWDashPaySetupFlowController.m b/DashPay/Presentation/Setup/DWDashPaySetupFlowController.m new file mode 100644 index 000000000..847eb7125 --- /dev/null +++ b/DashPay/Presentation/Setup/DWDashPaySetupFlowController.m @@ -0,0 +1,316 @@ +// +// 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 "DWDashPaySetupFlowController.h" + +#import "DWConfirmUsernameViewController.h" +#import "DWContainerViewController.h" +#import "DWCreateUsernameViewController.h" +#import "DWDPRegistrationStatus.h" +#import "DWDashPaySetupModel.h" +#import "DWRegistrationCompletedViewController.h" +#import "DWUIKit.h" +#import "DWUsernameHeaderView.h" +#import "DWUsernamePendingViewController.h" +#import "UIViewController+DWDisplayError.h" +#import "UIViewController+DWEmbedding.h" + +static CGFloat const HeaderHeight(void) { + if (IS_IPHONE_6 || IS_IPHONE_5_OR_LESS) { + return 135.0; + } + else { + return 231.0; + } +} + +static CGFloat const LandscapeHeaderHeight(void) { + return 158.0; +} + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDashPaySetupFlowController () + +@property (readonly, nonatomic, strong) id dashPayModel; +@property (nullable, nonatomic, readonly, strong) NSURL *invitationURL; +@property (nullable, nonatomic, readonly, copy) NSString *definedUsername; +@property (nullable, nonatomic, weak) id confirmationDelegate; + +@property (null_resettable, nonatomic, strong) DWUsernameHeaderView *headerView; +@property (null_resettable, nonatomic, strong) UIView *contentView; +@property (nonatomic, strong) NSLayoutConstraint *headerHeightConstraint; + +@property (nonatomic, strong) DWContainerViewController *containerController; +@property (null_resettable, nonatomic, strong) DWCreateUsernameViewController *createUsernameViewController; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDashPaySetupFlowController + +- (instancetype)initWithDashPayModel:(id)dashPayModel + invitation:(NSURL *)invitationURL + definedUsername:(NSString *)definedUsername { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _dashPayModel = dashPayModel; + _invitationURL = invitationURL; + _definedUsername = definedUsername; + } + return self; +} + +- (instancetype)initWithConfirmationDelegate:(id)delegate { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _dashPayModel = [[DWDashPaySetupModel alloc] init]; + _invitationURL = nil; + _confirmationDelegate = delegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.clipsToBounds = YES; + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + [self.view addSubview:self.contentView]; + [self.view addSubview:self.headerView]; + + const BOOL isLandscape = CGRectGetWidth(self.view.bounds) > CGRectGetHeight(self.view.bounds); + const CGFloat headerHeight = isLandscape ? LandscapeHeaderHeight() : HeaderHeight(); + self.headerView.landscapeMode = isLandscape; + + [NSLayoutConstraint activateConstraints:@[ + [self.headerView.topAnchor constraintEqualToAnchor:self.view.topAnchor], + [self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor], + (self.headerHeightConstraint = [self.headerView.heightAnchor constraintEqualToConstant:headerHeight]), + + [self.contentView.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor], + [self.contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], + [self.view.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor], + [self.contentView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor], + ]]; + + self.containerController = [[DWContainerViewController alloc] init]; + [self dw_embedChild:self.containerController inContainer:self.contentView]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(registrationStatusUpdatedNotification) + name:DWDashPayRegistrationStatusUpdatedNotification + object:nil]; + + [self setCurrentStateController]; +} + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + + [coordinator + animateAlongsideTransition:^(id context) { + BOOL isLandscape = size.width > size.height; + self.headerView.landscapeMode = isLandscape; + if (isLandscape) { + self.headerHeightConstraint.constant = LandscapeHeaderHeight(); + } + else { + self.headerHeightConstraint.constant = HeaderHeight(); + } + } + completion:^(id _Nonnull context){ + + }]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleDefault; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [self.headerView showInitialAnimation]; +} + +#pragma mark - DWNavigationFullscreenable + +- (BOOL)requiresNoNavigationBar { + return YES; +} + +#pragma mark - Private + +- (void)registrationStatusUpdatedNotification { + if (self.dashPayModel.lastRegistrationError) { + [self dw_displayErrorModally:self.dashPayModel.lastRegistrationError]; + } + + [self setCurrentStateController]; +} + +- (void)setCurrentStateController { + if (self.definedUsername != nil) { + [self createUsername:self.definedUsername]; + return; + } + + if (self.dashPayModel.registrationStatus == nil || self.dashPayModel.registrationStatus.failed) { + [self showCreateUsernameController]; + + return; + } + + if (self.dashPayModel.registrationStatus.state != DWDPRegistrationState_Done) { + [self showPendingController:self.dashPayModel.username]; + } + else { + [self showRegistrationCompletedController:self.dashPayModel.username]; + } +} + +- (void)createUsername:(NSString *)username { + __weak typeof(self) weakSelf = self; + [self.dashPayModel createUsername:username invitation:self.invitationURL]; + [self showPendingController:username]; +} + +- (UIView *)contentView { + if (_contentView == nil) { + _contentView = [[UIView alloc] initWithFrame:CGRectZero]; + _contentView.translatesAutoresizingMaskIntoConstraints = NO; + } + return _contentView; +} + +- (DWUsernameHeaderView *)headerView { + if (_headerView == nil) { + _headerView = [[DWUsernameHeaderView alloc] initWithFrame:CGRectZero]; + _headerView.translatesAutoresizingMaskIntoConstraints = NO; + _headerView.preservesSuperviewLayoutMargins = YES; + _headerView.cancelButton.hidden = self.confirmationDelegate != nil; + [_headerView.cancelButton addTarget:self + action:@selector(cancelButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + } + + return _headerView; +} + +- (DWCreateUsernameViewController *)createUsernameViewController { + if (_createUsernameViewController == nil) { + DWCreateUsernameViewController *controller = + [[DWCreateUsernameViewController alloc] initWithDashPayModel:self.dashPayModel]; + controller.delegate = self; + _createUsernameViewController = controller; + } + return _createUsernameViewController; +} + +- (void)showPendingController:(NSString *)username { + DWUsernamePendingViewController *controller = [[DWUsernamePendingViewController alloc] init]; + controller.username = username; + controller.delegate = self; + __weak DWUsernamePendingViewController *weakController = controller; + self.headerView.titleBuilder = ^NSAttributedString *_Nonnull { + return [weakController attributedTitle]; + }; + [self.containerController transitionToController:controller]; +} + +- (void)showCreateUsernameController { + DWCreateUsernameViewController *controller = self.createUsernameViewController; + __weak DWCreateUsernameViewController *weakController = controller; + self.headerView.titleBuilder = ^NSAttributedString *_Nonnull { + return [weakController attributedTitle]; + }; + [self.containerController transitionToController:controller]; +} + +- (void)showRegistrationCompletedController:(NSString *)username { + NSAssert(username.length > 1, @"Invalid username"); + + [self.headerView configurePlanetsViewWithUsername:username]; + + DWRegistrationCompletedViewController *controller = [[DWRegistrationCompletedViewController alloc] init]; + controller.username = username; + controller.delegate = self; + self.headerView.titleBuilder = ^NSAttributedString *_Nonnull { + return [[NSAttributedString alloc] init]; + }; + [self.containerController transitionToController:controller]; +} + +#pragma mark - Actions + +- (void)cancelButtonAction { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - DWCreateUsernameViewControllerDelegate + +- (void)createUsernameViewController:(DWCreateUsernameViewController *)controller + registerUsername:(NSString *)username { + if ([self.dashPayModel shouldPresentRegistrationPaymentConfirmation]) { + DWConfirmUsernameViewController *confirmController = [[DWConfirmUsernameViewController alloc] initWithUsername:username]; + confirmController.delegate = self; + [self presentViewController:confirmController animated:YES completion:nil]; + } + else { + [self createUsername:username]; + } +} + +#pragma mark - DWConfirmUsernameViewControllerDelegate + +- (void)confirmUsernameViewControllerDidConfirm:(DWConfirmUsernameViewController *)controller { + NSString *username = controller.username; + [controller dismissViewControllerAnimated:YES + completion:^{ + if (self.confirmationDelegate) { + [self.confirmationDelegate dashPaySetupFlowController:self didConfirmUsername:username]; + } + else { + // initiate creation process once confirmation is dismissed because + // DashSync will be showing pin request modally + [self createUsername:username]; + } + }]; +} + +#pragma mark - DWUsernamePendingViewControllerDelegate + +- (void)usernamePendingViewControllerAction:(UIViewController *)controller { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - DWRegistrationCompletedViewControllerDelegate + +- (void)registrationCompletedViewControllerAction:(UIViewController *)controller { + [self.dashPayModel completeRegistration]; + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/DashPay/Presentation/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.h b/DashPay/Presentation/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.h new file mode 100644 index 000000000..490104b0d --- /dev/null +++ b/DashPay/Presentation/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.h @@ -0,0 +1,35 @@ +// +// 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 + +@protocol DWRegistrationCompletedViewControllerDelegate + +- (void)registrationCompletedViewControllerAction:(UIViewController *)controller; + +@end + +@interface DWRegistrationCompletedViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; +@property (nonatomic, copy) NSString *username; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.m b/DashPay/Presentation/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.m new file mode 100644 index 000000000..c582009f3 --- /dev/null +++ b/DashPay/Presentation/Setup/RegistrationCompleted/DWRegistrationCompletedViewController.m @@ -0,0 +1,156 @@ +// +// 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 "DWRegistrationCompletedViewController.h" + +#import "DWActionButton.h" +#import "DWBaseActionButtonViewController.h" +#import "DWUIKit.h" +#import "dashwallet-Swift.h" + +@interface DWRegistrationCompletedViewController () + +@property (null_resettable, nonatomic, strong) UIImageView *iconImageView; +@property (null_resettable, nonatomic, strong) UILabel *descriptionLabel; +@property (null_resettable, strong, nonatomic) UIButton *actionButton; + +@end + +@implementation DWRegistrationCompletedViewController + +- (void)setUsername:(NSString *)username { + _username = username; + [self updateDetailLabel]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_backgroundColor]; + + [self.view addSubview:self.iconImageView]; + [self.view addSubview:self.actionButton]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ self.descriptionLabel ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.alignment = UIStackViewAlignmentTop; + [self.view addSubview:stackView]; + + UILayoutGuide *marginsGuide = self.view.layoutMarginsGuide; + UILayoutGuide *safeAreaGuide = self.view.safeAreaLayoutGuide; + + [self.iconImageView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [self.iconImageView setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [stackView setContentHuggingPriority:UILayoutPriorityDefaultLow - 1 forAxis:UILayoutConstraintAxisVertical]; + + const CGFloat bottomPadding = [DWBaseActionButtonViewController deviceSpecificBottomPadding]; + [NSLayoutConstraint activateConstraints:@[ + [self.iconImageView.topAnchor constraintEqualToAnchor:marginsGuide.topAnchor], + [self.iconImageView.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], + + [stackView.topAnchor constraintEqualToAnchor:self.iconImageView.bottomAnchor + constant:24.0], + [stackView.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], + [stackView.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], + + [self.actionButton.topAnchor constraintEqualToAnchor:stackView.bottomAnchor], + [self.actionButton.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], + [self.actionButton.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], + [safeAreaGuide.bottomAnchor constraintEqualToAnchor:self.actionButton.bottomAnchor + constant:bottomPadding], + [self.actionButton.heightAnchor constraintEqualToConstant:DWBottomButtonHeight()], + ]]; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(contentSizeCategoryDidChangeNotification) + name:UIContentSizeCategoryDidChangeNotification + object:nil]; +} + +#pragma mark - Private + +- (UIImageView *)iconImageView { + if (_iconImageView == nil) { + UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dp_registration_done_icon"]]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + _iconImageView = imageView; + } + return _iconImageView; +} + +- (UILabel *)descriptionLabel { + if (_descriptionLabel == nil) { + UILabel *label = [[UILabel alloc] init]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.numberOfLines = 0; + label.adjustsFontSizeToFitWidth = YES; + label.minimumScaleFactor = 0.5; + label.contentMode = UIViewContentModeTop; + _descriptionLabel = label; + } + return _descriptionLabel; +} + +- (UIButton *)actionButton { + if (_actionButton == nil) { + DWActionButton *button = [[DWActionButton alloc] initWithFrame:CGRectZero]; + button.translatesAutoresizingMaskIntoConstraints = NO; + [button setTitle:NSLocalizedString(@"Continue", nil) forState:UIControlStateNormal]; + [button addTarget:self action:@selector(actionButtonAction) forControlEvents:UIControlEventTouchUpInside]; + _actionButton = button; + } + return _actionButton; +} + +- (void)contentSizeCategoryDidChangeNotification { + [self updateDetailLabel]; +} + +- (void)updateDetailLabel { + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + + [result beginEditing]; + + NSString *formattedString = [NSString + stringWithFormat:NSLocalizedString(@"Your username %@ has been successfully created on the Dash Network", nil), + self.username]; + NSAttributedString *detail = [[NSAttributedString alloc] + initWithString:formattedString + attributes:@{ + NSFontAttributeName : [UIFont dw_regularFontOfSize:26], + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + }]; + [result appendAttributedString:detail]; + + NSRange usernameRange = [formattedString rangeOfString:self.username]; + [result setAttributes:@{ + NSFontAttributeName : [UIFont dw_mediumFontOfSize:26], + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + } + range:usernameRange]; + + [result endEditing]; + + self.descriptionLabel.attributedText = result; +} + +- (void)actionButtonAction { + [self.delegate registrationCompletedViewControllerAction:self]; +} + +@end diff --git a/DashPay/Presentation/Setup/UsernamePending/DWUsernamePendingViewController.h b/DashPay/Presentation/Setup/UsernamePending/DWUsernamePendingViewController.h new file mode 100644 index 000000000..070ad6711 --- /dev/null +++ b/DashPay/Presentation/Setup/UsernamePending/DWUsernamePendingViewController.h @@ -0,0 +1,37 @@ +// +// 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 + +@protocol DWUsernamePendingViewControllerDelegate + +- (void)usernamePendingViewControllerAction:(UIViewController *)controller; + +@end + +@interface DWUsernamePendingViewController : UIViewController + +- (NSAttributedString *)attributedTitle; + +@property (nullable, nonatomic, weak) id delegate; +@property (nonatomic, copy) NSString *username; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashPay/Presentation/Setup/UsernamePending/DWUsernamePendingViewController.m b/DashPay/Presentation/Setup/UsernamePending/DWUsernamePendingViewController.m new file mode 100644 index 000000000..88fe268e0 --- /dev/null +++ b/DashPay/Presentation/Setup/UsernamePending/DWUsernamePendingViewController.m @@ -0,0 +1,205 @@ +// +// 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 "DWUsernamePendingViewController.h" + +#import "DWBaseActionButton.h" +#import "DWBaseActionButtonViewController.h" +#import "DWDashPayAnimationView.h" +#import "DWUIKit.h" +#import "dashwallet-Swift.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUsernamePendingViewController () + +@property (null_resettable, strong, nonatomic) UIView *contentView; +@property (null_resettable, strong, nonatomic) DWDashPayAnimationView *progressView; +@property (null_resettable, strong, nonatomic) UILabel *detailLabel; +@property (null_resettable, strong, nonatomic) UIButton *actionButton; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUsernamePendingViewController + +- (NSAttributedString *)attributedTitle { + NSDictionary *regularAttributes = @{ + NSFontAttributeName : [UIFont dw_regularFontOfSize:22.0], + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + }; + + NSAttributedString *pleaseString = + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Please wait", nil) + attributes:regularAttributes]; + return pleaseString; +} + +- (void)setUsername:(NSString *)username { + _username = username; + [self updateDetailLabel]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_dashBlueColor]; + + UIView *centeredView = [[UIView alloc] init]; + centeredView.translatesAutoresizingMaskIntoConstraints = NO; + [centeredView addSubview:self.progressView]; + [centeredView addSubview:self.detailLabel]; + + [self.contentView addSubview:centeredView]; + [self.view addSubview:self.contentView]; + [self.view addSubview:self.actionButton]; + + UILayoutGuide *marginsGuide = self.view.layoutMarginsGuide; + UILayoutGuide *safeAreaGuide = self.view.safeAreaLayoutGuide; + + [self.progressView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [self.detailLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [centeredView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + + const CGFloat bottomPadding = [DWBaseActionButtonViewController deviceSpecificBottomPadding]; + [NSLayoutConstraint activateConstraints:@[ + [self.contentView.topAnchor constraintEqualToAnchor:safeAreaGuide.topAnchor], + [self.contentView.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], + [self.contentView.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], + + [self.progressView.topAnchor constraintEqualToAnchor:centeredView.topAnchor], + [self.progressView.centerXAnchor constraintEqualToAnchor:centeredView.centerXAnchor], + + [self.detailLabel.topAnchor constraintEqualToAnchor:self.progressView.bottomAnchor + constant:10.0], + [self.detailLabel.leadingAnchor constraintEqualToAnchor:centeredView.leadingAnchor], + [self.detailLabel.trailingAnchor constraintEqualToAnchor:centeredView.trailingAnchor], + [self.detailLabel.bottomAnchor constraintEqualToAnchor:centeredView.bottomAnchor], + + [centeredView.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor], + [centeredView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], + [centeredView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], + + [self.actionButton.topAnchor constraintEqualToAnchor:self.contentView.bottomAnchor], + [self.actionButton.leadingAnchor constraintEqualToAnchor:marginsGuide.leadingAnchor], + [self.actionButton.trailingAnchor constraintEqualToAnchor:marginsGuide.trailingAnchor], + [safeAreaGuide.bottomAnchor constraintEqualToAnchor:self.actionButton.bottomAnchor + constant:bottomPadding], + [self.actionButton.heightAnchor constraintEqualToConstant:DWBottomButtonHeight()], + ]]; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(contentSizeCategoryDidChangeNotification) + name:UIContentSizeCategoryDidChangeNotification + object:nil]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [self.progressView startAnimating]; +} + +#pragma mark - Private + +- (UIView *)contentView { + if (_contentView == nil) { + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = [UIColor dw_dashBlueColor]; + _contentView = contentView; + } + return _contentView; +} + +- (DWDashPayAnimationView *)progressView { + if (_progressView == nil) { + DWDashPayAnimationView *progressView = [[DWDashPayAnimationView alloc] initWithFrame:CGRectZero]; + progressView.translatesAutoresizingMaskIntoConstraints = NO; + _progressView = progressView; + } + return _progressView; +} + +- (UILabel *)detailLabel { + if (_detailLabel == nil) { + UILabel *detailLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + detailLabel.translatesAutoresizingMaskIntoConstraints = NO; + detailLabel.numberOfLines = 0; + detailLabel.adjustsFontSizeToFitWidth = YES; + detailLabel.minimumScaleFactor = 0.5; + detailLabel.textAlignment = NSTextAlignmentCenter; + _detailLabel = detailLabel; + } + return _detailLabel; +} + +- (UIButton *)actionButton { + if (_actionButton == nil) { + DWBaseActionButton *actionButton = [[DWBaseActionButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 54.0)]; + actionButton.translatesAutoresizingMaskIntoConstraints = NO; + actionButton.layer.cornerRadius = 8; + actionButton.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; + [actionButton setBackgroundColor:[UIColor whiteColor] forState:UIControlStateNormal]; + [actionButton setTitleColor:[UIColor dw_dashBlueColor] forState:UIControlStateNormal]; + [actionButton setTitleColor:[[UIColor dw_dashBlueColor] colorWithAlphaComponent:0.5] forState:UIControlStateHighlighted]; + [actionButton setTitle:NSLocalizedString(@"Let me know when it’s done", nil) + forState:UIControlStateNormal]; + [actionButton addTarget:self + action:@selector(actionButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + _actionButton = actionButton; + } + return _actionButton; +} + +- (void)actionButtonAction:(id)sender { + [self.delegate usernamePendingViewControllerAction:self]; +} + +- (void)contentSizeCategoryDidChangeNotification { + [self updateDetailLabel]; +} + +- (void)updateDetailLabel { + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + + [result beginEditing]; + + NSString *formattedString = [NSString + stringWithFormat:NSLocalizedString(@"Your username %@ is being created on the Dash Network", nil), + self.username]; + NSAttributedString *detail = [[NSAttributedString alloc] + initWithString:formattedString + attributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : [UIColor dw_lightTitleColor]}]; + [result appendAttributedString:detail]; + + NSRange usernameRange = [formattedString rangeOfString:self.username]; + [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline], NSForegroundColorAttributeName : [UIColor dw_lightTitleColor]} range:usernameRange]; + + [result endEditing]; + + self.detailLabel.attributedText = result; +} + +@end diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index 2040e2f72..d25245ef0 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -669,23 +669,6 @@ C943B4C62A40A54600AF23C5 /* DWUserSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3962A40A54600AF23C5 /* DWUserSearchViewController.m */; }; C943B4C72A40A54600AF23C5 /* DWUserSearchResultViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B39A2A40A54600AF23C5 /* DWUserSearchResultViewController.m */; }; C943B4C82A40A54600AF23C5 /* DWSearchStateViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B39B2A40A54600AF23C5 /* DWSearchStateViewController.m */; }; - C943B4C92A40A54600AF23C5 /* DWUsernamePendingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B39F2A40A54600AF23C5 /* DWUsernamePendingViewController.m */; }; - C943B4CA2A40A54600AF23C5 /* DWUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3A42A40A54600AF23C5 /* DWUsernameValidationRule.m */; }; - C943B4CB2A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3A62A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m */; }; - C943B4CC2A40A54600AF23C5 /* DWLengthUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3A92A40A54600AF23C5 /* DWLengthUsernameValidationRule.m */; }; - C943B4CD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3AD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m */; }; - C943B4CE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3AE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m */; }; - C943B4CF2A40A54600AF23C5 /* DWInputUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3AF2A40A54600AF23C5 /* DWInputUsernameViewController.m */; }; - C943B4D02A40A54600AF23C5 /* DWCreateUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B02A40A54600AF23C5 /* DWCreateUsernameViewController.m */; }; - C943B4D12A40A54600AF23C5 /* DWUsernameHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B22A40A54600AF23C5 /* DWUsernameHeaderView.m */; }; - C943B4D22A40A54600AF23C5 /* DWPlanetarySystemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B32A40A54600AF23C5 /* DWPlanetarySystemView.m */; }; - C943B4D32A40A54600AF23C5 /* DWTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B42A40A54600AF23C5 /* DWTextField.m */; }; - C943B4D42A40A54600AF23C5 /* DWUsernameValidationView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3B92A40A54600AF23C5 /* DWUsernameValidationView.m */; }; - C943B4D52A40A54600AF23C5 /* DWRegistrationCompletedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3BB2A40A54600AF23C5 /* DWRegistrationCompletedViewController.m */; }; - C943B4D62A40A54600AF23C5 /* DWDashPaySetupFlowController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3BE2A40A54600AF23C5 /* DWDashPaySetupFlowController.m */; }; - C943B4D82A40A54600AF23C5 /* DWConfirmUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3C22A40A54600AF23C5 /* DWConfirmUsernameViewController.m */; }; - C943B4D92A40A54600AF23C5 /* DWConfirmUsernameContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3C52A40A54600AF23C5 /* DWConfirmUsernameContentView.m */; }; - C943B4DA2A40A54600AF23C5 /* DWConfirmUsernameContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C943B3C62A40A54600AF23C5 /* DWConfirmUsernameContentView.xib */; }; C943B4DB2A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3CA2A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.m */; }; C943B4DC2A40A54600AF23C5 /* DWInvitationFlowViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3CB2A40A54600AF23C5 /* DWInvitationFlowViewController.m */; }; C943B4DD2A40A54600AF23C5 /* DWDPWelcomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B3CC2A40A54600AF23C5 /* DWDPWelcomeViewController.m */; }; @@ -759,7 +742,6 @@ C943B5242A40A54600AF23C5 /* UIColor+DWDashPay.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4812A40A54600AF23C5 /* UIColor+DWDashPay.m */; }; C943B5252A40A54600AF23C5 /* DWDPSmallContactView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4872A40A54600AF23C5 /* DWDPSmallContactView.m */; }; C943B5262A40A54600AF23C5 /* DWDashPayAnimationView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4882A40A54600AF23C5 /* DWDashPayAnimationView.m */; }; - C943B5282A40A54600AF23C5 /* DWNetworkErrorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B48B2A40A54600AF23C5 /* DWNetworkErrorViewController.m */; }; C943B5292A40A54600AF23C5 /* DWDashPayConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B48D2A40A54600AF23C5 /* DWDashPayConstants.m */; }; C943B52A2A40A54600AF23C5 /* UIImageView+DWDPAvatar.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B48F2A40A54600AF23C5 /* UIImageView+DWDPAvatar.m */; }; C943B52B2A40A54600AF23C5 /* DWDashPayContactsActions.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B4912A40A54600AF23C5 /* DWDashPayContactsActions.m */; }; @@ -784,6 +766,23 @@ C943B5582A40DA3700AF23C5 /* DWFullScreenModalControllerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5572A40DA3700AF23C5 /* DWFullScreenModalControllerViewController.m */; }; C943B55B2A40DD4000AF23C5 /* NSArray+DWFlatten.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B55A2A40DD4000AF23C5 /* NSArray+DWFlatten.m */; }; C943B55E2A40E6F200AF23C5 /* DWFilterHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B55C2A40E6F200AF23C5 /* DWFilterHeaderView.m */; }; + C943B5892A40ED5A00AF23C5 /* DWDashPaySetupFlowController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5612A40ED4000AF23C5 /* DWDashPaySetupFlowController.m */; }; + C943B58A2A40ED5F00AF23C5 /* DWUsernamePendingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5632A40ED4000AF23C5 /* DWUsernamePendingViewController.m */; }; + C943B58B2A40ED6F00AF23C5 /* DWInputUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B56F2A40ED4200AF23C5 /* DWInputUsernameViewController.m */; }; + C943B58C2A40ED6F00AF23C5 /* DWUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5882A40ED4500AF23C5 /* DWUsernameValidationRule.m */; }; + C943B58D2A40ED6F00AF23C5 /* DWCreateUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B56E2A40ED4200AF23C5 /* DWCreateUsernameViewController.m */; }; + C943B58E2A40ED6F00AF23C5 /* DWAllowedCharactersUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5852A40ED4500AF23C5 /* DWAllowedCharactersUsernameValidationRule.m */; }; + C943B58F2A40ED6F00AF23C5 /* DWCheckExistenceUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5862A40ED4500AF23C5 /* DWCheckExistenceUsernameValidationRule.m */; }; + C943B5902A40ED6F00AF23C5 /* DWFirstUsernameSymbolValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B57E2A40ED4400AF23C5 /* DWFirstUsernameSymbolValidationRule.m */; }; + C943B5912A40ED7B00AF23C5 /* DWUsernameValidationView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B57A2A40ED4300AF23C5 /* DWUsernameValidationView.m */; }; + C943B5922A40ED7B00AF23C5 /* DWTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5772A40ED4300AF23C5 /* DWTextField.m */; }; + C943B5932A40ED7B00AF23C5 /* DWUsernameHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5792A40ED4300AF23C5 /* DWUsernameHeaderView.m */; }; + C943B5942A40ED7B00AF23C5 /* DWPlanetarySystemView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B57C2A40ED4400AF23C5 /* DWPlanetarySystemView.m */; }; + C943B5952A40EDAB00AF23C5 /* DWLengthUsernameValidationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5832A40ED4500AF23C5 /* DWLengthUsernameValidationRule.m */; }; + C943B5962A40EDC400AF23C5 /* DWRegistrationCompletedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5692A40ED4100AF23C5 /* DWRegistrationCompletedViewController.m */; }; + C943B5972A40EDDA00AF23C5 /* DWConfirmUsernameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5672A40ED4100AF23C5 /* DWConfirmUsernameViewController.m */; }; + C943B5982A40EDEF00AF23C5 /* DWConfirmUsernameContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B5722A40ED4200AF23C5 /* DWConfirmUsernameContentView.m */; }; + C943B59C2A40EE5300AF23C5 /* DWNetworkErrorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C943B59A2A40EE4800AF23C5 /* DWNetworkErrorViewController.m */; }; 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 */; }; @@ -2591,40 +2590,6 @@ C943B3992A40A54600AF23C5 /* DWSearchStateViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSearchStateViewController.h; sourceTree = ""; }; C943B39A2A40A54600AF23C5 /* DWUserSearchResultViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUserSearchResultViewController.m; sourceTree = ""; }; C943B39B2A40A54600AF23C5 /* DWSearchStateViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWSearchStateViewController.m; sourceTree = ""; }; - C943B39E2A40A54600AF23C5 /* DWUsernamePendingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUsernamePendingViewController.h; sourceTree = ""; }; - C943B39F2A40A54600AF23C5 /* DWUsernamePendingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUsernamePendingViewController.m; sourceTree = ""; }; - C943B3A12A40A54600AF23C5 /* DWInputUsernameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWInputUsernameViewController.h; sourceTree = ""; }; - C943B3A22A40A54600AF23C5 /* DWCreateUsernameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWCreateUsernameViewController.h; sourceTree = ""; }; - C943B3A42A40A54600AF23C5 /* DWUsernameValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUsernameValidationRule.m; sourceTree = ""; }; - C943B3A52A40A54600AF23C5 /* DWLengthUsernameValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWLengthUsernameValidationRule.h; sourceTree = ""; }; - C943B3A62A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAllowedCharactersUsernameValidationRule.m; sourceTree = ""; }; - C943B3A72A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWCheckExistenceUsernameValidationRule.h; sourceTree = ""; }; - C943B3A82A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWFirstUsernameSymbolValidationRule.h; sourceTree = ""; }; - C943B3A92A40A54600AF23C5 /* DWLengthUsernameValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWLengthUsernameValidationRule.m; sourceTree = ""; }; - C943B3AA2A40A54600AF23C5 /* DWUsernameValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUsernameValidationRule.h; sourceTree = ""; }; - C943B3AB2A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAllowedCharactersUsernameValidationRule.h; sourceTree = ""; }; - C943B3AC2A40A54600AF23C5 /* DWUsernameValidationRule+Protected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DWUsernameValidationRule+Protected.h"; sourceTree = ""; }; - C943B3AD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWFirstUsernameSymbolValidationRule.m; sourceTree = ""; }; - C943B3AE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWCheckExistenceUsernameValidationRule.m; sourceTree = ""; }; - C943B3AF2A40A54600AF23C5 /* DWInputUsernameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInputUsernameViewController.m; sourceTree = ""; }; - C943B3B02A40A54600AF23C5 /* DWCreateUsernameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWCreateUsernameViewController.m; sourceTree = ""; }; - C943B3B22A40A54600AF23C5 /* DWUsernameHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUsernameHeaderView.m; sourceTree = ""; }; - C943B3B32A40A54600AF23C5 /* DWPlanetarySystemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWPlanetarySystemView.m; sourceTree = ""; }; - C943B3B42A40A54600AF23C5 /* DWTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWTextField.m; sourceTree = ""; }; - C943B3B52A40A54600AF23C5 /* DWUsernameValidationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUsernameValidationView.h; sourceTree = ""; }; - C943B3B62A40A54600AF23C5 /* DWTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWTextField.h; sourceTree = ""; }; - C943B3B72A40A54600AF23C5 /* DWPlanetarySystemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWPlanetarySystemView.h; sourceTree = ""; }; - C943B3B82A40A54600AF23C5 /* DWUsernameHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWUsernameHeaderView.h; sourceTree = ""; }; - C943B3B92A40A54600AF23C5 /* DWUsernameValidationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWUsernameValidationView.m; sourceTree = ""; }; - C943B3BB2A40A54600AF23C5 /* DWRegistrationCompletedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWRegistrationCompletedViewController.m; sourceTree = ""; }; - C943B3BC2A40A54600AF23C5 /* DWRegistrationCompletedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWRegistrationCompletedViewController.h; sourceTree = ""; }; - C943B3BE2A40A54600AF23C5 /* DWDashPaySetupFlowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPaySetupFlowController.m; sourceTree = ""; }; - C943B3C02A40A54600AF23C5 /* DWDashPaySetupFlowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDashPaySetupFlowController.h; sourceTree = ""; }; - C943B3C22A40A54600AF23C5 /* DWConfirmUsernameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWConfirmUsernameViewController.m; sourceTree = ""; }; - C943B3C32A40A54600AF23C5 /* DWConfirmUsernameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWConfirmUsernameViewController.h; sourceTree = ""; }; - C943B3C52A40A54600AF23C5 /* DWConfirmUsernameContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWConfirmUsernameContentView.m; sourceTree = ""; }; - C943B3C62A40A54600AF23C5 /* DWConfirmUsernameContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DWConfirmUsernameContentView.xib; sourceTree = ""; }; - C943B3C72A40A54600AF23C5 /* DWConfirmUsernameContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWConfirmUsernameContentView.h; sourceTree = ""; }; C943B3C82A40A54600AF23C5 /* DWDashPayConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWDashPayConstants.h; sourceTree = ""; }; C943B3CA2A40A54600AF23C5 /* DWDPWelcomeCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPWelcomeCollectionViewController.m; sourceTree = ""; }; C943B3CB2A40A54600AF23C5 /* DWInvitationFlowViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWInvitationFlowViewController.m; sourceTree = ""; }; @@ -2782,8 +2747,6 @@ C943B4862A40A54600AF23C5 /* DWNetworkUnavailableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNetworkUnavailableView.h; sourceTree = ""; }; C943B4872A40A54600AF23C5 /* DWDPSmallContactView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDPSmallContactView.m; sourceTree = ""; }; C943B4882A40A54600AF23C5 /* DWDashPayAnimationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPayAnimationView.m; sourceTree = ""; }; - C943B48B2A40A54600AF23C5 /* DWNetworkErrorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWNetworkErrorViewController.m; sourceTree = ""; }; - C943B48C2A40A54600AF23C5 /* DWNetworkErrorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWNetworkErrorViewController.h; sourceTree = ""; }; C943B48D2A40A54600AF23C5 /* DWDashPayConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWDashPayConstants.m; sourceTree = ""; }; C943B48F2A40A54600AF23C5 /* UIImageView+DWDPAvatar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImageView+DWDPAvatar.m"; sourceTree = ""; }; C943B4902A40A54600AF23C5 /* DSBlockchainIdentity+DWDisplayName.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DSBlockchainIdentity+DWDisplayName.h"; sourceTree = ""; }; @@ -2831,6 +2794,42 @@ C943B55A2A40DD4000AF23C5 /* NSArray+DWFlatten.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+DWFlatten.m"; sourceTree = ""; }; C943B55C2A40E6F200AF23C5 /* DWFilterHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWFilterHeaderView.m; sourceTree = ""; }; C943B55D2A40E6F200AF23C5 /* DWFilterHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWFilterHeaderView.h; sourceTree = ""; }; + C943B5602A40ED4000AF23C5 /* DWDashPaySetupFlowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWDashPaySetupFlowController.h; sourceTree = ""; }; + C943B5612A40ED4000AF23C5 /* DWDashPaySetupFlowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWDashPaySetupFlowController.m; sourceTree = ""; }; + C943B5632A40ED4000AF23C5 /* DWUsernamePendingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUsernamePendingViewController.m; sourceTree = ""; }; + C943B5642A40ED4000AF23C5 /* DWUsernamePendingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUsernamePendingViewController.h; sourceTree = ""; }; + C943B5662A40ED4100AF23C5 /* DWConfirmUsernameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWConfirmUsernameViewController.h; sourceTree = ""; }; + C943B5672A40ED4100AF23C5 /* DWConfirmUsernameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWConfirmUsernameViewController.m; sourceTree = ""; }; + C943B5692A40ED4100AF23C5 /* DWRegistrationCompletedViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWRegistrationCompletedViewController.m; sourceTree = ""; }; + C943B56A2A40ED4100AF23C5 /* DWRegistrationCompletedViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWRegistrationCompletedViewController.h; sourceTree = ""; }; + C943B56C2A40ED4100AF23C5 /* DWCreateUsernameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWCreateUsernameViewController.h; sourceTree = ""; }; + C943B56D2A40ED4200AF23C5 /* DWInputUsernameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWInputUsernameViewController.h; sourceTree = ""; }; + C943B56E2A40ED4200AF23C5 /* DWCreateUsernameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWCreateUsernameViewController.m; sourceTree = ""; }; + C943B56F2A40ED4200AF23C5 /* DWInputUsernameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWInputUsernameViewController.m; sourceTree = ""; }; + C943B5712A40ED4200AF23C5 /* DWConfirmUsernameContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DWConfirmUsernameContentView.xib; sourceTree = ""; }; + C943B5722A40ED4200AF23C5 /* DWConfirmUsernameContentView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWConfirmUsernameContentView.m; sourceTree = ""; }; + C943B5732A40ED4200AF23C5 /* DWConfirmUsernameContentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWConfirmUsernameContentView.h; sourceTree = ""; }; + C943B5752A40ED4300AF23C5 /* DWUsernameValidationView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUsernameValidationView.h; sourceTree = ""; }; + C943B5762A40ED4300AF23C5 /* DWPlanetarySystemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWPlanetarySystemView.h; sourceTree = ""; }; + C943B5772A40ED4300AF23C5 /* DWTextField.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWTextField.m; sourceTree = ""; }; + C943B5782A40ED4300AF23C5 /* DWUsernameHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUsernameHeaderView.h; sourceTree = ""; }; + C943B5792A40ED4300AF23C5 /* DWUsernameHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUsernameHeaderView.m; sourceTree = ""; }; + C943B57A2A40ED4300AF23C5 /* DWUsernameValidationView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUsernameValidationView.m; sourceTree = ""; }; + C943B57B2A40ED4400AF23C5 /* DWTextField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWTextField.h; sourceTree = ""; }; + C943B57C2A40ED4400AF23C5 /* DWPlanetarySystemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWPlanetarySystemView.m; sourceTree = ""; }; + C943B57E2A40ED4400AF23C5 /* DWFirstUsernameSymbolValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWFirstUsernameSymbolValidationRule.m; sourceTree = ""; }; + C943B57F2A40ED4400AF23C5 /* DWLengthUsernameValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWLengthUsernameValidationRule.h; sourceTree = ""; }; + C943B5802A40ED4400AF23C5 /* DWUsernameValidationRule+Protected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DWUsernameValidationRule+Protected.h"; sourceTree = ""; }; + C943B5812A40ED4400AF23C5 /* DWAllowedCharactersUsernameValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWAllowedCharactersUsernameValidationRule.h; sourceTree = ""; }; + C943B5822A40ED4400AF23C5 /* DWUsernameValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWUsernameValidationRule.h; sourceTree = ""; }; + C943B5832A40ED4500AF23C5 /* DWLengthUsernameValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWLengthUsernameValidationRule.m; sourceTree = ""; }; + C943B5842A40ED4500AF23C5 /* DWCheckExistenceUsernameValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWCheckExistenceUsernameValidationRule.h; sourceTree = ""; }; + C943B5852A40ED4500AF23C5 /* DWAllowedCharactersUsernameValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWAllowedCharactersUsernameValidationRule.m; sourceTree = ""; }; + C943B5862A40ED4500AF23C5 /* DWCheckExistenceUsernameValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWCheckExistenceUsernameValidationRule.m; sourceTree = ""; }; + C943B5872A40ED4500AF23C5 /* DWFirstUsernameSymbolValidationRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWFirstUsernameSymbolValidationRule.h; sourceTree = ""; }; + C943B5882A40ED4500AF23C5 /* DWUsernameValidationRule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWUsernameValidationRule.m; sourceTree = ""; }; + C943B59A2A40EE4800AF23C5 /* DWNetworkErrorViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DWNetworkErrorViewController.m; sourceTree = ""; }; + C943B59B2A40EE4800AF23C5 /* DWNetworkErrorViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DWNetworkErrorViewController.h; 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 = ""; }; @@ -3878,20 +3877,16 @@ 2A4E533C22F025ED00E5168A /* Cells */ = { isa = PBXGroup; children = ( - C943B55D2A40E6F200AF23C5 /* DWFilterHeaderView.h */, - C943B55C2A40E6F200AF23C5 /* DWFilterHeaderView.m */, 2A4E535322F1D0D900E5168A /* TxListTableViewCell.xib */, C9F451F22A0C933700825057 /* SyncingHeaderView.swift */, - 2A5E4544243E0595006BA067 /* RegistrationStatus */, C9F452022A0CEB5800825057 /* TxListEmptyTableViewCell.swift */, 2A4E533F22F025FE00E5168A /* TxListEmptyTableViewCell.xib */, - 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */, 474C7219298A803200475CA6 /* TxListTableViewCell.swift */, ); path = Cells; sourceTree = ""; }; - 2A5E4544243E0595006BA067 /* RegistrationStatus */ = { + 2A5E4544243E0595006BA067 /* Registration Status */ = { isa = PBXGroup; children = ( 4708119C298CF9E0003FCA3D /* DWDPRegistrationErrorRetryDelegate.h */, @@ -3905,7 +3900,7 @@ 2A5E4546243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.m */, 2A5E4547243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.xib */, ); - path = RegistrationStatus; + path = "Registration Status"; sourceTree = ""; }; 2A6300402328CCAC00827825 /* LockScreen */ = { @@ -6340,14 +6335,12 @@ isa = PBXGroup; children = ( C943B3502A40A54500AF23C5 /* Contacts */, - C943B39C2A40A54600AF23C5 /* Setup */, C943B3C82A40A54600AF23C5 /* DWDashPayConstants.h */, C943B3C92A40A54600AF23C5 /* Welcome */, C943B3E82A40A54600AF23C5 /* Profile */, C943B4032A40A54600AF23C5 /* Items */, C943B44B2A40A54600AF23C5 /* Invites */, C943B47F2A40A54600AF23C5 /* Views */, - C943B48A2A40A54600AF23C5 /* Error */, C943B48D2A40A54600AF23C5 /* DWDashPayConstants.m */, C943B48E2A40A54600AF23C5 /* Global */, C943B49E2A40A54600AF23C5 /* Notifications */, @@ -6515,103 +6508,6 @@ path = Children; sourceTree = ""; }; - C943B39C2A40A54600AF23C5 /* Setup */ = { - isa = PBXGroup; - children = ( - C943B39D2A40A54600AF23C5 /* UsernamePending */, - C943B3A02A40A54600AF23C5 /* CreateUsername */, - C943B3BA2A40A54600AF23C5 /* RegistrationCompleted */, - C943B3BE2A40A54600AF23C5 /* DWDashPaySetupFlowController.m */, - C943B3C02A40A54600AF23C5 /* DWDashPaySetupFlowController.h */, - C943B3C12A40A54600AF23C5 /* ConfirmUsername */, - ); - path = Setup; - sourceTree = ""; - }; - C943B39D2A40A54600AF23C5 /* UsernamePending */ = { - isa = PBXGroup; - children = ( - C943B39E2A40A54600AF23C5 /* DWUsernamePendingViewController.h */, - C943B39F2A40A54600AF23C5 /* DWUsernamePendingViewController.m */, - ); - path = UsernamePending; - sourceTree = ""; - }; - C943B3A02A40A54600AF23C5 /* CreateUsername */ = { - isa = PBXGroup; - children = ( - C943B3A12A40A54600AF23C5 /* DWInputUsernameViewController.h */, - C943B3A22A40A54600AF23C5 /* DWCreateUsernameViewController.h */, - C943B3A32A40A54600AF23C5 /* Models */, - C943B3AF2A40A54600AF23C5 /* DWInputUsernameViewController.m */, - C943B3B02A40A54600AF23C5 /* DWCreateUsernameViewController.m */, - C943B3B12A40A54600AF23C5 /* Views */, - ); - path = CreateUsername; - sourceTree = ""; - }; - C943B3A32A40A54600AF23C5 /* Models */ = { - isa = PBXGroup; - children = ( - C943B3A42A40A54600AF23C5 /* DWUsernameValidationRule.m */, - C943B3A52A40A54600AF23C5 /* DWLengthUsernameValidationRule.h */, - C943B3A62A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m */, - C943B3A72A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.h */, - C943B3A82A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.h */, - C943B3A92A40A54600AF23C5 /* DWLengthUsernameValidationRule.m */, - C943B3AA2A40A54600AF23C5 /* DWUsernameValidationRule.h */, - C943B3AB2A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.h */, - C943B3AC2A40A54600AF23C5 /* DWUsernameValidationRule+Protected.h */, - C943B3AD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m */, - C943B3AE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m */, - ); - path = Models; - sourceTree = ""; - }; - C943B3B12A40A54600AF23C5 /* Views */ = { - isa = PBXGroup; - children = ( - C943B3B22A40A54600AF23C5 /* DWUsernameHeaderView.m */, - C943B3B32A40A54600AF23C5 /* DWPlanetarySystemView.m */, - C943B3B42A40A54600AF23C5 /* DWTextField.m */, - C943B3B52A40A54600AF23C5 /* DWUsernameValidationView.h */, - C943B3B62A40A54600AF23C5 /* DWTextField.h */, - C943B3B72A40A54600AF23C5 /* DWPlanetarySystemView.h */, - C943B3B82A40A54600AF23C5 /* DWUsernameHeaderView.h */, - C943B3B92A40A54600AF23C5 /* DWUsernameValidationView.m */, - ); - path = Views; - sourceTree = ""; - }; - C943B3BA2A40A54600AF23C5 /* RegistrationCompleted */ = { - isa = PBXGroup; - children = ( - C943B3BB2A40A54600AF23C5 /* DWRegistrationCompletedViewController.m */, - C943B3BC2A40A54600AF23C5 /* DWRegistrationCompletedViewController.h */, - ); - path = RegistrationCompleted; - sourceTree = ""; - }; - C943B3C12A40A54600AF23C5 /* ConfirmUsername */ = { - isa = PBXGroup; - children = ( - C943B3C22A40A54600AF23C5 /* DWConfirmUsernameViewController.m */, - C943B3C32A40A54600AF23C5 /* DWConfirmUsernameViewController.h */, - C943B3C42A40A54600AF23C5 /* Views */, - ); - path = ConfirmUsername; - sourceTree = ""; - }; - C943B3C42A40A54600AF23C5 /* Views */ = { - isa = PBXGroup; - children = ( - C943B3C52A40A54600AF23C5 /* DWConfirmUsernameContentView.m */, - C943B3C62A40A54600AF23C5 /* DWConfirmUsernameContentView.xib */, - C943B3C72A40A54600AF23C5 /* DWConfirmUsernameContentView.h */, - ); - path = Views; - sourceTree = ""; - }; C943B3C92A40A54600AF23C5 /* Welcome */ = { isa = PBXGroup; children = ( @@ -6967,27 +6863,18 @@ C943B47F2A40A54600AF23C5 /* Views */ = { isa = PBXGroup; children = ( + C943B4862A40A54600AF23C5 /* DWNetworkUnavailableView.h */, C943B4802A40A54600AF23C5 /* DWNetworkUnavailableView.m */, + C943B4852A40A54600AF23C5 /* UIColor+DWDashPay.h */, C943B4812A40A54600AF23C5 /* UIColor+DWDashPay.m */, C943B4822A40A54600AF23C5 /* DWDPSmallContactView.h */, - C943B4842A40A54600AF23C5 /* DWDashPayAnimationView.h */, - C943B4852A40A54600AF23C5 /* UIColor+DWDashPay.h */, - C943B4862A40A54600AF23C5 /* DWNetworkUnavailableView.h */, C943B4872A40A54600AF23C5 /* DWDPSmallContactView.m */, + C943B4842A40A54600AF23C5 /* DWDashPayAnimationView.h */, C943B4882A40A54600AF23C5 /* DWDashPayAnimationView.m */, ); path = Views; sourceTree = ""; }; - C943B48A2A40A54600AF23C5 /* Error */ = { - isa = PBXGroup; - children = ( - C943B48B2A40A54600AF23C5 /* DWNetworkErrorViewController.m */, - C943B48C2A40A54600AF23C5 /* DWNetworkErrorViewController.h */, - ); - path = Error; - sourceTree = ""; - }; C943B48E2A40A54600AF23C5 /* Global */ = { isa = PBXGroup; children = ( @@ -7100,6 +6987,110 @@ path = Containers; sourceTree = ""; }; + C943B55F2A40ECEC00AF23C5 /* Cells */ = { + isa = PBXGroup; + children = ( + 2A5E4544243E0595006BA067 /* Registration Status */, + C943B55D2A40E6F200AF23C5 /* DWFilterHeaderView.h */, + C943B55C2A40E6F200AF23C5 /* DWFilterHeaderView.m */, + 2A4E534C22F03AAC00E5168A /* DWFilterHeaderView.xib */, + ); + path = Cells; + sourceTree = ""; + }; + C943B5622A40ED4000AF23C5 /* UsernamePending */ = { + isa = PBXGroup; + children = ( + C943B5642A40ED4000AF23C5 /* DWUsernamePendingViewController.h */, + C943B5632A40ED4000AF23C5 /* DWUsernamePendingViewController.m */, + ); + path = UsernamePending; + sourceTree = ""; + }; + C943B5652A40ED4000AF23C5 /* ConfirmUsername */ = { + isa = PBXGroup; + children = ( + C943B5672A40ED4100AF23C5 /* DWConfirmUsernameViewController.m */, + C943B5662A40ED4100AF23C5 /* DWConfirmUsernameViewController.h */, + C943B5702A40ED4200AF23C5 /* Views */, + ); + path = ConfirmUsername; + sourceTree = ""; + }; + C943B5682A40ED4100AF23C5 /* RegistrationCompleted */ = { + isa = PBXGroup; + children = ( + C943B5692A40ED4100AF23C5 /* DWRegistrationCompletedViewController.m */, + C943B56A2A40ED4100AF23C5 /* DWRegistrationCompletedViewController.h */, + ); + path = RegistrationCompleted; + sourceTree = ""; + }; + C943B56B2A40ED4100AF23C5 /* CreateUsername */ = { + isa = PBXGroup; + children = ( + C943B56D2A40ED4200AF23C5 /* DWInputUsernameViewController.h */, + C943B56C2A40ED4100AF23C5 /* DWCreateUsernameViewController.h */, + C943B57D2A40ED4400AF23C5 /* Models */, + C943B56F2A40ED4200AF23C5 /* DWInputUsernameViewController.m */, + C943B56E2A40ED4200AF23C5 /* DWCreateUsernameViewController.m */, + C943B5742A40ED4300AF23C5 /* Views */, + ); + path = CreateUsername; + sourceTree = ""; + }; + C943B5702A40ED4200AF23C5 /* Views */ = { + isa = PBXGroup; + children = ( + C943B5722A40ED4200AF23C5 /* DWConfirmUsernameContentView.m */, + C943B5712A40ED4200AF23C5 /* DWConfirmUsernameContentView.xib */, + C943B5732A40ED4200AF23C5 /* DWConfirmUsernameContentView.h */, + ); + path = Views; + sourceTree = ""; + }; + C943B5742A40ED4300AF23C5 /* Views */ = { + isa = PBXGroup; + children = ( + C943B5792A40ED4300AF23C5 /* DWUsernameHeaderView.m */, + C943B57C2A40ED4400AF23C5 /* DWPlanetarySystemView.m */, + C943B5772A40ED4300AF23C5 /* DWTextField.m */, + C943B5752A40ED4300AF23C5 /* DWUsernameValidationView.h */, + C943B57B2A40ED4400AF23C5 /* DWTextField.h */, + C943B5762A40ED4300AF23C5 /* DWPlanetarySystemView.h */, + C943B5782A40ED4300AF23C5 /* DWUsernameHeaderView.h */, + C943B57A2A40ED4300AF23C5 /* DWUsernameValidationView.m */, + ); + path = Views; + sourceTree = ""; + }; + C943B57D2A40ED4400AF23C5 /* Models */ = { + isa = PBXGroup; + children = ( + C943B5882A40ED4500AF23C5 /* DWUsernameValidationRule.m */, + C943B57F2A40ED4400AF23C5 /* DWLengthUsernameValidationRule.h */, + C943B5852A40ED4500AF23C5 /* DWAllowedCharactersUsernameValidationRule.m */, + C943B5842A40ED4500AF23C5 /* DWCheckExistenceUsernameValidationRule.h */, + C943B5872A40ED4500AF23C5 /* DWFirstUsernameSymbolValidationRule.h */, + C943B5832A40ED4500AF23C5 /* DWLengthUsernameValidationRule.m */, + C943B5822A40ED4400AF23C5 /* DWUsernameValidationRule.h */, + C943B5812A40ED4400AF23C5 /* DWAllowedCharactersUsernameValidationRule.h */, + C943B5802A40ED4400AF23C5 /* DWUsernameValidationRule+Protected.h */, + C943B57E2A40ED4400AF23C5 /* DWFirstUsernameSymbolValidationRule.m */, + C943B5862A40ED4500AF23C5 /* DWCheckExistenceUsernameValidationRule.m */, + ); + path = Models; + sourceTree = ""; + }; + C943B5992A40EE4800AF23C5 /* Error */ = { + isa = PBXGroup; + children = ( + C943B59B2A40EE4800AF23C5 /* DWNetworkErrorViewController.h */, + C943B59A2A40EE4800AF23C5 /* DWNetworkErrorViewController.m */, + ); + path = Error; + sourceTree = ""; + }; C9D2C9552A320AC100D15901 /* DashPay */ = { isa = PBXGroup; children = ( @@ -7113,6 +7104,7 @@ C9D2C9582A386A5B00D15901 /* Presentation */ = { isa = PBXGroup; children = ( + C943B5992A40EE4800AF23C5 /* Error */, C943B5552A40DA2F00AF23C5 /* Containers */, C943B54F2A40C21A00AF23C5 /* Filter View */, C943B5402A40AFC400AF23C5 /* Tx Details */, @@ -7129,6 +7121,7 @@ C9D2C9592A386A6700D15901 /* Home */ = { isa = PBXGroup; children = ( + C943B55F2A40ECEC00AF23C5 /* Cells */, C943B5442A40B4AF00AF23C5 /* DWDashPayReadyProtocol.h */, C9D2C9662A3875AB00D15901 /* Model */, C9D2C95F2A386D9700D15901 /* Views */, @@ -7193,6 +7186,12 @@ C9D2C96D2A38777A00D15901 /* Setup */ = { isa = PBXGroup; children = ( + C943B5622A40ED4000AF23C5 /* UsernamePending */, + C943B56B2A40ED4100AF23C5 /* CreateUsername */, + C943B5682A40ED4100AF23C5 /* RegistrationCompleted */, + C943B5612A40ED4000AF23C5 /* DWDashPaySetupFlowController.m */, + C943B5602A40ED4000AF23C5 /* DWDashPaySetupFlowController.h */, + C943B5652A40ED4000AF23C5 /* ConfirmUsername */, C9D2C96E2A38778400D15901 /* Model */, ); path = Setup; @@ -7737,7 +7736,6 @@ C9D2C9442A320AA000D15901 /* uphold-logout.jpg in Resources */, C9D2C9452A320AA000D15901 /* AmountPreviewView.xib in Resources */, C9D2C9462A320AA000D15901 /* TxDetailActionCell.xib in Resources */, - C943B4DA2A40A54600AF23C5 /* DWConfirmUsernameContentView.xib in Resources */, C9D2C9472A320AA000D15901 /* CNCreateAccountCell.xib in Resources */, C943B50F2A40A54600AF23C5 /* DWConfirmInvitationContentView.xib in Resources */, ); @@ -8738,13 +8736,11 @@ C943B32A2A408CED00AF23C5 /* DWAvatarExternalSourceView.m in Sources */, C9D2C6BC2A320AA000D15901 /* WalletKeysOverviewModel.swift in Sources */, C9D2C6BD2A320AA000D15901 /* UIAssembly.swift in Sources */, - C943B4CE2A40A54600AF23C5 /* DWCheckExistenceUsernameValidationRule.m in Sources */, C943B4B32A40A54600AF23C5 /* DWContactsSearchDataSourceObject.m in Sources */, C9D2C6BE2A320AA000D15901 /* DWLocationManager.swift in Sources */, C943B3432A409F9E00AF23C5 /* UIImageView+DWDPAvatar.m in Sources */, C943B5212A40A54600AF23C5 /* SuccessInvitationTopView.swift in Sources */, C9D2C6BF2A320AA000D15901 /* FetchingNextPageCell.swift in Sources */, - C943B4CA2A40A54600AF23C5 /* DWUsernameValidationRule.m in Sources */, C943B3172A408CED00AF23C5 /* DWErrorUpdatingUserProfileView.m in Sources */, C943B4E32A40A54600AF23C5 /* DWPassthroughStackView.m in Sources */, C943B32E2A408CED00AF23C5 /* DWAvatarGravatarViewController.m in Sources */, @@ -8773,8 +8769,6 @@ C9D2C6D12A320AA000D15901 /* DWReceiveModel.m in Sources */, C9D2C6D22A320AA000D15901 /* TransactionWrapper.swift in Sources */, C9D2C6D32A320AA000D15901 /* Transactions.swift in Sources */, - C943B5282A40A54600AF23C5 /* DWNetworkErrorViewController.m in Sources */, - C943B4D42A40A54600AF23C5 /* DWUsernameValidationView.m in Sources */, C9D2C6D42A320AA000D15901 /* DWRecoverTextView.m in Sources */, C943B5012A40A54600AF23C5 /* DWDPGenericItemView.m in Sources */, C9D2C6D52A320AA000D15901 /* TxUserInfo.swift in Sources */, @@ -8789,13 +8783,13 @@ C943B3222A408CED00AF23C5 /* DWImgurInfoViewController.m in Sources */, C9D2C6DD2A320AA000D15901 /* NumberKeyboard.swift in Sources */, C9D2C6DE2A320AA000D15901 /* DWSeedPhraseTitledView.m in Sources */, - C943B4D02A40A54600AF23C5 /* DWCreateUsernameViewController.m in Sources */, C9D2C6E02A320AA000D15901 /* CrowdNodeTopUpTx.swift in Sources */, C943B3302A408CED00AF23C5 /* DWSaveAlertChildView.m in Sources */, C9D2C6E12A320AA000D15901 /* BackupInfoItemView.swift in Sources */, C9D2C6E32A320AA000D15901 /* AccountCreatingController.swift in Sources */, C9D2C6E42A320AA000D15901 /* UIView+DWReuseHelper.m in Sources */, C9D2C6E52A320AA000D15901 /* DWExtendedContainerViewController.m in Sources */, + C943B58B2A40ED6F00AF23C5 /* DWInputUsernameViewController.m in Sources */, C9D2C6E62A320AA000D15901 /* CrowdNodeEndpoint.swift in Sources */, C9D2C6E72A320AA000D15901 /* ServiceEntryPointModel.swift in Sources */, C943B4B72A40A54600AF23C5 /* DWBaseContactsModel.m in Sources */, @@ -8835,6 +8829,7 @@ C9D2C7052A320AA000D15901 /* DWRequestAmountContentView.m in Sources */, C943B3312A408CED00AF23C5 /* DWProfileAboutCellModel.m in Sources */, C943B4C42A40A54600AF23C5 /* DWSearchViewController.m in Sources */, + C943B58A2A40ED5F00AF23C5 /* DWUsernamePendingViewController.m in Sources */, C9D2C7062A320AA000D15901 /* DWOnboardingModel.m in Sources */, C9D2C7072A320AA000D15901 /* AmountInputTypeSwitcher.swift in Sources */, C943B4FA2A40A54600AF23C5 /* DWDPOutgoingRequestNotificationObject.m in Sources */, @@ -8851,7 +8846,6 @@ C943B33B2A408CED00AF23C5 /* DWUploadAvatarChildView.m in Sources */, C9D2C7112A320AA000D15901 /* Coinbase.swift in Sources */, C9D2C7122A320AA000D15901 /* SyncModel.swift in Sources */, - C943B4D32A40A54600AF23C5 /* DWTextField.m in Sources */, C9D2C7132A320AA000D15901 /* ReceiveContentView.swift in Sources */, C943B4FB2A40A54600AF23C5 /* DWDPNewIncomingRequestNotificationObject.m in Sources */, C9D2C7142A320AA000D15901 /* AmountView.swift in Sources */, @@ -8913,9 +8907,7 @@ C9D2C7402A320AA000D15901 /* MerchantListViewController.swift in Sources */, C9D2C7412A320AA000D15901 /* DWUpholdCardObject.m in Sources */, C9D2C7422A320AA000D15901 /* DWIntrinsicCollectionView.m in Sources */, - C943B4CC2A40A54600AF23C5 /* DWLengthUsernameValidationRule.m in Sources */, C9D2C7432A320AA000D15901 /* DWRootModelStub.m in Sources */, - C943B4D52A40A54600AF23C5 /* DWRegistrationCompletedViewController.m in Sources */, C943B3272A408CED00AF23C5 /* DWAvatarEditSelectorViewController.m in Sources */, C9D2C7452A320AA000D15901 /* UIDevice+DashWallet.m in Sources */, C9D2C7462A320AA000D15901 /* CBAccountManager.swift in Sources */, @@ -8938,7 +8930,6 @@ C943B4C32A40A54600AF23C5 /* DWTitleActionHeaderView.m in Sources */, C9D2C7502A320AA000D15901 /* ServiceOverviewViewController.swift in Sources */, C9D2C7512A320AA000D15901 /* AccountListModel.swift in Sources */, - C943B4D12A40A54600AF23C5 /* DWUsernameHeaderView.m in Sources */, C9D2C7532A320AA000D15901 /* DWRecoverWalletCommand.m in Sources */, C9D2C7542A320AA000D15901 /* DWSwitcherFormTableViewCell.m in Sources */, C9D2C7552A320AA000D15901 /* DWPaymentInput.m in Sources */, @@ -8998,6 +8989,7 @@ C9D2C7852A320AA000D15901 /* BalanceModel.swift in Sources */, C9D2C7862A320AA000D15901 /* TxReclassifyTransactionsInfoViewController.swift in Sources */, C9D2C7872A320AA000D15901 /* DWTransactionListDataProvider.m in Sources */, + C943B5902A40ED6F00AF23C5 /* DWFirstUsernameSymbolValidationRule.m in Sources */, C943B53E2A40A6BE00AF23C5 /* DPAlertViewController.m in Sources */, C9D2C9692A3875BA00D15901 /* DWCurrentUserProfileModel.m in Sources */, C9D2C7882A320AA000D15901 /* DWModalPopupTransition.m in Sources */, @@ -9036,10 +9028,8 @@ C9D2C7A82A320AA000D15901 /* PortalServiceItemCell.swift in Sources */, C9D2C7AA2A320AA000D15901 /* WithdrawalConfirmationController.swift in Sources */, C943B5002A40A54600AF23C5 /* DWDPTxItemView.m in Sources */, - C943B4C92A40A54600AF23C5 /* DWUsernamePendingViewController.m in Sources */, C9D2C7AB2A320AA000D15901 /* DWBaseFormTableViewCell.m in Sources */, C9D2C7AC2A320AA000D15901 /* AccountCell.swift in Sources */, - C943B4D82A40A54600AF23C5 /* DWConfirmUsernameViewController.m in Sources */, C9D2C7AD2A320AA000D15901 /* DWTransactionListDataProviderStub.m in Sources */, C943B33A2A408CED00AF23C5 /* DWUploadAvatarModel.m in Sources */, C943B3392A408CED00AF23C5 /* DWUploadAvatarViewController.m in Sources */, @@ -9048,11 +9038,14 @@ C9D2C7B22A320AA000D15901 /* DWBaseActionButton.m in Sources */, C9D2C7B32A320AA000D15901 /* SpecifyAmountViewController.swift in Sources */, C943B4EA2A40A54600AF23C5 /* DWUserProfileViewController.m in Sources */, + C943B5922A40ED7B00AF23C5 /* DWTextField.m in Sources */, C9D2C7B42A320AA000D15901 /* DWUpholdTransactionObject+DWView.m in Sources */, C9D2C7B52A320AA000D15901 /* DWBackupSeedPhraseViewController.m in Sources */, C943B54A2A40B52F00AF23C5 /* NSLayoutConstraint+DWAutolayout.m in Sources */, + C943B5892A40ED5A00AF23C5 /* DWDashPaySetupFlowController.m in Sources */, C9D2C7B62A320AA000D15901 /* CrowdNodeDepositTx.swift in Sources */, C9D2C7B72A320AA000D15901 /* DWUpholdViewController.m in Sources */, + C943B5962A40EDC400AF23C5 /* DWRegistrationCompletedViewController.m in Sources */, C9D2C7B92A320AA000D15901 /* DWIntrinsicTableView.m in Sources */, C9D2C7BA2A320AA000D15901 /* DWURLActions.m in Sources */, C9D2C7BB2A320AA000D15901 /* KeysOverviewViewController.swift in Sources */, @@ -9062,11 +9055,13 @@ C9D2C7BE2A320AA000D15901 /* BRAppleWatchTransactionData.m in Sources */, C9D2C7BF2A320AA000D15901 /* TerritoriesListViewController.swift in Sources */, C9D2C7C02A320AA000D15901 /* HomeHeaderModel.swift in Sources */, + C943B5942A40ED7B00AF23C5 /* DWPlanetarySystemView.m in Sources */, C9D2C7C12A320AA000D15901 /* TerritoryListModel.swift in Sources */, C9D2C7C22A320AA000D15901 /* DWUpholdConfirmTransferModel.m in Sources */, C9D2C7C32A320AA000D15901 /* DWDPRegistrationStatus.m in Sources */, C9D2C7C42A320AA000D15901 /* CurrencyExchanger_Objc.m in Sources */, C943B32B2A408CED00AF23C5 /* DWAvatarExternalLoadingView.m in Sources */, + C943B5972A40EDDA00AF23C5 /* DWConfirmUsernameViewController.m in Sources */, C9D2C7C52A320AA000D15901 /* GiftCardInfoViewController.swift in Sources */, C9D2C7C62A320AA000D15901 /* Tools.swift in Sources */, C9D2C7C72A320AA000D15901 /* PortalModel.swift in Sources */, @@ -9106,7 +9101,6 @@ C9D2C7E42A320AA000D15901 /* DWURLRequestHandler.m in Sources */, C9D2C7E52A320AA000D15901 /* CoinbaseAPIEndpoint.swift in Sources */, C9D2C7E62A320AA000D15901 /* ConfirmOrderCells.swift in Sources */, - C943B4D22A40A54600AF23C5 /* DWPlanetarySystemView.m in Sources */, C9D2C7E72A320AA000D15901 /* CoinbaseUserAccountData.swift in Sources */, C943B3202A408CED00AF23C5 /* DWImgurInfoChildView.m in Sources */, C9D2C7E82A320AA000D15901 /* DWDPRegistrationDoneTableViewCell.m in Sources */, @@ -9149,9 +9143,9 @@ C9D2C8132A320AA000D15901 /* DWPinField.m in Sources */, C943B3252A408CED00AF23C5 /* DWEditProfileViewController.m in Sources */, C943B4DC2A40A54600AF23C5 /* DWInvitationFlowViewController.m in Sources */, + C943B59C2A40EE5300AF23C5 /* DWNetworkErrorViewController.m in Sources */, C9D2C8142A320AA000D15901 /* DWCenteredScrollView.m in Sources */, C9D2C8162A320AA000D15901 /* DWPhraseRepairViewController.m in Sources */, - C943B4D62A40A54600AF23C5 /* DWDashPaySetupFlowController.m in Sources */, C9D2C8172A320AA000D15901 /* DWBiometricAuthModel.m in Sources */, C943B52A2A40A54600AF23C5 /* UIImageView+DWDPAvatar.m in Sources */, C9D2C8182A320AA000D15901 /* DWNumberKeyboardInputViewAudioFeedback.m in Sources */, @@ -9179,6 +9173,8 @@ C9D2C8262A320AA000D15901 /* CSVBuilder.swift in Sources */, C9D2C8272A320AA000D15901 /* DWDPRegistrationStatusTableViewCell.m in Sources */, C9D2C8282A320AA000D15901 /* CoinbaseUserAuthInformation.swift in Sources */, + C943B5982A40EDEF00AF23C5 /* DWConfirmUsernameContentView.m in Sources */, + C943B58C2A40ED6F00AF23C5 /* DWUsernameValidationRule.m in Sources */, C9D2C8292A320AA000D15901 /* DWTransactionStub.m in Sources */, C9D2C82A2A320AA000D15901 /* OnlineAccountEmailController.swift in Sources */, C9D2C82B2A320AA000D15901 /* DWBaseActionButtonViewController.m in Sources */, @@ -9194,6 +9190,7 @@ C9D2C8312A320AA000D15901 /* DWCheckbox.m in Sources */, C9D2C8322A320AA000D15901 /* DWRequestAmountViewController.m in Sources */, C9D2C8332A320AA000D15901 /* DerivationPathKeysModel.swift in Sources */, + C943B5932A40ED7B00AF23C5 /* DWUsernameHeaderView.m in Sources */, C9D2C8342A320AA000D15901 /* DWSeedUIConstants.m in Sources */, C9D2C8352A320AA000D15901 /* DWWindow.m in Sources */, C9D2C8362A320AA000D15901 /* BalanceNotifier.swift in Sources */, @@ -9232,7 +9229,6 @@ C9D2C8562A320AA000D15901 /* TransactionItemView.swift in Sources */, C9D2C8572A320AA000D15901 /* UIView+Reuse.swift in Sources */, C9D2C8582A320AA000D15901 /* DWUpholdClient.m in Sources */, - C943B4CD2A40A54600AF23C5 /* DWFirstUsernameSymbolValidationRule.m in Sources */, C9D2C8592A320AA000D15901 /* CrowdNodePortalViewController.swift in Sources */, C9D2C85A2A320AA000D15901 /* ConfirmOrderModel.swift in Sources */, C943B53F2A40A6BE00AF23C5 /* DPAlertChildContentsView.m in Sources */, @@ -9252,6 +9248,7 @@ C943B5322A40A54600AF23C5 /* DWNotificationsInvitationCell.m in Sources */, C9D2C86C2A320AA000D15901 /* ModalNavigationController.swift in Sources */, C943B5032A40A54600AF23C5 /* DWDPGenericImageItemView.m in Sources */, + C943B58F2A40ED6F00AF23C5 /* DWCheckExistenceUsernameValidationRule.m in Sources */, C9D2C86E2A320AA000D15901 /* TransferAmountModel.swift in Sources */, C9D2C86F2A320AA000D15901 /* NavigationBarAppearanceCustomizable.swift in Sources */, C943B3462A409FFA00AF23C5 /* DWDPAvatarView.m in Sources */, @@ -9259,6 +9256,8 @@ C9D2C8712A320AA000D15901 /* DerivationPathKeysViewController.swift in Sources */, C9D2C8722A320AA000D15901 /* DSTransaction+DashWallet.m in Sources */, C9D2C8732A320AA000D15901 /* DWHomeViewController+DWJailbreakCheck.m in Sources */, + C943B58D2A40ED6F00AF23C5 /* DWCreateUsernameViewController.m in Sources */, + C943B58E2A40ED6F00AF23C5 /* DWAllowedCharactersUsernameValidationRule.m in Sources */, C9D2C8742A320AA000D15901 /* DWLocalCurrencyModel.m in Sources */, C9D2C8752A320AA000D15901 /* DWMainMenuModel.m in Sources */, C9D2C8762A320AA000D15901 /* DWUpholdMainnetConstants.m in Sources */, @@ -9269,7 +9268,6 @@ C9D2C87D2A320AA000D15901 /* DWIntrinsicTextView.m in Sources */, C9D2C87E2A320AA000D15901 /* DWExploreTestnetViewController.m in Sources */, C9D2C87F2A320AA000D15901 /* AddressUserInfoDAO.swift in Sources */, - C943B4CB2A40A54600AF23C5 /* DWAllowedCharactersUsernameValidationRule.m in Sources */, C943B5112A40A54600AF23C5 /* DWInvitationHistoryViewController.m in Sources */, C9D2C9712A38778E00D15901 /* DWDashPaySetupModel.m in Sources */, C9D2C8812A320AA000D15901 /* DWModalTransition.m in Sources */, @@ -9278,7 +9276,6 @@ C9D2C8832A320AA000D15901 /* MerchantsDataProvider.swift in Sources */, C9D2C8842A320AA000D15901 /* FullCrowdNodeSignUpTxSet.swift in Sources */, C9D2C8852A320AA000D15901 /* DWSeedPhraseModel.m in Sources */, - C943B4CF2A40A54600AF23C5 /* DWInputUsernameViewController.m in Sources */, C9D2C8862A320AA000D15901 /* Constants.swift in Sources */, C9D2C8872A320AA000D15901 /* DWBaseFormCellModel.m in Sources */, C9D2C8882A320AA000D15901 /* ExploreMapAnnotationView.swift in Sources */, @@ -9314,6 +9311,7 @@ C9D2C8A32A320AA000D15901 /* DWBaseLegacyViewController.m in Sources */, C9D2C8A52A320AA000D15901 /* HomeView.swift in Sources */, C9D2C8A62A320AA000D15901 /* CoinbaseRatesProvider.swift in Sources */, + C943B5952A40EDAB00AF23C5 /* DWLengthUsernameValidationRule.m in Sources */, C9D2C8A72A320AA000D15901 /* TaxReportGenerator.swift in Sources */, C9D2C8A82A320AA000D15901 /* CrowdNodeError.swift in Sources */, C9D2C8A92A320AA000D15901 /* WithdrawalLimit.swift in Sources */, @@ -9341,6 +9339,7 @@ C943B34E2A40A4C500AF23C5 /* DWInfoPopupContentView.m in Sources */, C9D2C8BF2A320AA000D15901 /* ExplorePointOfUse.swift in Sources */, C9D2C8C12A320AA000D15901 /* DWDashPayModel.m in Sources */, + C943B5912A40ED7B00AF23C5 /* DWUsernameValidationView.m in Sources */, C9D2C8C22A320AA000D15901 /* TransactionObserver.swift in Sources */, C943B4EB2A40A54600AF23C5 /* DWModalUserProfileViewController.m in Sources */, C9D2C8C32A320AA000D15901 /* DWInfoTextCell.m in Sources */, @@ -9375,7 +9374,6 @@ C9D2C8DF2A320AA000D15901 /* NetworkUnavailableView.swift in Sources */, C9D2C8E02A320AA000D15901 /* DWBorderedActionButton.m in Sources */, C943B4FC2A40A54600AF23C5 /* DWDPEstablishedContactNotificationObject.m in Sources */, - C943B4D92A40A54600AF23C5 /* DWConfirmUsernameContentView.m in Sources */, C9D2C8E12A320AA000D15901 /* DWLockScreenViewController.m in Sources */, C9D2C8E22A320AA000D15901 /* UpholdTransferViewController.swift in Sources */, C943B31E2A408CED00AF23C5 /* DWUserProfileModalQRContentView.m in Sources */, From 1d159e2ede1faeeaa8cebac2228ff705ce757808 Mon Sep 17 00:00:00 2001 From: tikhop Date: Tue, 20 Jun 2023 13:28:24 +0400 Subject: [PATCH 007/123] fix: DashWallet compilation issues --- DashWallet.xcodeproj/project.pbxproj | 4 -- .../Sources/Models/Coinbase/Auth/CBAuth.swift | 1 - .../Home/DWHomeViewController+DWShortcuts.m | 5 +++ .../Sources/UI/Home/DWHomeViewController.m | 11 +++++- .../Sources/UI/Home/Models/DWHomeModel.m | 28 +++++++++---- .../Home Header View/HomeHeaderView.swift | 39 ++++++++++++------- .../UI/Menu/Settings/About/DWAboutModel.m | 5 ++- .../PaymentModels/DWPaymentProcessor.m | 7 +++- .../Receive/Models/DWBaseReceiveModel.m | 7 +++- .../Payments/Receive/Models/DWReceiveModel.m | 5 ++- .../UI/Tx/Details/Model/TxDetailModel.swift | 9 +++++ .../TitleDetailCell/DWTitleDetailCellView.m | 14 ++++++- 12 files changed, 99 insertions(+), 36 deletions(-) diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index d25245ef0..a8c529053 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -178,7 +178,6 @@ 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, ); }; }; - 2A56EF0624193AEB002C32F3 /* DWDashPayModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A56EF0524193AEB002C32F3 /* DWDashPayModel.m */; }; 2A58815921A5906C00FD4D2C /* DWBaseLegacyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A58815821A5906C00FD4D2C /* DWBaseLegacyViewController.m */; }; 2A5E4548243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5E4546243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.m */; }; 2A5E4549243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A5E4547243E06E7006BA067 /* DWDPRegistrationStatusTableViewCell.xib */; }; @@ -267,7 +266,6 @@ 2A913EB623A7E145006A2A59 /* DWTransactionStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A913EB523A7E145006A2A59 /* DWTransactionStub.m */; }; 2A9172C425233DC50024B4C5 /* DWPhraseRepairViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9172C325233DC50024B4C5 /* DWPhraseRepairViewController.m */; }; 2A9172D325233F4F0024B4C5 /* DWPhraseRepairChildViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9172D225233F4F0024B4C5 /* DWPhraseRepairChildViewController.m */; }; - 2A919F9F24A65CE00018C9A3 /* DWDPAmountContactView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A919F9E24A65CE00018C9A3 /* DWDPAmountContactView.m */; }; 2A9CEBAD22E1DA4000A50237 /* DWAppRootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBAC22E1DA4000A50237 /* DWAppRootViewController.m */; }; 2A9CEBB922E1FA1000A50237 /* DWHomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9CEBB822E1FA1000A50237 /* DWHomeViewController.m */; }; 2A9FFDF42230FF1A00956D5F /* UIView+DWAnimations.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFDEF2230FF1A00956D5F /* UIView+DWAnimations.m */; }; @@ -8497,7 +8495,6 @@ 47EE171729560DC200BA1986 /* ErrorPresentable.swift in Sources */, 2ACCD8E2231E507900A96B62 /* DWPinInputStepView.m in Sources */, 2A74EFED2305318000C475EB /* DWRecoverViewController.m in Sources */, - 2A919F9F24A65CE00018C9A3 /* DWDPAmountContactView.m in Sources */, 2A913EAE23A7AC86006A2A59 /* DWBaseTransactionListDataProvider.m in Sources */, 0F6EDFCC28C896BD000427E7 /* CoinbaseTransactionsRequest.swift in Sources */, 2A9CEBAD22E1DA4000A50237 /* DWAppRootViewController.m in Sources */, @@ -8597,7 +8594,6 @@ 2AD1CE6022D8E9D900C99324 /* DWSeedPhraseView.m in Sources */, 47B30D7C29100ABA0080C326 /* UIStackView+DashWallet.swift in Sources */, 47AE8B9C28BFAD3600490F5E /* ExplorePointOfUse.swift in Sources */, - 2A56EF0624193AEB002C32F3 /* DWDashPayModel.m in Sources */, 117ED4A128EC86E0006E3EE4 /* TransactionObserver.swift in Sources */, 2A4431D522D52F67009BAF7F /* DWInfoTextCell.m in Sources */, 47EE17192959CDC200BA1986 /* ColorizedText.swift in Sources */, diff --git a/DashWallet/Sources/Models/Coinbase/Auth/CBAuth.swift b/DashWallet/Sources/Models/Coinbase/Auth/CBAuth.swift index 0ad71b75a..6016da938 100644 --- a/DashWallet/Sources/Models/Coinbase/Auth/CBAuth.swift +++ b/DashWallet/Sources/Models/Coinbase/Auth/CBAuth.swift @@ -19,7 +19,6 @@ import AuthenticationServices import Foundation // MARK: - CBAuth - extension Notification.Name { static let userDidChangeNotification: Notification.Name = .init(rawValue: "userDidChangeNotification") } diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController+DWShortcuts.m b/DashWallet/Sources/UI/Home/DWHomeViewController+DWShortcuts.m index 8d91c7f1c..88381dff0 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController+DWShortcuts.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController+DWShortcuts.m @@ -19,7 +19,10 @@ #import +#if DASHPAY #import "DWDashPaySetupFlowController.h" +#endif + #import "DWExploreTestnetViewController.h" #import "DWGlobalOptions.h" #import "DWHomeViewController+DWImportPrivateKeyDelegateImpl.h" @@ -172,12 +175,14 @@ - (void)payToAddressAction:(UIView *)sender { } - (void)showCreateUsername { +#if DASHPAY DWDashPaySetupFlowController *controller = [[DWDashPaySetupFlowController alloc] initWithDashPayModel:self.model.dashPayModel invitation:nil definedUsername:nil]; controller.modalPresentationStyle = UIModalPresentationFullScreen; [self presentViewController:controller animated:YES completion:nil]; +#endif } - (void)showExploreDash { diff --git a/DashWallet/Sources/UI/Home/DWHomeViewController.m b/DashWallet/Sources/UI/Home/DWHomeViewController.m index 0a3ce118b..665060d22 100644 --- a/DashWallet/Sources/UI/Home/DWHomeViewController.m +++ b/DashWallet/Sources/UI/Home/DWHomeViewController.m @@ -23,13 +23,16 @@ #import "DWHomeViewController+DWBackupReminder.h" #import "DWHomeViewController+DWJailbreakCheck.h" #import "DWHomeViewController+DWShortcuts.h" -#import "DWModalUserProfileViewController.h" -#import "DWNotificationsViewController.h" #import "DWWindow.h" #import "UIViewController+DWTxFilter.h" #import "UIWindow+DSUtils.h" #import "dashwallet-Swift.h" +#if DASHPAY +#import "DWNotificationsViewController.h" +#import "DWModalUserProfileViewController.h" +#endif + NS_ASSUME_NONNULL_BEGIN @interface DWHomeViewController () @@ -106,8 +109,10 @@ - (void)homeView:(DWHomeView *)homeView showSyncingStatus:(UIView *)sender { } - (void)homeView:(DWHomeView *)homeView profileButtonAction:(UIControl *)sender { +#if DASHPAY DWNotificationsViewController *controller = [[DWNotificationsViewController alloc] initWithPayModel:self.payModel dataProvider:self.dataProvider]; [self.navigationController pushViewController:controller animated:YES]; +#endif } - (void)homeView:(DWHomeView *)homeView didSelectTransaction:(DSTransaction *)transaction { @@ -143,6 +148,7 @@ - (void)shortcutsView:(UIView *)view didSelectAction:(DWShortcutAction *)action #pragma mark - Private +#if DASHPAY - (void)payViewControllerDidHidePaymentResultToContact:(nullable id)contact { if (!contact) { return; @@ -154,6 +160,7 @@ - (void)payViewControllerDidHidePaymentResultToContact:(nullable id)payModel { return self.model.payModel; diff --git a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m index e901ca15f..06c97d407 100644 --- a/DashWallet/Sources/UI/Home/Models/DWHomeModel.m +++ b/DashWallet/Sources/UI/Home/Models/DWHomeModel.m @@ -23,9 +23,12 @@ #import #import "AppDelegate.h" +#if DASHPAY #import "DWDashPayConstants.h" #import "DWDashPayContactsUpdater.h" #import "DWDashPayModel.h" +#endif + #import "DWEnvironment.h" #import "DWGlobalOptions.h" #import "DWPayModel.h" @@ -85,7 +88,7 @@ - (instancetype)init { _dataProvider = [[DWTransactionListDataProvider alloc] init]; -#if DASHPAY_ENABLED +#if DASHPAY _dashPayModel = [[DWDashPayModel alloc] init]; #endif /* DASHPAY_ENABLED */ @@ -119,10 +122,7 @@ - (instancetype)init { selector:@selector(chainWalletsDidChangeNotification:) name:DSChainWalletsDidChangeNotification object:nil]; - [notificationCenter addObserver:self - selector:@selector(dashPayRegistrationStatusUpdatedNotification) - name:DWDashPayRegistrationStatusUpdatedNotification - object:nil]; + [notificationCenter addObserver:self selector:@selector(willWipeWalletNotification) name:DWWillWipeWalletNotification @@ -132,6 +132,13 @@ - (instancetype)init { name:DWApp.fiatCurrencyDidChangeNotification object:nil]; +#if DASHPAY + [notificationCenter addObserver:self + selector:@selector(dashPayRegistrationStatusUpdatedNotification) + name:DWDashPayRegistrationStatusUpdatedNotification + object:nil]; +#endif + [self reloadTxDataSource]; NSDate *date = [NSDate new]; @@ -273,7 +280,7 @@ - (BOOL)performOnSetupUpgrades { } - (void)walletDidWipe { -#if DASHPAY_ENABLED +#if DASHPAY self.dashPayModel = [[DWDashPayModel alloc] init]; #endif /* DASHPAY_ENABLED */ } @@ -292,6 +299,7 @@ - (void)checkCrowdNodeState { #pragma mark - DWShortcutsModelDataSource +#if DASHPAY - (BOOL)shouldShowCreateUserNameButton { if (self.reachability.networkReachabilityStatus == DSReachabilityStatusNotReachable) { return NO; @@ -319,6 +327,7 @@ - (BOOL)shouldShowCreateUserNameButton { BOOL isSynced = [SyncingActivityMonitor shared].state == SyncingActivityMonitorStateSyncDone; return canRegisterUsername && isSynced && isEnoughBalance; } +#endif #pragma mark - Notifications @@ -361,12 +370,15 @@ - (void)chainWalletsDidChangeNotification:(NSNotification *)notification { - (void)dashPayRegistrationStatusUpdatedNotification { [self reloadTxDataSource]; - +#if DASHPAY [[DWDashPayContactsUpdater sharedInstance] beginUpdating]; +#endif } - (void)willWipeWalletNotification { +#if DASHPAY [[DWDashPayContactsUpdater sharedInstance] endUpdating]; +#endif } #pragma mark - Private @@ -540,7 +552,9 @@ - (void)syncingActivityMonitorStateDidChangeWithPreviousState:(enum SyncingActiv if (self.dashPayModel.username != nil) { [self.receiveModel updateReceivingInfo]; +#if DASHPAY [[DWDashPayContactsUpdater sharedInstance] beginUpdating]; +#endif } [self checkCrowdNodeState]; 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 9fb2bf43b..a3a0c8fe8 100644 --- a/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift +++ b/DashWallet/Sources/UI/Home/Views/Home Header View/HomeHeaderView.swift @@ -43,9 +43,11 @@ final class HomeHeaderView: UIView { private(set) var stackView: UIStackView! // Available only in DashPay + #if DASHPAY private(set) var profileView: DashPayProfileView? private(set) var welcomeView: DWDPWelcomeView? private var isProfileReady = false + #endif weak var shortcutsDelegate: ShortcutsActionDelegate? { get { @@ -83,6 +85,7 @@ final class HomeHeaderView: UIView { welcomeView!.addTarget(self, action: #selector(joinDashPayAction), for: .touchUpInside) let views: [UIView] = [profileView!, balanceView, shortcutsView, syncView, welcomeView!] + updateProfileView() #else let views: [UIView] = [balanceView, shortcutsView, syncView] #endif @@ -123,7 +126,7 @@ final class HomeHeaderView: UIView { // }]; reloadBalance() - updateProfileView() + model.stateDidChage = { [weak self] state in self?.balanceView.state = state == .syncing ? .syncing : .default @@ -148,6 +151,7 @@ final class HomeHeaderView: UIView { delegate?.homeHeaderView(self, profileButtonAction: sender) } + #if DASHPAY @objc func joinDashPayAction() { delegate?.homeHeaderViewJoinDashPayAction(self) @@ -156,20 +160,6 @@ final class HomeHeaderView: UIView { updateProfileView() } - 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!.username = "madmax" @@ -188,6 +178,25 @@ final class HomeHeaderView: UIView { // delegate?.homeHeaderViewDidUpdateContents(self) } + #endif + + 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 hideSyncView() { syncView.isHidden = true delegate?.homeHeaderViewDidUpdateContents(self) diff --git a/DashWallet/Sources/UI/Menu/Settings/About/DWAboutModel.m b/DashWallet/Sources/UI/Menu/Settings/About/DWAboutModel.m index 0c5b2aa3c..57207c19a 100644 --- a/DashWallet/Sources/UI/Menu/Settings/About/DWAboutModel.m +++ b/DashWallet/Sources/UI/Menu/Settings/About/DWAboutModel.m @@ -159,11 +159,14 @@ - (NSString *)status { (int)[currentMasternodeList quorumsCountOfType:LLMQType_Llmqtype50_60]]; NSString *usernameString = @""; + +#if DASHPAY if ([DWGlobalOptions sharedInstance].dashpayUsername) { usernameString = [NSString stringWithFormat:NSLocalizedString(@"Current user: %@", nil), [DWGlobalOptions sharedInstance].dashpayUsername]; } - +#endif + NSArray *statusLines = @[ rateString, updatedString, blockString, peersString, dlPeerString, quorumsString, usernameString ]; return [statusLines componentsJoinedByString:@"\n"]; diff --git a/DashWallet/Sources/UI/Payments/PaymentModels/DWPaymentProcessor.m b/DashWallet/Sources/UI/Payments/PaymentModels/DWPaymentProcessor.m index 565356b54..8747f441e 100644 --- a/DashWallet/Sources/UI/Payments/PaymentModels/DWPaymentProcessor.m +++ b/DashWallet/Sources/UI/Payments/PaymentModels/DWPaymentProcessor.m @@ -18,7 +18,6 @@ #import "DWPaymentProcessor.h" #import "CurrencyExchanger_Objc.h" -#import "DWDPUserObject.h" #import "DWEnvironment.h" #import "DWGlobalOptions.h" #import "DWPaymentInput+Private.h" @@ -26,6 +25,10 @@ #import "DWPaymentInputBuilder.h" #import "DWPaymentOutput+Private.h" +#if DASHPAY +#import "DWDPUserObject.h" +#endif + NS_ASSUME_NONNULL_BEGIN #define LOCK @"\xF0\x9F\x94\x92" // unicode lock symbol U+1F512 (utf-8) @@ -134,9 +137,11 @@ - (void)processPaymentInput:(DWPaymentInput *)paymentInput { } } +#if DASHPAY if (requestIdentity) { paymentInput.userItem = [[DWDPUserObject alloc] initWithBlockchainIdentity:requestIdentity]; } +#endif } } diff --git a/DashWallet/Sources/UI/Payments/Receive/Models/DWBaseReceiveModel.m b/DashWallet/Sources/UI/Payments/Receive/Models/DWBaseReceiveModel.m index b5dbc553d..94eb76d3b 100644 --- a/DashWallet/Sources/UI/Payments/Receive/Models/DWBaseReceiveModel.m +++ b/DashWallet/Sources/UI/Payments/Receive/Models/DWBaseReceiveModel.m @@ -116,7 +116,12 @@ - (UIImage *)qrCodeImageWithRawQRImage:(UIImage *)rawQRImage hasAmount:(BOOL)has NSParameterAssert(overlayImage); CGSize size = overlayImage.size; - NSString *username = [DWGlobalOptions sharedInstance].dashpayUsername; + NSString *username; + +#if DASHPAY + username = [DWGlobalOptions sharedInstance].dashpayUsername; +#endif + const BOOL shouldDrawUser = username != nil; if (ShouldResizeLogoToSmall(hasAmount)) { diff --git a/DashWallet/Sources/UI/Payments/Receive/Models/DWReceiveModel.m b/DashWallet/Sources/UI/Payments/Receive/Models/DWReceiveModel.m index d0a4ba67f..f26b4c099 100644 --- a/DashWallet/Sources/UI/Payments/Receive/Models/DWReceiveModel.m +++ b/DashWallet/Sources/UI/Payments/Receive/Models/DWReceiveModel.m @@ -161,9 +161,10 @@ - (void)updateReceivingInfo { } paymentRequest.requestedFiatCurrencyCode = CurrencyExchangerObjcWrapper.localCurrencyCode; } - +#if DASHPAY paymentRequest.dashpayUsername = [DWGlobalOptions sharedInstance].dashpayUsername; - +#endif + UIImage *rawQRImage = nil; if (!hasAmount && [paymentRequest.data isEqual:appGroupOptions.receiveRequestData]) { NSData *qrImageData = appGroupOptions.receiveQRImageData; diff --git a/DashWallet/Sources/UI/Tx/Details/Model/TxDetailModel.swift b/DashWallet/Sources/UI/Tx/Details/Model/TxDetailModel.swift index 13122ccb1..71fa242ec 100644 --- a/DashWallet/Sources/UI/Tx/Details/Model/TxDetailModel.swift +++ b/DashWallet/Sources/UI/Tx/Details/Model/TxDetailModel.swift @@ -181,9 +181,13 @@ extension TxDetailModel { return [] } + #if DASHPAY let user = DWDPUserObject(blockchainIdentity: blockchainIdentity) let model = DWTitleDetailCellModel(title: title, userItem: user, copyableData: nil) return [model] + #endif + + return [] } private func destinationUsers(with title: String, font: UIFont) -> [DWTitleDetailItem] { @@ -191,11 +195,16 @@ extension TxDetailModel { return [] } + #if DASHPAY let user = DWDPUserObject(blockchainIdentity: blockchainIdentity) let model = DWTitleDetailCellModel(title: title, userItem: user, copyableData: nil) return [model] + #endif + + return [] } + func inputAddresses(with font: UIFont) -> [DWTitleDetailItem] { if !shouldDisplayInputAddresses { return [] diff --git a/DashWallet/Sources/UI/Views/SharedViews/TitleDetailCell/DWTitleDetailCellView.m b/DashWallet/Sources/UI/Views/SharedViews/TitleDetailCell/DWTitleDetailCellView.m index 3f801c223..0aaf0c027 100644 --- a/DashWallet/Sources/UI/Views/SharedViews/TitleDetailCell/DWTitleDetailCellView.m +++ b/DashWallet/Sources/UI/Views/SharedViews/TitleDetailCell/DWTitleDetailCellView.m @@ -17,7 +17,10 @@ #import "DWTitleDetailCellView.h" +#if DASHPAY #import "DWDPSmallContactView.h" +#endif + #import "DWUIKit.h" #import @@ -31,7 +34,9 @@ @interface DWTitleDetailCellView () @property (readonly, nonatomic, strong) UILabel *titleLabel; @property (readonly, nonatomic, strong) UILabel *detailLabel; +#if DASHPAY @property (readonly, nonatomic, strong) DWDPSmallContactView *contactView; +#endif @property (readonly, nonatomic, strong) UIStackView *stackView; @property (readonly, nonatomic, strong) CALayer *separatorLayer; @@ -84,11 +89,14 @@ - (void)titleDetailCellViewCommonInit { detailLabel.textColor = [UIColor dw_secondaryTextColor]; _detailLabel = detailLabel; +#if DASHPAY DWDPSmallContactView *contactView = [[DWDPSmallContactView alloc] initWithFrame:CGRectZero]; contactView.translatesAutoresizingMaskIntoConstraints = NO; _contactView = contactView; - UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ titleLabel, detailLabel, contactView ]]; +#else + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ titleLabel, detailLabel ]]; +#endif stackView.translatesAutoresizingMaskIntoConstraints = NO; stackView.axis = UILayoutConstraintAxisHorizontal; stackView.alignment = UIStackViewAlignmentCenter; @@ -169,6 +177,7 @@ - (void)setModel:(nullable id)model { self.detailLabel.hidden = YES; } +#if DASHPAY if (model.userItem) { self.contactView.hidden = NO; self.contactView.item = model.userItem; @@ -176,7 +185,8 @@ - (void)setModel:(nullable id)model { else { self.contactView.hidden = YES; } - +#endif + self.detailLabel.textAlignment = model.detailAlignment; } From 4e234cdad7456093b8edeae93e963db2451d6cf6 Mon Sep 17 00:00:00 2001 From: tikhop Date: Wed, 21 Jun 2023 19:57:37 +0400 Subject: [PATCH 008/123] feat(ui): Show user profile on more screen --- .../UserProfile/DWCurrentUserProfileView.m | 50 ++++++--- .../UserProfile/DWUserProfileContainerView.h | 3 +- .../UserProfile/DWUserProfileContainerView.m | 44 ++++---- .../Menu/Main/Views/DWMainMenuContentView.h | 15 ++- .../Menu/Main/Views/DWMainMenuContentView.m | 100 ++++++++++++++---- 5 files changed, 153 insertions(+), 59 deletions(-) diff --git a/DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.m b/DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.m index 41f315e21..36064e0bf 100644 --- a/DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.m +++ b/DashPay/Presentation/Profile/UserProfile/DWCurrentUserProfileView.m @@ -116,21 +116,23 @@ - (instancetype)initWithFrame:(CGRect)frame { - (void)setBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { _blockchainIdentity = blockchainIdentity; - __weak typeof(self) weakSelf = self; - [self.avatarImageView dw_setAvatarWithURLString:blockchainIdentity.avatarPath - completion:^(UIImage *_Nullable image) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (image) { - strongSelf.avatarImageView.image = image; - } - else { - strongSelf.avatarImageView.image = [UIImage imageNamed:@"dp_current_user_placeholder"]; - } - }]; + self.avatarImageView.image = [UIImage imageNamed:@"dp_current_user_placeholder"]; + +// __weak typeof(self) weakSelf = self; +// [self.avatarImageView dw_setAvatarWithURLString:blockchainIdentity.avatarPath +// completion:^(UIImage *_Nullable image) { +// __strong typeof(weakSelf) strongSelf = weakSelf; +// if (!strongSelf) { +// return; +// } +// +// if (image) { +// strongSelf.avatarImageView.image = image; +// } +// else { +// strongSelf.avatarImageView.image = [UIImage imageNamed:@"dp_current_user_placeholder"]; +// } +// }]; [self reloadAttributedData]; } @@ -152,7 +154,23 @@ - (void)editProfileButtonAction:(UIButton *)sender { #pragma mark - Private - (void)reloadAttributedData { - self.infoLabel.attributedText = [self.blockchainIdentity dw_asTitleSubtitle]; + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + NSString *title = nil; + NSString *subtitle = @"Max"; + + if (title != nil) { + [result appendAttributedString:[[NSAttributedString alloc] + initWithString:title + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleTitle3], + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + }]]; + } + + [result endEditing]; + + self.infoLabel.attributedText = result; //[self.blockchainIdentity dw_asTitleSubtitle]; } @end diff --git a/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.h b/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.h index 2530ac46e..c6c1ad298 100644 --- a/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.h +++ b/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.h @@ -15,7 +15,6 @@ // limitations under the License. // -#import #import "DWCurrentUserProfileView.h" @@ -23,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN @class DWCurrentUserProfileModel; -@interface DWUserProfileContainerView : KVOUIView +@interface DWUserProfileContainerView : UIView @property (nonatomic, strong) DWCurrentUserProfileModel *userModel; @property (nullable, nonatomic, weak) id delegate; diff --git a/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.m b/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.m index 6cee51d73..b20752511 100644 --- a/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.m +++ b/DashPay/Presentation/Profile/UserProfile/DWUserProfileContainerView.m @@ -87,33 +87,33 @@ - (instancetype)initWithFrame:(CGRect)frame { constant:padding], ]]; - [self mvvm_observe:DW_KEYPATH(self, userModel.updateModel.state) - with:^(typeof(self) self, id value) { - switch (self.userModel.updateModel.state) { - case DWDPUpdateProfileModelState_Ready: - [self update]; - self.profileView.hidden = NO; - self.updatingView.hidden = YES; - self.errorView.hidden = YES; - break; - case DWDPUpdateProfileModelState_Loading: - self.profileView.hidden = YES; - self.updatingView.hidden = NO; - self.errorView.hidden = YES; - break; - case DWDPUpdateProfileModelState_Error: - self.profileView.hidden = YES; - self.updatingView.hidden = YES; - self.errorView.hidden = NO; - break; - } - }]; +// [self mvvm_observe:DW_KEYPATH(self, userModel.updateModel.state) +// with:^(typeof(self) self, id value) { +// switch (self.userModel.updateModel.state) { +// case DWDPUpdateProfileModelState_Ready: +// [self update]; +// self.profileView.hidden = NO; +// self.updatingView.hidden = YES; +// self.errorView.hidden = YES; +// break; +// case DWDPUpdateProfileModelState_Loading: +// self.profileView.hidden = YES; +// self.updatingView.hidden = NO; +// self.errorView.hidden = YES; +// break; +// case DWDPUpdateProfileModelState_Error: +// self.profileView.hidden = YES; +// self.updatingView.hidden = YES; +// self.errorView.hidden = NO; +// break; +// } +// }]; } return self; } - (void)update { - self.profileView.blockchainIdentity = self.userModel.blockchainIdentity; + self.profileView.blockchainIdentity = nil; //self.userModel.blockchainIdentity; } #pragma mark - DWErrorUpdatingUserProfileViewDelegate diff --git a/DashWallet/Sources/UI/Menu/Main/Views/DWMainMenuContentView.h b/DashWallet/Sources/UI/Menu/Main/Views/DWMainMenuContentView.h index fd52906a5..f847017c1 100644 --- a/DashWallet/Sources/UI/Menu/Main/Views/DWMainMenuContentView.h +++ b/DashWallet/Sources/UI/Menu/Main/Views/DWMainMenuContentView.h @@ -19,6 +19,11 @@ #import "DWMainMenuItem.h" +#if DASHPAY +#import "DWCurrentUserProfileModel.h" +#import "DWDashPayReadyProtocol.h" +#endif + NS_ASSUME_NONNULL_BEGIN @class DWMainMenuModel; @@ -28,6 +33,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)mainMenuContentView:(DWMainMenuContentView *)view didSelectMenuItem:(id)item; +#if DASHPAY +- (void)mainMenuContentView:(DWMainMenuContentView *)view joinDashPayAction:(UIButton *)sender; +- (void)mainMenuContentView:(DWMainMenuContentView *)view showQRAction:(UIButton *)sender; +- (void)mainMenuContentView:(DWMainMenuContentView *)view editProfileAction:(UIButton *)sender; +#endif + @end @interface DWMainMenuContentView : UIView @@ -38,7 +49,9 @@ NS_ASSUME_NONNULL_BEGIN - (nullable instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; -#ifdef DASHPAY +#if DASHPAY +@property (nonatomic, strong) DWCurrentUserProfileModel *userModel; +@property (nonatomic, strong) id dashPayReady; - (void)updateUserHeader; #endif diff --git a/DashWallet/Sources/UI/Menu/Main/Views/DWMainMenuContentView.m b/DashWallet/Sources/UI/Menu/Main/Views/DWMainMenuContentView.m index 3560bee2f..063624fe7 100644 --- a/DashWallet/Sources/UI/Menu/Main/Views/DWMainMenuContentView.m +++ b/DashWallet/Sources/UI/Menu/Main/Views/DWMainMenuContentView.m @@ -22,12 +22,25 @@ #import "DWSharedUIConstants.h" #import "DWUIKit.h" +#if DASHPAY +#import "DWUserProfileContainerView.h" +#import "DWDPWelcomeMenuView.h" +#import "DWDashPayReadyProtocol.h" +#endif + NS_ASSUME_NONNULL_BEGIN @interface DWMainMenuContentView () @property (nonatomic, strong) UITableView *tableView; +#if DASHPAY +@property (nonatomic, strong) DWUserProfileContainerView *headerView; +@property (nonatomic, strong) DWDPWelcomeMenuView *joinHeaderView; + +@property (assign, nonatomic) bool hasUsername; +#endif + @end @implementation DWMainMenuContentView @@ -49,6 +62,18 @@ - (instancetype)initWithFrame:(CGRect)frame { [self addSubview:tableView]; _tableView = tableView; +#if DASHPAY + DWUserProfileContainerView *headerView = [[DWUserProfileContainerView alloc] initWithFrame:CGRectZero]; + headerView.delegate = self; + _headerView = headerView; + + DWDPWelcomeMenuView *joinHeaderView = [[DWDPWelcomeMenuView alloc] initWithFrame:CGRectZero]; + [joinHeaderView.joinButton addTarget:self + action:@selector(joinButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + _joinHeaderView = joinHeaderView; +#endif + NSString *cellId = DWMainMenuTableViewCell.dw_reuseIdentifier; UINib *nib = [UINib nibWithNibName:cellId bundle:nil]; NSParameterAssert(nib); @@ -63,24 +88,19 @@ - (void)setModel:(DWMainMenuModel *)model { [self.tableView reloadData]; } -- (void)updateUserHeader { - //[self.userModel update]; - [self updateHeader]; -} - -- (void)updateHeader { - //TODO: DashPay -// UIView *header = nil; -// if (self.dashPayReady.isDashPayReady) { -// header = self.joinHeaderView; -// } -// else if (self.userModel.blockchainIdentity != nil) { -// [self.headerView update]; -// header = self.headerView; -// } -// -// self.tableView.tableHeaderView = header; -// [self setNeedsLayout]; +- (void)layoutSubviews { + [super layoutSubviews]; + +#if DASHPAY + 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; + } + } +#endif } #pragma mark - UITableViewDataSource @@ -114,6 +134,50 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [self.delegate mainMenuContentView:self didSelectMenuItem:menuItem]; } +#if DASHPAY + +- (void)updateUserHeader { + //[self.userModel update]; //TODO: DashPay + [self updateHeader]; +} + +- (void)updateHeader { + UIView *header = nil; + if(self.hasUsername) { + header = self.headerView; + [self.headerView update]; + } else { + header = self.joinHeaderView; + } + +// if (self.dashPayReady.isDashPayReady) { + +// } +// else if (self.userModel.blockchainIdentity != nil) { +// [self.headerView update]; +// header = self.headerView; +// } + + self.tableView.tableHeaderView = header; + [self setNeedsLayout]; +} + +#pragma mark - DWCurrentUserProfileViewDelegate + +- (void)currentUserProfileView:(DWCurrentUserProfileView *)view showQRAction:(UIButton *)sender { + [self.delegate mainMenuContentView:self showQRAction:sender]; +} + +- (void)currentUserProfileView:(DWCurrentUserProfileView *)view editProfileAction:(UIButton *)sender { + [self.delegate mainMenuContentView:self editProfileAction:sender]; +} + +- (void)joinButtonAction:(UIButton *)sender { + self.hasUsername = !_hasUsername; + [self updateHeader]; + //[self.delegate mainMenuContentView:self joinDashPayAction:sender]; +} +#endif @end NS_ASSUME_NONNULL_END From a6729235040e91e152729a28d01f694b716cb749 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 10 Jul 2023 16:29:12 +0400 Subject: [PATCH 009/123] fix: Project structure --- DashWallet.xcodeproj/project.pbxproj | 3 +- .../Contacts/Base/DWSearchViewController.h | 37 ++ .../Contacts/Base/DWSearchViewController.m | 186 +++++++ ...ontactsContentViewController+DWProtected.h | 49 ++ .../DWBaseContactsContentViewController.h | 63 +++ .../DWBaseContactsContentViewController.m | 523 ++++++++++++++++++ ...DWBaseContactsViewController+DWProtected.h | 43 ++ .../Contacts/DWBaseContactsViewController.h | 40 ++ .../Contacts/DWBaseContactsViewController.m | 204 +++++++ .../DWContactsContentViewController.h | 45 ++ .../DWContactsContentViewController.m | 50 ++ .../Contacts/DWContactsViewController.h | 45 ++ .../Contacts/DWContactsViewController.m | 245 ++++++++ .../Contacts/DWRootContactsViewController.h | 4 +- .../Children/DWSearchStateViewController.h | 44 ++ .../Children/DWSearchStateViewController.m | 410 ++++++++++++++ .../DWUserSearchResultViewController.h | 50 ++ .../DWUserSearchResultViewController.m | 118 ++++ .../GlobalSearch/DWUserSearchViewController.h | 40 ++ .../GlobalSearch/DWUserSearchViewController.m | 200 +++++++ .../GlobalSearch/Model/DWUserSearchModel.h | 54 ++ .../GlobalSearch/Model/DWUserSearchModel.m | 205 +++++++ .../Models/DWBaseContactsModel+DWProtected.h | 47 ++ .../Contacts/Models/DWBaseContactsModel.h | 54 ++ .../Contacts/Models/DWBaseContactsModel.m | 163 ++++++ .../DashPay/Contacts/Models/DWContactsModel.h | 35 ++ .../DashPay/Contacts/Models/DWContactsModel.m | 68 +++ .../Models/DWContactsSortModeProtocol.h | 32 ++ .../Models/DataSource/DWContactsDataSource.h | 41 ++ .../DataSource/DWContactsDataSourceObject.h | 37 ++ .../DataSource/DWContactsDataSourceObject.m | 91 +++ .../DWContactsSearchDataSourceObject.h | 39 ++ .../DWContactsSearchDataSourceObject.m | 105 ++++ .../DWContactsFetchedDataSource.h | 35 ++ .../DWContactsFetchedDataSource.m | 49 ++ .../DWFetchedResultsDataSource.h | 56 ++ .../DWFetchedResultsDataSource.m | 198 +++++++ .../DWIncomingFetchedDataSource.h | 35 ++ .../DWIncomingFetchedDataSource.m | 58 ++ .../Placeholders/DWNoContactsViewController.m | 2 +- .../DWRequestsContentViewController.h | 26 + .../DWRequestsContentViewController.m | 30 + .../Requests/DWRequestsViewController.h | 39 ++ .../Requests/DWRequestsViewController.m | 72 +++ .../Requests/Models/DWRequestsModel.h | 33 ++ .../Requests/Models/DWRequestsModel.m | 43 ++ .../Views/DWContactsSearchInfoHeaderView.h | 28 + .../Views/DWContactsSearchInfoHeaderView.m | 55 ++ .../Contacts/Views/DWTitleActionHeaderView.h | 39 ++ .../Contacts/Views/DWTitleActionHeaderView.m | 72 +++ .../Views/DWTitleActionHeaderView.xib | 82 +++ .../Sources/UI/DashPay/DWDashPayConstants.h | 28 + .../Sources/UI/DashPay/DWDashPayConstants.m | 28 + .../Error/DWNetworkErrorViewController.m | 107 ---- .../DWExploreTestnetViewController.h} | 4 +- .../Explore/DWExploreTestnetViewController.m | 133 +++++ .../Explore/Views/DWExploreHeaderView.h | 30 + .../Explore/Views/DWExploreHeaderView.m | 119 ++++ .../Views/DWExploreTestnetContentsView.h | 26 + .../Views/DWExploreTestnetContentsView.m | 84 +++ .../DashPay/Global/DWDashPayContactsActions.h | 36 ++ .../DashPay/Global/DWDashPayContactsActions.m | 112 ++++ .../DashPay/Global/DWDashPayContactsUpdater.h | 43 ++ .../DashPay/Global/DWDashPayContactsUpdater.m | 160 ++++++ .../Notifications/DWNotificationsData.h | 37 ++ .../Notifications/DWNotificationsData.m | 45 ++ .../DWNotificationsFetchedDataSource.h | 35 ++ .../DWNotificationsFetchedDataSource.m | 48 ++ .../Notifications/DWNotificationsProvider.h | 41 ++ .../Notifications/DWNotificationsProvider.m | 209 +++++++ .../DWConfirmInvitationContentView.m | 2 +- .../Invites/DWSendInviteFlowController.m | 1 - .../History/Views/DWCreateInvitationButton.m | 2 + .../BaseInvitationViewController.swift | 9 +- .../Views/DWSuccessInvitationView.m | 2 +- .../Items/Factory/DWDPContactsItemsFactory.h | 31 ++ .../Items/Factory/DWDPContactsItemsFactory.m | 52 ++ .../Items/Factory/DWDPSearchItemsFactory.h | 33 ++ .../Items/Factory/DWDPSearchItemsFactory.m | 47 ++ .../DashPay/Items/Objects/DWDPContactObject.h | 36 ++ .../DashPay/Items/Objects/DWDPContactObject.m | 72 +++ .../Objects/DWDPEstablishedContactObject.h | 32 ++ .../Objects/DWDPEstablishedContactObject.m | 33 ++ .../Items/Objects/DWDPIncomingRequestObject.h | 38 ++ .../Items/Objects/DWDPIncomingRequestObject.m | 79 +++ .../Objects/DWDPNewIncomingRequestObject.h | 28 + .../Objects/DWDPNewIncomingRequestObject.m | 24 + .../Items/Objects/DWDPPendingRequestObject.h | 27 + .../Items/Objects/DWDPPendingRequestObject.m | 28 + .../DWDPRespondedIncomingRequestObject.h | 27 + .../DWDPRespondedIncomingRequestObject.m | 24 + .../UI/DashPay/Items/Objects/DWDPTxObject.h | 36 ++ .../UI/DashPay/Items/Objects/DWDPTxObject.m | 95 ++++ .../UI/DashPay/Items/Objects/DWDPUserObject.h | 39 ++ .../UI/DashPay/Items/Objects/DWDPUserObject.m | 87 +++ .../DWDPAcceptedRequestNotificationObject.h | 35 ++ .../DWDPAcceptedRequestNotificationObject.m | 78 +++ ...DWDPEstablishedContactNotificationObject.h | 36 ++ ...DWDPEstablishedContactNotificationObject.m | 80 +++ ...DWDPNewIncomingRequestNotificationObject.h | 29 + ...DWDPNewIncomingRequestNotificationObject.m | 45 ++ .../DWDPOutgoingRequestNotificationObject.h | 35 ++ .../DWDPOutgoingRequestNotificationObject.m | 83 +++ .../DashPay/Items/Protocols/DWDPBasicItem.h | 36 ++ .../Items/Protocols/DWDPBasicUserItem.h | 33 ++ .../Protocols/DWDPEstablishedContactItem.h} | 4 +- .../Items/Protocols/DWDPIncomingRequestItem.h | 26 + .../Protocols/DWDPNewIncomingRequestItem.h | 43 ++ .../Items/Protocols/DWDPNotificationItem.h | 28 + .../Items/Protocols/DWDPPendingRequestItem.h | 26 + .../Protocols/DWDPRespondedRequestItem.h | 26 + .../UI/DashPay/Items/Protocols/DWDPTxItem.h | 32 ++ .../DWDPBlockchainIdentityBackedItem.h | 30 + .../DWDPDashpayUserBackedItem.h | 30 + .../DWDPFriendRequestBackedItem.h | 30 + .../DWDPGenericContactRequestItemView.h | 33 ++ .../DWDPGenericContactRequestItemView.m | 182 ++++++ .../ContentViews/DWDPGenericImageItemView.h | 28 + .../ContentViews/DWDPGenericImageItemView.m | 58 ++ .../Views/ContentViews/DWDPGenericItemView.h | 34 ++ .../Views/ContentViews/DWDPGenericItemView.m | 131 +++++ .../ContentViews/DWDPGenericStatusItemView.h | 28 + .../ContentViews/DWDPGenericStatusItemView.m | 68 +++ .../Items/Views/ContentViews/DWDPTxItemView.h | 28 + .../Items/Views/ContentViews/DWDPTxItemView.m | 67 +++ .../UI/DashPay/Items/Views/DWDPBasicCell.h | 51 ++ .../UI/DashPay/Items/Views/DWDPBasicCell.m | 222 ++++++++ .../DashPay/Items/Views/DWDPImageStatusCell.h | 26 + .../DashPay/Items/Views/DWDPImageStatusCell.m | 49 ++ .../Items/Views/DWDPIncomingRequestCell.h | 31 ++ .../Items/Views/DWDPIncomingRequestCell.m | 78 +++ .../DashPay/Items/Views/DWDPTextStatusCell.h | 26 + .../DashPay/Items/Views/DWDPTextStatusCell.m | 49 ++ .../UI/DashPay/Items/Views/DWDPTxListCell.h | 26 + .../UI/DashPay/Items/Views/DWDPTxListCell.m | 49 ++ .../Views/UICollectionView+DWDPItemDequeue.h | 31 ++ .../Views/UICollectionView+DWDPItemDequeue.m | 69 +++ .../UI/DashPay/Items/Views/UIFont+DWDPItem.h | 29 + .../UI/DashPay/Items/Views/UIFont+DWDPItem.m | 32 ++ .../Cells/DWNoNotificationsCell.h | 28 + .../Cells/DWNoNotificationsCell.m | 109 ++++ .../Cells/DWNotificationsInvitationCell.m | 2 +- .../Notifications/DWListCollectionLayout.h | 29 + .../Notifications/DWListCollectionLayout.m | 41 ++ .../DWNotificationsViewController.h} | 18 +- .../DWNotificationsViewController.m | 385 +++++++++++++ .../Models/DWNotificationsModel.h | 47 ++ .../Models/DWNotificationsModel.m | 92 +++ .../DWModalUserProfileViewController.h | 41 ++ .../DWModalUserProfileViewController.m | 74 +++ .../Profile/DWUserProfileViewController.h | 43 ++ .../Profile/DWUserProfileViewController.m | 498 +++++++++++++++++ .../Profile/Model/DWUserProfileModel.h | 81 +++ .../Profile/Model/DWUserProfileModel.m | 292 ++++++++++ .../DWProfileTxsFetchedDataSource.h | 34 ++ .../DWProfileTxsFetchedDataSource.m | 85 +++ .../DataSource/DWUserProfileDataSource.h | 32 ++ .../DWUserProfileDataSourceObject.h | 41 ++ .../DWUserProfileDataSourceObject.m | 216 ++++++++ .../DWStretchyHeaderListCollectionLayout.h | 26 + .../DWStretchyHeaderListCollectionLayout.m | 70 +++ .../Views/DWUserProfileContactActionsCell.h | 41 ++ .../Views/DWUserProfileContactActionsCell.m | 226 ++++++++ .../Profile/Views/DWUserProfileHeaderView.h | 41 ++ .../Profile/Views/DWUserProfileHeaderView.m | 362 ++++++++++++ .../Views/DWUserProfileNavigationTitleView.h | 35 ++ .../Views/DWUserProfileNavigationTitleView.m | 106 ++++ .../Views/DWUserProfileSendRequestCell.h | 41 ++ .../Views/DWUserProfileSendRequestCell.m | 342 ++++++++++++ .../DWFirstUsernameSymbolValidationRule.m | 44 -- .../Models/DWLengthUsernameValidationRule.m | 41 -- .../UI/DashPay/Views/DWDPSmallContactView.h | 30 + .../UI/DashPay/Views/DWDPSmallContactView.m | 98 ++++ .../UI/DashPay/Views/DWDashPayAnimationView.h | 29 + .../UI/DashPay/Views/DWDashPayAnimationView.m | 368 ++++++++++++ .../UI/DashPay/Views/UIColor+DWDashPay.h | 28 + .../UI/DashPay/Views/UIColor+DWDashPay.m | 40 ++ .../Welcome/DWDPWelcomeViewController.h | 2 +- .../Welcome/DWInvitationFlowViewController.h | 2 +- .../Welcome/DWInvitationFlowViewController.m | 2 +- .../GetStarted/DWGetStartedViewController.h | 3 +- 181 files changed, 12662 insertions(+), 224 deletions(-) create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController+DWProtected.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController+DWProtected.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel+DWProtected.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsSortModeProtocol.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSource.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.h create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.m create mode 100644 DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.xib create mode 100644 DashWallet/Sources/UI/DashPay/DWDashPayConstants.h create mode 100644 DashWallet/Sources/UI/DashPay/DWDashPayConstants.m delete mode 100644 DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.m rename DashWallet/Sources/UI/DashPay/{Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h => Explore/DWExploreTestnetViewController.h} (86%) create mode 100644 DashWallet/Sources/UI/DashPay/Explore/DWExploreTestnetViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreHeaderView.h create mode 100644 DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreHeaderView.m create mode 100644 DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreTestnetContentsView.h create mode 100644 DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreTestnetContentsView.m create mode 100644 DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.h create mode 100644 DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.m create mode 100644 DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.h create mode 100644 DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.m create mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.h create mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.m create mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.h create mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.m create mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.h create mode 100644 DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicUserItem.h rename DashWallet/Sources/UI/DashPay/{Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h => Items/Protocols/DWDPEstablishedContactItem.h} (86%) create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPIncomingRequestItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNewIncomingRequestItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNotificationItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPPendingRequestItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPRespondedRequestItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPTxItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPBlockchainIdentityBackedItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPDashpayUserBackedItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPFriendRequestBackedItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.m create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.h create mode 100644 DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.m create mode 100644 DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.m create mode 100644 DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.h create mode 100644 DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.m rename DashWallet/Sources/UI/DashPay/{Error/DWNetworkErrorViewController.h => Notifications/DWNotificationsViewController.h} (69%) create mode 100644 DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.h create mode 100644 DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSource.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.m create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.h create mode 100644 DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m delete mode 100644 DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m create mode 100644 DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.h create mode 100644 DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.m create mode 100644 DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.h create mode 100644 DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.m create mode 100644 DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.h create mode 100644 DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.m diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index d60e09b55..d548d24cb 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -6374,8 +6374,7 @@ C943B48E2A40A54600AF23C5 /* Global */, C943B49E2A40A54600AF23C5 /* Notifications */, ); - name = DashPay; - path = "../../../../DashWallet-Platform/DashWallet/Sources/UI/DashPay"; + path = DashPay; sourceTree = ""; }; C943B3502A40A54500AF23C5 /* Contacts */ = { diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.h new file mode 100644 index 000000000..f87ece5e3 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.h @@ -0,0 +1,37 @@ +// +// 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 UIKit; + +#import "dashwallet-Swift.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWSearchViewController : UIViewController + +@property (readonly, nonatomic, strong) UISearchBar *searchBar; +@property (readonly, nonatomic, strong) UIView *contentView; + +@property (nonatomic, assign) BOOL disableSearchBarBecomesFirstResponderOnFirstAppearance; + +- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height + animationDuration:(NSTimeInterval)animationDuration + animationCurve:(UIViewAnimationCurve)animationCurve; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.m new file mode 100644 index 000000000..c944ed3c5 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Base/DWSearchViewController.m @@ -0,0 +1,186 @@ +// +// 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 "DWSearchViewController.h" + +#import + +#import "DWUIKit.h" +#import "UISearchBar+DWAdditions.h" +#import "UIView+DWRecursiveSubview.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWSearchViewController () + +@property (nonatomic, assign) BOOL requiresNoNavigationBar; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWSearchViewController + +@synthesize searchBar = _searchBar; +@synthesize contentView = _contentView; + +@synthesize requiresNoNavigationBar = _requiresNoNavigationBar; + +- (BOOL)requiresNoNavigationBar { + return _requiresNoNavigationBar; +} + +- (void)setRequiresNoNavigationBar:(BOOL)requiresNoNavigationBar { + _requiresNoNavigationBar = requiresNoNavigationBar; + + [self.navigationController setNavigationBarHidden:requiresNoNavigationBar animated:YES]; + [self setNeedsStatusBarAppearanceUpdate]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + [self.view addSubview:self.searchBar]; + [self.view addSubview:self.contentView]; + + [NSLayoutConstraint activateConstraints:@[ + [self.searchBar.topAnchor constraintEqualToAnchor:self.view.layoutMarginsGuide.topAnchor], + [self.searchBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:self.searchBar.trailingAnchor], + + [self.contentView.topAnchor constraintEqualToAnchor:self.searchBar.bottomAnchor], + [self.contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], + [self.view.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor], + ]]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + // pre-layout view to avoid undesired animation if the keyboard is shown while appearing + [self.view layoutIfNeeded]; + [self ka_startObservingKeyboardNotifications]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [self ka_stopObservingKeyboardNotifications]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + // Activate Search Bar initially + if (!self.disableSearchBarBecomesFirstResponderOnFirstAppearance) { + [self.searchBar becomeFirstResponder]; + self.disableSearchBarBecomesFirstResponderOnFirstAppearance = YES; + } +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return self.requiresNoNavigationBar ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent; +} + +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + + if ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion >= 13) { + // hide semi-transparent overlays above UITextField in UISearchBar to achive basic white color + UISearchBar *searchBar = self.searchBar; + UITextField *searchTextField = (UITextField *)[searchBar dw_findSubviewOfClass:UITextField.class]; + UIView *searchTextFieldBackground = searchTextField.subviews.firstObject; + [searchTextFieldBackground.subviews makeObjectsPerformSelector:@selector(setHidden:) withObject:@YES]; + } +} + +#pragma mark - UISearchBarDelegate + +- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { + [searchBar setShowsCancelButton:YES animated:YES]; + self.requiresNoNavigationBar = YES; +} + +- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar { + dispatch_async(dispatch_get_main_queue(), ^{ + if (searchBar.showsCancelButton) { + [searchBar dw_enableCancelButton]; + } + }); +} + +- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { + // To be overriden +} + +- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { + [searchBar resignFirstResponder]; +} + +- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { + [searchBar setShowsCancelButton:NO animated:YES]; + [searchBar resignFirstResponder]; + + self.requiresNoNavigationBar = NO; +} + +- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { + return YES; +} + +#pragma mark - Keyboard + +- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height + animationDuration:(NSTimeInterval)animationDuration + animationCurve:(UIViewAnimationCurve)animationCurve { + // To be overriden +} + +#pragma mark - Private + +- (UISearchBar *)searchBar { + if (_searchBar == nil) { + UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectZero]; + searchBar.translatesAutoresizingMaskIntoConstraints = NO; + searchBar.delegate = self; + searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone; + searchBar.barTintColor = [UIColor dw_secondaryBackgroundColor]; + searchBar.searchBarStyle = UISearchBarStyleMinimal; + searchBar.tintColor = [UIColor dw_dashBlueColor]; + UITextField *searchTextField = (UITextField *)[searchBar dw_findSubviewOfClass:UITextField.class]; + searchTextField.tintColor = [UIColor dw_dashBlueColor]; + searchTextField.textColor = [UIColor dw_darkTitleColor]; + searchTextField.backgroundColor = [UIColor dw_backgroundColor]; + _searchBar = searchBar; + } + return _searchBar; +} + +- (UIView *)contentView { + if (_contentView == nil) { + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + _contentView = contentView; + } + return _contentView; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController+DWProtected.h b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController+DWProtected.h new file mode 100644 index 000000000..d87b86e27 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController+DWProtected.h @@ -0,0 +1,49 @@ +// +// 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 "DWBaseContactsContentViewController.h" + +#import "DWContactsSearchInfoHeaderView.h" +#import "DWContactsSearchPlaceholderView.h" +#import "DWFilterHeaderView.h" +#import "DWGlobalMatchFailedHeaderView.h" +#import "DWGlobalMatchHeaderView.h" +#import "DWTitleActionHeaderView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWBaseContactsContentViewController () + +@property (readonly, nonatomic, strong) id payModel; +@property (readonly, nonatomic, strong) id dataProvider; + +@property (null_resettable, nonatomic, strong) UICollectionView *collectionView; + +@property (null_resettable, nonatomic, strong) DWContactsSearchPlaceholderView *measuringSearchPlaceholderView; +@property (null_resettable, nonatomic, strong) DWContactsSearchInfoHeaderView *measuringSearchHeaderView; +@property (null_resettable, nonatomic, strong) DWTitleActionHeaderView *measuringRequestsHeaderView; +@property (null_resettable, nonatomic, strong) DWFilterHeaderView *measuringContactsHeaderView; +@property (null_resettable, nonatomic, strong) DWGlobalMatchHeaderView *measuringGlobalMatchHeaderView; +@property (null_resettable, nonatomic, strong) DWGlobalMatchFailedHeaderView *measuringGlobalMatchFailedHeaderView; + +@property (null_resettable, nonatomic, copy) NSAttributedString *searchHeaderTitle; +@property (null_resettable, nonatomic, copy) NSString *requestsHeaderTitle; +@property (null_resettable, nonatomic, copy) NSAttributedString *contactsHeaderFilterButtonTitle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.h new file mode 100644 index 000000000..b6dc485ee --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.h @@ -0,0 +1,63 @@ +// +// 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 + +#import "DWContactsDataSource.h" +#import "DWDPBasicUserItem.h" +#import "DWDPNewIncomingRequestItem.h" +#import "DWPayModelProtocol.h" +#import "DWTransactionListDataProviderProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWBaseContactsContentViewController; + +@protocol DWBaseContactsContentViewControllerDelegate + +- (void)baseContactsContentViewController:(DWBaseContactsContentViewController *)controller + didSelect:(id)item + indexPath:(NSIndexPath *)indexPath; + +@end + +@interface DWBaseContactsContentViewController : UIViewController + +@property (readonly, null_resettable, nonatomic, strong) UICollectionView *collectionView; + +@property (nonatomic, assign, getter=isContactsScreen) BOOL contactsScreen; +@property (readonly, nonatomic, assign) NSUInteger maxVisibleContactRequestsCount; + +@property (nullable, nonatomic, weak) id delegate; +@property (nullable, nonatomic, weak) id itemsDelegate; +@property (nonatomic, strong) id dataSource; + +@property (nullable, nonatomic, copy) NSArray> *matchedItems; +@property (nonatomic, assign) BOOL matchFailed; + +- (instancetype)initWithPayModel:(id)payModel + dataProvider:(id)dataProvider NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE; +- (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 + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.m new file mode 100644 index 000000000..5eeaf2d51 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsContentViewController.m @@ -0,0 +1,523 @@ +// +// 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 "DWBaseContactsContentViewController+DWProtected.h" + +#import "DWDPBasicCell.h" +#import "DWListCollectionLayout.h" +#import "DWSharedUIConstants.h" +#import "DWUIKit.h" +#import "UICollectionView+DWDPItemDequeue.h" +#import "DWFilterHeaderView.h" + +typedef NS_ENUM(NSInteger, DWContactsContentSection) { + DWContactsContentSectionSearch, + DWContactsContentSectionRequests, + DWContactsContentSectionContacts, + DWContactsContentSectionGlobalMatched, + CountOfDWContactsContentSections, +}; + +static NSString *const DummyCellId = @"DummyCellId"; + +@implementation DWBaseContactsContentViewController + +- (instancetype)initWithPayModel:(id)payModel + dataProvider:(id)dataProvider { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _payModel = payModel; + _dataProvider = dataProvider; + } + return self; +} + +- (NSUInteger)maxVisibleContactRequestsCount { + return NSUIntegerMax; +} + +- (void)setDataSource:(id)dataSource { + _dataSource = dataSource; + + self.searchHeaderTitle = nil; + self.requestsHeaderTitle = nil; + self.contactsHeaderFilterButtonTitle = nil; + + // TODO: DP polishing: diff reload + CGPoint contentOffset = self.collectionView.contentOffset; + [self.collectionView reloadData]; + + if (self.view.window != nil) { + [self.collectionView layoutIfNeeded]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.collectionView setContentOffset:contentOffset animated:NO]; + }); + } +} + +- (void)setMatchedItems:(NSArray> *)matchedItems { + if (self.matchFailed) { + _matchedItems = @[ (id)DummyCellId ]; + } + else { + _matchedItems = matchedItems; + } + + // TODO: DP polishing: diff reload + CGPoint contentOffset = self.collectionView.contentOffset; + [self.collectionView reloadData]; + + if (self.view.window != nil) { + [self.collectionView layoutIfNeeded]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.collectionView setContentOffset:contentOffset animated:NO]; + }); + } +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + [self.view addSubview:self.collectionView]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + NSParameterAssert(self.delegate); +} + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return CountOfDWContactsContentSections; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + if (section == DWContactsContentSectionSearch) { + return 0; + } + else if (section == DWContactsContentSectionRequests) { + return MIN(self.dataSource.requestsCount, self.maxVisibleContactRequestsCount); + } + else if (section == DWContactsContentSectionContacts) { + return self.dataSource.contactsCount; + } + else { + return self.matchedItems.count; + } +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == DWContactsContentSectionGlobalMatched && self.matchFailed) { + UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:DummyCellId forIndexPath:indexPath]; + return cell; + } + + DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; + NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); + const CGFloat contentWidth = layout.contentWidth; + + id item = [self itemAtIndexPath:indexPath]; + DWDPBasicCell *cell = [collectionView dw_dequeueReusableCellForItem:item atIndexPath:indexPath]; + switch (indexPath.section) { + case DWContactsContentSectionRequests: + cell.backgroundStyle = DWDPBasicCellBackgroundStyle_WhiteOnGray; + break; + case DWContactsContentSectionGlobalMatched: + cell.backgroundStyle = DWDPBasicCellBackgroundStyle_GrayOnWhite; + break; + default: + cell.backgroundStyle = DWDPBasicCellBackgroundStyle_GrayOnGray; + break; + } + cell.contentWidth = contentWidth; + cell.delegate = self.itemsDelegate; + [cell setItem:item highlightedText:self.dataSource.trimmedQuery]; + return cell; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + const NSInteger section = indexPath.section; + if ([self shouldDisplayHeaderForSection:section] == NO) { + return [[UICollectionReusableView alloc] init]; + } + + if (section == DWContactsContentSectionSearch) { + if (self.contactsScreen && self.dataSource.isEmpty) { + DWContactsSearchPlaceholderView *headerView = (DWContactsSearchPlaceholderView *)[collectionView + dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:DWContactsSearchPlaceholderView.dw_reuseIdentifier + forIndexPath:indexPath]; + headerView.searchQuery = self.dataSource.trimmedQuery; + headerView.delegate = self; + return headerView; + } + else { + DWContactsSearchInfoHeaderView *headerView = (DWContactsSearchInfoHeaderView *)[collectionView + dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:DWContactsSearchInfoHeaderView.dw_reuseIdentifier + forIndexPath:indexPath]; + headerView.titleLabel.attributedText = self.searchHeaderTitle; + return headerView; + } + } + else if (section == DWContactsContentSectionRequests) { + const BOOL shouldHideViewAll = [self shouldHideViewAllRequests]; + DWTitleActionHeaderView *headerView = (DWTitleActionHeaderView *)[collectionView + dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:DWTitleActionHeaderView.dw_reuseIdentifier + forIndexPath:indexPath]; + headerView.titleLabel.text = self.requestsHeaderTitle; + headerView.delegate = self; + headerView.actionButton.hidden = shouldHideViewAll; + [headerView.actionButton setTitle:NSLocalizedString(@"View All", nil) forState:UIControlStateNormal]; + return headerView; + } + else if (section == DWContactsContentSectionContacts) { + DWFilterHeaderView *headerView = (DWFilterHeaderView *)[collectionView + dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:DWFilterHeaderView.dw_reuseIdentifier + forIndexPath:indexPath]; + headerView.titleLabel.text = NSLocalizedString(@"My Contacts", nil); + headerView.delegate = self; + [headerView.filterButton setAttributedTitle:self.contactsHeaderFilterButtonTitle forState:UIControlStateNormal]; + return headerView; + } + else { + if (self.matchFailed) { + DWGlobalMatchFailedHeaderView *headerView = (DWGlobalMatchFailedHeaderView *)[collectionView + dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:DWGlobalMatchFailedHeaderView.dw_reuseIdentifier + forIndexPath:indexPath]; + return headerView; + } + else { + DWGlobalMatchHeaderView *headerView = (DWGlobalMatchHeaderView *)[collectionView + dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:DWGlobalMatchHeaderView.dw_reuseIdentifier + forIndexPath:indexPath]; + headerView.searchQuery = self.dataSource.trimmedQuery; + return headerView; + } + } +} + +#pragma mark - UICollectionViewDelegate + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + [collectionView deselectItemAtIndexPath:indexPath animated:YES]; + + id item = [self itemAtIndexPath:indexPath]; + + [self.delegate baseContactsContentViewController:self didSelect:item indexPath:indexPath]; +} + +#pragma mark - UICollectionViewDelegateFlowLayout + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { + if ([self shouldDisplayHeaderForSection:section] == NO) { + return CGSizeZero; + } + + DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; + NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); + const CGFloat contentWidth = layout.contentWidth; + + BaseCollectionReusableView *measuringView = nil; + if (section == DWContactsContentSectionSearch) { + if (self.contactsScreen && self.dataSource.isEmpty) { + measuringView = self.measuringSearchPlaceholderView; + } + else { + measuringView = self.measuringSearchHeaderView; + } + } + else if (section == DWContactsContentSectionRequests) { + measuringView = self.measuringRequestsHeaderView; + } + else if (section == DWContactsContentSectionContacts) { + measuringView = self.measuringContactsHeaderView; + } + else { + if (self.matchFailed) { + measuringView = self.measuringGlobalMatchFailedHeaderView; + } + else { + measuringView = self.measuringGlobalMatchHeaderView; + } + } + + if (measuringView.isContentChanged) { + measuringView.frame = CGRectMake(0, 0, contentWidth, 300); + CGSize size = [measuringView systemLayoutSizeFittingSize:CGSizeMake(contentWidth, UILayoutFittingExpandedSize.height) + withHorizontalFittingPriority:UILayoutPriorityRequired + verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; + measuringView.cachedSize = size; + measuringView.isContentChanged = NO; + } + + return measuringView.cachedSize; +} + +#pragma mark - DWFilterHeaderViewDelegate + +- (void)filterHeaderView:(DWFilterHeaderView *)view filterButtonAction:(UIView *)sender { + // to be overriden +} + +- (void)filterHeaderView:(DWFilterHeaderView *)view infoButtonAction:(UIView *)sender { +} + +#pragma mark - DWTitleActionHeaderViewDelegate + +- (void)titleActionHeaderView:(DWTitleActionHeaderView *)view buttonAction:(UIView *)sender { + // to be overriden +} + +#pragma mark - DWContactsSearchPlaceholderViewDelegate + +- (void)contactsSearchPlaceholderView:(DWContactsSearchPlaceholderView *)view searchAction:(UIButton *)sender { + // to be overriden +} + +#pragma mark - Private + +- (id)itemAtIndexPath:(NSIndexPath *)indexPath { + NSAssert(indexPath.section > 0, @"Section 0 is empty and should not have any data items"); + if (indexPath.section == DWContactsContentSectionGlobalMatched) { + id item = self.matchedItems[indexPath.row]; + return item; + } + else { + NSIndexPath *dataIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:indexPath.section - 1]; + id item = [self.dataSource itemAtIndexPath:dataIndexPath]; + return item; + } +} + +- (BOOL)shouldDisplayHeaderForSection:(NSInteger)section { + if (section == DWContactsContentSectionSearch) { + if (self.dataSource.isSearching == NO) { + return NO; + } + else if (self.contactsScreen && self.dataSource.isEmpty) { + return YES; + } + } + else if (section == DWContactsContentSectionRequests && self.dataSource.requestsCount == 0) { + return NO; + } + else if (section == DWContactsContentSectionContacts && self.dataSource.contactsCount == 0) { + return NO; + } + else if (section == DWContactsContentSectionGlobalMatched && self.matchedItems.count == 0) { + return NO; + } + return YES; +} + +- (UICollectionView *)collectionView { + if (_collectionView == nil) { + DWListCollectionLayout *layout = [[DWListCollectionLayout alloc] init]; + + UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds + collectionViewLayout:layout]; + collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + collectionView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + collectionView.dataSource = self; + collectionView.delegate = self; + collectionView.alwaysBounceVertical = YES; + [collectionView dw_registerDPItemCells]; + [collectionView registerClass:DWContactsSearchPlaceholderView.class + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:DWContactsSearchPlaceholderView.dw_reuseIdentifier]; + [collectionView registerClass:DWContactsSearchInfoHeaderView.class + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:DWContactsSearchInfoHeaderView.dw_reuseIdentifier]; + [collectionView registerClass:DWTitleActionHeaderView.class + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:DWTitleActionHeaderView.dw_reuseIdentifier]; + [collectionView registerClass:DWFilterHeaderView.class + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:DWFilterHeaderView.dw_reuseIdentifier]; + [collectionView registerClass:DWGlobalMatchHeaderView.class + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:DWGlobalMatchHeaderView.dw_reuseIdentifier]; + [collectionView registerClass:DWGlobalMatchFailedHeaderView.class + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:DWGlobalMatchFailedHeaderView.dw_reuseIdentifier]; + [collectionView registerClass:UICollectionViewCell.class forCellWithReuseIdentifier:DummyCellId]; + + _collectionView = collectionView; + } + return _collectionView; +} + +- (DWContactsSearchPlaceholderView *)measuringSearchPlaceholderView { + if (_measuringSearchPlaceholderView == nil) { + _measuringSearchPlaceholderView = [[DWContactsSearchPlaceholderView alloc] initWithFrame:CGRectZero]; + } + _measuringSearchPlaceholderView.searchQuery = self.dataSource.trimmedQuery; + return _measuringSearchPlaceholderView; +} + +- (DWContactsSearchInfoHeaderView *)measuringSearchHeaderView { + if (_measuringSearchHeaderView == nil) { + _measuringSearchHeaderView = [[DWContactsSearchInfoHeaderView alloc] initWithFrame:CGRectZero]; + } + if (![self.searchHeaderTitle isEqualToAttributedString:_measuringSearchHeaderView.titleLabel.attributedText]) { + _measuringSearchHeaderView.isContentChanged = YES; + } + _measuringSearchHeaderView.titleLabel.attributedText = self.searchHeaderTitle; + return _measuringSearchHeaderView; +} + +- (DWTitleActionHeaderView *)measuringRequestsHeaderView { + if (_measuringRequestsHeaderView == nil) { + DWTitleActionHeaderView *view = [[DWTitleActionHeaderView alloc] initWithFrame:CGRectZero]; + [view.actionButton setTitle:NSLocalizedString(@"View All", nil) forState:UIControlStateNormal]; + _measuringRequestsHeaderView = view; + } + if (![self.requestsHeaderTitle isEqualToString:_measuringRequestsHeaderView.titleLabel.text] || + [self shouldHideViewAllRequests] != _measuringRequestsHeaderView.actionButton.hidden) { + _measuringRequestsHeaderView.isContentChanged = YES; + } + _measuringRequestsHeaderView.titleLabel.text = self.requestsHeaderTitle; + _measuringRequestsHeaderView.actionButton.hidden = [self shouldHideViewAllRequests]; + return _measuringRequestsHeaderView; +} + +- (DWFilterHeaderView *)measuringContactsHeaderView { + if (_measuringContactsHeaderView == nil) { + DWFilterHeaderView *headerView = [[DWFilterHeaderView alloc] initWithFrame:CGRectZero]; + headerView.titleLabel.text = NSLocalizedString(@"My Contacts", nil); + _measuringContactsHeaderView = headerView; + } + if (![self.contactsHeaderFilterButtonTitle isEqualToAttributedString:[_measuringContactsHeaderView.filterButton attributedTitleForState:UIControlStateNormal]]) { + _measuringContactsHeaderView.isContentChanged = YES; + } + [_measuringContactsHeaderView.filterButton setAttributedTitle:self.contactsHeaderFilterButtonTitle + forState:UIControlStateNormal]; + return _measuringContactsHeaderView; +} + +- (DWGlobalMatchHeaderView *)measuringGlobalMatchHeaderView { + if (_measuringGlobalMatchHeaderView == nil) { + _measuringGlobalMatchHeaderView = [[DWGlobalMatchHeaderView alloc] initWithFrame:CGRectZero]; + } + _measuringGlobalMatchHeaderView.searchQuery = self.dataSource.trimmedQuery; + return _measuringGlobalMatchHeaderView; +} + +- (DWGlobalMatchFailedHeaderView *)measuringGlobalMatchFailedHeaderView { + if (_measuringGlobalMatchFailedHeaderView == nil) { + _measuringGlobalMatchFailedHeaderView = [[DWGlobalMatchFailedHeaderView alloc] initWithFrame:CGRectZero]; + _measuringGlobalMatchFailedHeaderView.isContentChanged = YES; + } + return _measuringGlobalMatchFailedHeaderView; +} + +- (BOOL)shouldHideViewAllRequests { + id dataSource = self.dataSource; + const NSUInteger contactRequestsCount = dataSource.requestsCount; + const BOOL isSearching = dataSource.isSearching; + const BOOL hasMore = contactRequestsCount > self.maxVisibleContactRequestsCount; + return isSearching || !hasMore; +} + +- (NSAttributedString *)searchHeaderTitle { + if (_searchHeaderTitle == nil) { + NSString *query = self.dataSource.trimmedQuery; + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + NSDictionary *plainAttributes = @{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote], + }; + NSAttributedString *prefix = + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Search results for \"", @"Search results for \"John Doe\"") + attributes:plainAttributes]; + [result appendAttributedString:prefix]; + NSAttributedString *queryString = + [[NSAttributedString alloc] initWithString:query + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline], + }]; + [result appendAttributedString:queryString]; + NSAttributedString *suffix = [[NSAttributedString alloc] initWithString:@"\"" attributes:plainAttributes]; + [result appendAttributedString:suffix]; + [result endEditing]; + _searchHeaderTitle = [result copy]; + } + return _searchHeaderTitle; +} + +- (NSString *)requestsHeaderTitle { + if (_requestsHeaderTitle == nil) { + const BOOL shouldHideViewAll = [self shouldHideViewAllRequests]; + if (shouldHideViewAll) { + _requestsHeaderTitle = NSLocalizedString(@"Contact Requests", nil); + } + else { + id dataSource = self.dataSource; + const NSUInteger contactRequestsCount = dataSource.requestsCount; + _requestsHeaderTitle = [NSString stringWithFormat:@"%@ (%ld)", + NSLocalizedString(@"Contact Requests", nil), + contactRequestsCount]; + } + } + return _requestsHeaderTitle; +} + +- (NSAttributedString *)contactsHeaderFilterButtonTitle { + if (_contactsHeaderFilterButtonTitle == nil) { + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + NSAttributedString *prefix = + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Sort by", nil) + attributes:@{ + NSForegroundColorAttributeName : [UIColor dw_tertiaryTextColor], + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleCaption1], + }]; + [result appendAttributedString:prefix]; + + [result appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; + NSString *optionValue = nil; + switch (self.dataSource.sortMode) { + case DWContactsSortMode_ByUsername: { + optionValue = NSLocalizedString(@"Name", nil); + break; + } + } + NSAttributedString *option = + [[NSAttributedString alloc] initWithString:optionValue + attributes:@{ + NSForegroundColorAttributeName : [UIColor dw_dashBlueColor], + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote], + }]; + [result appendAttributedString:option]; + [result endEditing]; + _contactsHeaderFilterButtonTitle = [result copy]; + } + return _contactsHeaderFilterButtonTitle; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController+DWProtected.h b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController+DWProtected.h new file mode 100644 index 000000000..3cb44fe11 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController+DWProtected.h @@ -0,0 +1,43 @@ +// +// 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 "DWBaseContactsViewController.h" + +#import "DWBaseContactsContentViewController.h" +#import "DWBaseContactsModel.h" +#import "DWDPNewIncomingRequestItem.h" +#import "DWSearchStateViewController.h" +#import "DWSendInviteFlowController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWBaseContactsViewController () + +@property (readonly, nonatomic, strong) id payModel; +@property (readonly, nonatomic, strong) id dataProvider; + +@property (readonly, nonatomic, strong) DWBaseContactsModel *model; +@property (readonly, nonatomic, strong) DWSearchStateViewController *stateController; +@property (readonly, nonatomic, strong) __kindof UIViewController *localNoContactsController; +@property (readonly, nonatomic, strong) DWBaseContactsContentViewController *contentController; + +- (void)addContactButtonAction; +- (void)inviteButtonAction; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.h new file mode 100644 index 000000000..7ff03b88b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.h @@ -0,0 +1,40 @@ +// +// 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 "DWSearchViewController.h" + +#import "DWBaseContactsContentViewController.h" +#import "DWPayModelProtocol.h" +#import "DWTransactionListDataProviderProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWBaseContactsViewController : DWSearchViewController + +@property (nonatomic, assign) BOOL disableSearchPlaceholder; + +- (instancetype)initWithPayModel:(id)payModel + dataProvider:(id)dataProvider NS_DESIGNATED_INITIALIZER; + +- (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 + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.m new file mode 100644 index 000000000..abef5f5bc --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWBaseContactsViewController.m @@ -0,0 +1,204 @@ +// +// 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 "DWBaseContactsViewController+DWProtected.h" + +#import + +#import "DWSendInviteFlowController.h" +#import "DWUIKit.h" +#import "DWUserProfileViewController.h" +#import "DWUserSearchViewController.h" +#import "UIView+DWFindConstraints.h" +#import "UIViewController+DWEmbedding.h" + +NS_ASSUME_NONNULL_BEGIN + +// Some sane limit to prevent breaking layout +static NSInteger const MAX_SEARCH_LENGTH = 100; + +NS_ASSUME_NONNULL_END + +@implementation DWBaseContactsViewController + +- (instancetype)initWithPayModel:(id)payModel + dataProvider:(id)dataProvider { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _payModel = payModel; + _dataProvider = dataProvider; + } + return self; +} + +- (void)dealloc { + DSLog(@"☠️ %@", NSStringFromClass(self.class)); +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.disableSearchBarBecomesFirstResponderOnFirstAppearance = YES; + + [self dw_embedChild:self.stateController inContainer:self.contentView]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self.model start]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [self.model stop]; +} + +#pragma mark - DWContactsModelDelegate + +- (void)contactsModelDidUpdate:(DWBaseContactsModel *)model { + self.searchBar.hidden = NO; + [self.localNoContactsController dw_detachFromParent]; + + id dataSource = model.dataSource; + if (dataSource.isEmpty) { + if (dataSource.isSearching) { + if (self.disableSearchPlaceholder) { + self.contentController.dataSource = dataSource; + + if (self.contentController.parentViewController == nil) { + [self dw_embedChild:self.contentController inContainer:self.contentView]; + [self updateContentKeyboardConstraintsIfNeeded]; + } + } + else { + [self dw_embedChild:self.stateController inContainer:self.contentView]; + [self.stateController setNoResultsLocalStateWithQuery:dataSource.trimmedQuery]; + [self.contentController dw_detachFromParent]; + } + } + else { + self.searchBar.hidden = YES; + [self dw_embedChild:self.localNoContactsController inContainer:self.contentView]; + [self.contentController dw_detachFromParent]; + } + } + else { + self.contentController.dataSource = dataSource; + + if (self.contentController.parentViewController == nil) { + [self dw_embedChild:self.contentController inContainer:self.contentView]; + [self updateContentKeyboardConstraintsIfNeeded]; + } + } +} + +#pragma mark - UISearchBarDelegate + +- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { + [self.model searchWithQuery:self.searchBar.text]; +} + +- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { + NSString *resultText = [searchBar.text stringByReplacingCharactersInRange:range withString:text]; + return resultText.length <= MAX_SEARCH_LENGTH; +} + +#pragma mark - DWSearchStateViewControllerDelegate + +- (void)searchStateViewController:(DWSearchStateViewController *)controller buttonAction:(UIButton *)sender { + [self addContactButtonAction]; +} + +- (void)searchStateViewController:(DWSearchStateViewController *)controller inviteButtonAction:(UIButton *)sender { + [self inviteButtonAction]; +} + +#pragma mark - DWBaseContactsContentViewController + +- (void)baseContactsContentViewController:(DWBaseContactsContentViewController *)controller + didSelect:(id)item + indexPath:(NSIndexPath *)indexPath { + DWUserProfileViewController *profileController = + [[DWUserProfileViewController alloc] initWithItem:item + payModel:self.payModel + dataProvider:self.dataProvider + shouldSkipUpdating:YES + shownAfterPayment:NO]; + [self.navigationController pushViewController:profileController animated:YES]; +} + +#pragma mark - DWDPNewIncomingRequestItemDelegate + +- (void)acceptIncomingRequest:(id)item { + [self.model acceptContactRequest:item]; +} + +- (void)declineIncomingRequest:(id)item { + [self.model declineContactRequest:item]; +} + +#pragma mark - Keyboard + +- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height + animationDuration:(NSTimeInterval)animationDuration + animationCurve:(UIViewAnimationCurve)animationCurve { + NSLayoutConstraint *constraint = [self.stateController.view dw_findConstraintWithAttribute:NSLayoutAttributeBottom]; + constraint.constant = height; + [self updateContentKeyboardConstraintsIfNeeded]; + [self.view layoutIfNeeded]; +} + +- (void)updateContentKeyboardConstraintsIfNeeded { + NSLayoutConstraint *constraint = [self.contentController.view dw_findConstraintWithAttribute:NSLayoutAttributeBottom]; + if (self.ka_keyboardHeight > 0) { + constraint.constant = self.ka_keyboardHeight; //TODO: DashPay - DW_TABBAR_HEIGHT; + } + else { + constraint.constant = 0; + } +} + +#pragma mark - Actions + +- (void)addContactButtonAction { + if (!self.model.hasBlockchainIdentity) { + return; + } + + DWUserSearchViewController *controller = + [[DWUserSearchViewController alloc] initWithPayModel:self.payModel + dataProvider:self.dataProvider]; + controller.stateController.delegate = self; + [self.navigationController pushViewController:controller animated:YES]; +} + +- (void)inviteButtonAction { + DWSendInviteFlowController *controller = [[DWSendInviteFlowController alloc] init]; + controller.delegate = self; + [self presentViewController:controller animated:YES completion:nil]; +} + +#pragma mark - DWSendInviteFlowControllerDelegate + +- (void)sendInviteFlowControllerDidFinish:(DWSendInviteFlowController *)controller { + [controller dismissViewControllerAnimated:YES completion:nil]; +} + + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.h new file mode 100644 index 000000000..8eb27c835 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.h @@ -0,0 +1,45 @@ +// +// 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 "DWBaseContactsContentViewController.h" + +#import "DWContactsModel.h" +#import "DWPayModelProtocol.h" +#import "DWTransactionListDataProviderProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWContactsContentViewController; + +@protocol DWContactsContentControllerDelegate + +- (void)contactsContentController:(DWContactsContentViewController *)controller + contactsFilterButtonAction:(UIView *)sender; +- (void)contactsContentController:(DWContactsContentViewController *)controller + contactRequestsButtonAction:(UIView *)sender; +- (void)contactsContentController:(DWContactsContentViewController *)controller + globalSearchButtonAction:(UIView *)sender; + +@end + +@interface DWContactsContentViewController : DWBaseContactsContentViewController + +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.m new file mode 100644 index 000000000..adb677785 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsContentViewController.m @@ -0,0 +1,50 @@ +// +// 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 "DWContactsContentViewController.h" + +#import "DWBaseContactsContentViewController+DWProtected.h" + +#import "DWUIKit.h" + +@implementation DWContactsContentViewController + +@dynamic delegate; + +- (NSUInteger)maxVisibleContactRequestsCount { + return 3; +} + +#pragma mark - DWContactsSearchPlaceholderViewDelegate + +- (void)contactsSearchPlaceholderView:(DWContactsSearchPlaceholderView *)view searchAction:(UIButton *)sender { + [self.delegate contactsContentController:self globalSearchButtonAction:sender]; +} + +#pragma mark - DWFilterHeaderViewDelegate + +- (void)filterHeaderView:(DWFilterHeaderView *)view filterButtonAction:(UIView *)sender { + [self.delegate contactsContentController:self contactsFilterButtonAction:sender]; +} + +#pragma mark - DWTitleActionHeaderViewDelegate + +- (void)titleActionHeaderView:(DWTitleActionHeaderView *)view buttonAction:(UIView *)sender { + [self.delegate contactsContentController:self contactRequestsButtonAction:sender]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.h new file mode 100644 index 000000000..dd134640c --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.h @@ -0,0 +1,45 @@ +// +// 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 "DWBaseContactsViewController.h" + +#import "DWContactsModel.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, DWContactsControllerIntent) { + DWContactsControllerIntent_Default, + DWContactsControllerIntent_PayToSelector, +}; + +@class DWContactsViewController; + +@protocol DWContactsViewControllerPayDelegate + +- (void)contactsViewController:(DWContactsViewController *)controller payToItem:(id)item; + +@end + +@interface DWContactsViewController : DWBaseContactsViewController + +@property (nonatomic, strong) DWContactsModel *model; +@property (nonatomic, assign) DWContactsControllerIntent intent; +@property (nullable, nonatomic, weak) id payDelegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.m new file mode 100644 index 000000000..bdd6dd2c8 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWContactsViewController.m @@ -0,0 +1,245 @@ +// +// 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 "DWContactsViewController.h" + +#import "DWBaseContactsViewController+DWProtected.h" +#import "DWContactsContentViewController.h" +#import "DWRequestsViewController.h" + +#import "DWDPEstablishedContactItem.h" +#import "DWDPNewIncomingRequestItem.h" +#import "DWDPPendingRequestItem.h" +#import "DWDPRespondedRequestItem.h" +#import "DWNoContactsViewController.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWContactsViewController () + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWContactsViewController + +@synthesize model = _model; +@synthesize stateController = _stateController; +@synthesize localNoContactsController = _localNoContactsController; +@synthesize contentController = _contentController; + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.disableSearchPlaceholder = YES; + + switch (self.intent) { + case DWContactsControllerIntent_Default: + self.title = NSLocalizedString(@"Contacts", nil); + break; + + case DWContactsControllerIntent_PayToSelector: + self.title = NSLocalizedString(@"Send to a Contact", nil); + break; + } + + self.searchBar.placeholder = NSLocalizedString(@"Search for a contact", nil); + + UIImage *image = [[UIImage imageNamed:@"dp_add_contact"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; + UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithImage:image + style:UIBarButtonItemStylePlain + target:self + action:@selector(addContactButtonAction)]; + self.navigationItem.rightBarButtonItem = button; +} + +#pragma mark - Private + +- (DWContactsModel *)model { + if (!_model) { + _model = [[DWContactsModel alloc] init]; + _model.delegate = self; + _model.context = self; + _model.globalSearchModel.delegate = self; + _model.globalSearchModel.context = self; + } + return _model; +} + +- (DWSearchStateViewController *)stateController { + if (_stateController == nil) { + _stateController = [[DWSearchStateViewController alloc] init]; + _stateController.delegate = self; + } + return _stateController; +} + +- (DWNoContactsViewController *)localNoContactsController { + if (_localNoContactsController == nil) { + DWNoContactsViewController *controller = [[DWNoContactsViewController alloc] init]; + [controller loadViewIfNeeded]; + [controller.addButton addTarget:self + action:@selector(addContactButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + [controller.inviteButton addTarget:self + action:@selector(inviteButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + _localNoContactsController = controller; + } + return _localNoContactsController; +} + +- (DWBaseContactsContentViewController *)contentController { + if (_contentController == nil) { + DWContactsContentViewController *controller = + [[DWContactsContentViewController alloc] initWithPayModel:self.payModel + dataProvider:self.dataProvider]; + controller.contactsScreen = YES; + controller.dataSource = self.model.dataSource; + controller.itemsDelegate = self; + controller.delegate = self; + _contentController = controller; + } + return _contentController; +} + +#pragma mark - UISearchBarDelegate + +- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { + [super searchBar:searchBar textDidChange:searchText]; + + if (self.intent == DWContactsControllerIntent_Default) { + self.contentController.matchFailed = NO; + self.contentController.matchedItems = @[]; + [self.model.globalSearchModel searchWithQuery:self.searchBar.text]; + } +} + +#pragma mark - DWBaseContactsContentViewControllerDelegate + +- (void)baseContactsContentViewController:(DWBaseContactsContentViewController *)controller + didSelect:(id)item + indexPath:(NSIndexPath *)indexPath { + if (![self.model canOpenBlockchainIdentity:item.blockchainIdentity]) { + UICollectionViewCell *cell = [self.contentController.collectionView cellForItemAtIndexPath:indexPath]; + [cell dw_shakeView]; + return; + } + + if (self.intent == DWContactsControllerIntent_Default) { + [super baseContactsContentViewController:controller didSelect:item indexPath:indexPath]; + } + else { + [self.payDelegate contactsViewController:self payToItem:item]; + } +} + +#pragma mark - DWContactsContentControllerDelegate + +- (void)contactsContentController:(DWContactsContentViewController *)controller + contactsFilterButtonAction:(UIButton *)sender { + NSString *title = NSLocalizedString(@"Sort Contacts", nil); + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:title + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + { + UIAlertAction *action = [UIAlertAction + actionWithTitle:NSLocalizedString(@"Name", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + self.model.sortMode = DWContactsSortMode_ByUsername; + }]; + [alert addAction:action]; + } + + { + UIAlertAction *action = [UIAlertAction + actionWithTitle:NSLocalizedString(@"Cancel", nil) + style:UIAlertActionStyleCancel + handler:nil]; + [alert addAction:action]; + } + + if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { + alert.popoverPresentationController.sourceView = sender; + alert.popoverPresentationController.sourceRect = sender.bounds; + } + + [self presentViewController:alert animated:YES completion:nil]; +} + +- (void)contactsContentController:(DWContactsContentViewController *)controller + contactRequestsButtonAction:(UIButton *)sender { + DWRequestsModel *requestsModel = [self.model contactRequestsModel]; + DWRequestsViewController *requestsController = + [[DWRequestsViewController alloc] initWithModel:requestsModel + payModel:self.payModel + dataProvider:self.dataProvider]; + if (self.intent == DWContactsControllerIntent_PayToSelector) { + requestsController.contentDelegate = self; + } + [self.navigationController pushViewController:requestsController animated:YES]; +} + +- (void)contactsContentController:(DWContactsContentViewController *)controller + globalSearchButtonAction:(UIView *)sender { + [self addContactButtonAction]; +} + +#pragma mark - DWUserSearchModelDelegate + +- (void)userSearchModelDidStartSearch:(DWUserSearchModel *)model { + self.contentController.matchFailed = NO; + self.contentController.matchedItems = @[]; +} + +- (void)userSearchModel:(DWUserSearchModel *)model completedWithItems:(NSArray> *)items { + const NSUInteger maxMatchedCount = 3; + NSMutableArray> *selected = [NSMutableArray array]; + for (id item in items) { + if ([item conformsToProtocol:@protocol(DWDPEstablishedContactItem)]) { + continue; + } + else if ([item conformsToProtocol:@protocol(DWDPPendingRequestItem)]) { + continue; + } + else if ([item conformsToProtocol:@protocol(DWDPRespondedRequestItem)]) { + continue; + } + else if ([item conformsToProtocol:@protocol(DWDPNewIncomingRequestItem)]) { + continue; + } + else { + [selected addObject:item]; + } + + if (selected.count == maxMatchedCount) { + break; + } + } + self.contentController.matchFailed = NO; + self.contentController.matchedItems = selected; +} + +- (void)userSearchModel:(DWUserSearchModel *)model completedWithError:(NSError *)error { + self.contentController.matchFailed = YES; + self.contentController.matchedItems = @[]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.h index 4d7dfbdc7..21a27c16e 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.h +++ b/DashWallet/Sources/UI/DashPay/Contacts/DWRootContactsViewController.h @@ -15,7 +15,7 @@ // limitations under the License. // -#import "DWNavigationChildViewController.h" +@import UIKit; #import "DWDashPayProtocol.h" #import "DWDashPayReadyProtocol.h" @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DWRootContactsViewController : DWNavigationChildViewController +@interface DWRootContactsViewController : UIViewController - (instancetype)initWithPayModel:(id)payModel dataProvider:(id)dataProvider diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.h new file mode 100644 index 000000000..11f0ed8f0 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.h @@ -0,0 +1,44 @@ +// +// 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 + +@class DWSearchStateViewController; + +@protocol DWSearchStateViewControllerDelegate + +- (void)searchStateViewController:(DWSearchStateViewController *)controller buttonAction:(UIButton *)sender; +- (void)searchStateViewController:(DWSearchStateViewController *)controller inviteButtonAction:(UIButton *)sender; + +@end + +@interface DWSearchStateViewController : UIViewController + +@property (nullable, nonatomic, weak) id delegate; + +- (void)setPlaceholderGlobalState; +- (void)setPlaceholderLocalState; +- (void)setSearchingStateWithQuery:(NSString *)query; +- (void)setNoResultsGlobalStateWithQuery:(NSString *)query; +- (void)setNoResultsLocalStateWithQuery:(NSString *)query; +- (void)setErrorState; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.m new file mode 100644 index 000000000..06e2e9346 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWSearchStateViewController.m @@ -0,0 +1,410 @@ +// +// 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 "DWSearchStateViewController.h" + +#import "DWActionButton.h" +#import "DWGlobalOptions.h" +#import "DWInvitationSuggestionView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, DWUserSearchState) { + DWUserSearchState_PlaceholderGlobal, + DWUserSearchState_PlaceholderLocal, + DWUserSearchState_Searching, + DWUserSearchState_NoResultsGlobal, + DWUserSearchState_NoResultsLocal, + DWUserSearchState_Error, +}; + + +@interface DWSearchStateViewController () + +@property (null_resettable, nonatomic, strong) UIImageView *iconImageView; +@property (null_resettable, nonatomic, strong) UILabel *descriptionLabel; +@property (null_resettable, nonatomic, strong) UIButton *actionButton; +@property (null_resettable, nonatomic, strong) DWInvitationSuggestionView *invitationView; + +@property (nonatomic, assign) DWUserSearchState state; +@property (nullable, copy, nonatomic) NSString *searchQuery; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWSearchStateViewController + +- (void)setPlaceholderGlobalState { + self.searchQuery = nil; + self.state = DWUserSearchState_PlaceholderGlobal; + + [self reloadData]; +} + +- (void)setPlaceholderLocalState { + self.searchQuery = nil; + self.state = DWUserSearchState_PlaceholderLocal; + + [self reloadData]; +} + +- (void)setSearchingStateWithQuery:(NSString *)query { + self.searchQuery = query; + self.state = DWUserSearchState_Searching; + + [self reloadData]; +} + +- (void)setNoResultsGlobalStateWithQuery:(NSString *)query { + self.searchQuery = query; + self.state = DWUserSearchState_NoResultsGlobal; + + [self reloadData]; +} + +- (void)setNoResultsLocalStateWithQuery:(NSString *)query { + self.searchQuery = query; + self.state = DWUserSearchState_NoResultsLocal; + + [self reloadData]; +} + +- (void)setErrorState { + self.searchQuery = nil; + self.state = DWUserSearchState_Error; + + [self reloadData]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + NSArray *views = @[ self.iconImageView, self.descriptionLabel, self.actionButton ]; + UIStackView *verticalStackView = [[UIStackView alloc] initWithArrangedSubviews:views]; + verticalStackView.translatesAutoresizingMaskIntoConstraints = NO; + verticalStackView.axis = UILayoutConstraintAxisVertical; + verticalStackView.alignment = UIStackViewAlignmentCenter; + verticalStackView.spacing = 24.0; + + UIStackView *horizontalStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ verticalStackView ]]; + horizontalStackView.translatesAutoresizingMaskIntoConstraints = NO; + horizontalStackView.axis = UILayoutConstraintAxisHorizontal; + horizontalStackView.alignment = UIStackViewAlignmentCenter; + [self.view addSubview:horizontalStackView]; + + [self.view addSubview:self.invitationView]; + + UILayoutGuide *guide = self.view.layoutMarginsGuide; + + [NSLayoutConstraint activateConstraints:@[ + [horizontalStackView.topAnchor constraintEqualToAnchor:self.view.topAnchor], + [horizontalStackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:horizontalStackView.trailingAnchor], + [self.view.bottomAnchor constraintEqualToAnchor:horizontalStackView.bottomAnchor], + [self.actionButton.heightAnchor constraintEqualToConstant:44.0], + + [self.invitationView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:self.invitationView.trailingAnchor], + [guide.bottomAnchor constraintEqualToAnchor:self.invitationView.bottomAnchor], + ]]; + + [self reloadData]; +} + +- (UIImageView *)iconImageView { + if (_iconImageView == nil) { + UIImageView *imageView = [[UIImageView alloc] init]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + _iconImageView = imageView; + } + return _iconImageView; +} + +- (UILabel *)descriptionLabel { + if (_descriptionLabel == nil) { + UILabel *label = [[UILabel alloc] init]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + label.numberOfLines = 0; + label.lineBreakMode = NSLineBreakByWordWrapping; + label.textAlignment = NSTextAlignmentCenter; + label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + label.adjustsFontForContentSizeCategory = YES; + label.textColor = [UIColor dw_darkTitleColor]; + [label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + _descriptionLabel = label; + } + return _descriptionLabel; +} + +- (UIButton *)actionButton { + if (_actionButton == nil) { + DWActionButton *button = [[DWActionButton alloc] initWithFrame:CGRectZero]; + button.translatesAutoresizingMaskIntoConstraints = NO; + button.small = YES; + button.inverted = YES; + button.usedOnDarkBackground = NO; + [button addTarget:self action:@selector(actionButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + _actionButton = button; + } + return _actionButton; +} + +- (DWInvitationSuggestionView *)invitationView { + if (!_invitationView) { + _invitationView = [[DWInvitationSuggestionView alloc] init]; + _invitationView.translatesAutoresizingMaskIntoConstraints = NO; + [_invitationView.inviteButton addTarget:self + action:@selector(inviteButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + _invitationView.alpha = [DWGlobalOptions sharedInstance].dpInvitationFlowEnabled ? 1.0 : 0.0; + } + return _invitationView; +} + +- (void)reloadData { + switch (self.state) { + case DWUserSearchState_PlaceholderGlobal: { + [self configurePlaceholderState]; + + break; + } + case DWUserSearchState_PlaceholderLocal: { + [self configurePlaceholderState]; + [self configureActionButtonForSearchUsers]; + + break; + } + case DWUserSearchState_Searching: { + [self configureSearchingAnimationState]; + + break; + } + case DWUserSearchState_NoResultsGlobal: { + [self configureNoResultsGlobalState]; + + break; + } + case DWUserSearchState_NoResultsLocal: { + [self configureNoResultsLocalState]; + [self configureActionButtonForSearchUsers]; + + break; + } + case DWUserSearchState_Error: { + [self configureSearchErrorState]; + + break; + } + } +} + +- (void)actionButtonAction:(UIButton *)sender { + [self.delegate searchStateViewController:self buttonAction:sender]; +} + +- (void)inviteButtonAction:(UIButton *)sender { + [self.delegate searchStateViewController:self inviteButtonAction:sender]; +} + +- (void)configurePlaceholderState { + self.invitationView.hidden = YES; + self.actionButton.hidden = YES; + + [self.iconImageView stopAnimating]; + self.iconImageView.animationImages = nil; + self.iconImageView.image = [UIImage imageNamed:@"dp_user_search_placeholder"]; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + + NSAttributedString *title = + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Add a New Contact", nil) + attributes:@{NSFontAttributeName : self.boldFont}]; + [result appendAttributedString:title]; + + [result appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; + + NSAttributedString *subtitle = + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Find a user on the Dash Network", nil) + attributes:@{NSFontAttributeName : self.regularFont}]; + [result appendAttributedString:subtitle]; + + [result endEditing]; + + self.descriptionLabel.attributedText = result; +} + +- (void)configureSearchingAnimationState { + NSParameterAssert(self.searchQuery); + + self.invitationView.hidden = YES; + self.actionButton.hidden = YES; + + if (self.iconImageView.animationImages == nil) { + NSArray *frames = @[ + [UIImage imageNamed:@"dp_user_search_anim_1"], + [UIImage imageNamed:@"dp_user_search_anim_2"], + [UIImage imageNamed:@"dp_user_search_anim_3"], + [UIImage imageNamed:@"dp_user_search_anim_4"], + ]; + self.iconImageView.animationImages = frames; + self.iconImageView.animationDuration = 0.65; + self.iconImageView.animationRepeatCount = 0; + [self.iconImageView startAnimating]; + } + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + + NSString *format = NSLocalizedString(@"Searching for username %@ on the Dash Network", nil); + NSString *query = [NSString stringWithFormat:@"\"%@\"", self.searchQuery ?: @""]; + NSString *text = [NSString stringWithFormat:format, query]; + + NSAttributedString *attributed = + [[NSAttributedString alloc] initWithString:text + attributes:@{NSFontAttributeName : self.regularFont}]; + [result appendAttributedString:attributed]; + + NSRange queryRange = [text rangeOfString:query]; + if (queryRange.location != NSNotFound) { + [result removeAttribute:NSFontAttributeName range:queryRange]; + [result setAttributes:@{NSFontAttributeName : self.boldFont} range:queryRange]; + } + + [result endEditing]; + + self.descriptionLabel.attributedText = result; +} + +- (void)configureNoResultsGlobalState { + NSParameterAssert(self.searchQuery); + + self.invitationView.hidden = NO; + self.actionButton.hidden = YES; + + [self.iconImageView stopAnimating]; + self.iconImageView.animationImages = nil; + self.iconImageView.image = [UIImage imageNamed:@"dp_user_search_warning"]; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + + NSString *format = NSLocalizedString(@"There are no users that match with the name %@", nil); + NSString *query = [NSString stringWithFormat:@"\"%@\"", self.searchQuery ?: @""]; + NSString *text = [NSString stringWithFormat:format, query]; + + NSAttributedString *attributed = + [[NSAttributedString alloc] initWithString:text + attributes:@{NSFontAttributeName : self.regularFont}]; + [result appendAttributedString:attributed]; + + NSRange queryRange = [text rangeOfString:query]; + if (queryRange.location != NSNotFound) { + [result removeAttribute:NSFontAttributeName range:queryRange]; + [result setAttributes:@{NSFontAttributeName : self.boldFont} range:queryRange]; + } + + [result endEditing]; + + self.descriptionLabel.attributedText = result; +} + +- (void)configureNoResultsLocalState { + NSParameterAssert(self.searchQuery); + + self.invitationView.hidden = YES; + self.actionButton.hidden = YES; + + [self.iconImageView stopAnimating]; + self.iconImageView.animationImages = nil; + self.iconImageView.image = [UIImage imageNamed:@"dp_user_search_warning"]; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + + NSString *format = NSLocalizedString(@"There are no users that match with the name %@ in your contacts", nil); + NSString *query = [NSString stringWithFormat:@"\"%@\"", self.searchQuery ?: @""]; + NSString *text = [NSString stringWithFormat:format, query]; + + NSAttributedString *attributed = + [[NSAttributedString alloc] initWithString:text + attributes:@{NSFontAttributeName : self.regularFont}]; + [result appendAttributedString:attributed]; + + NSRange queryRange = [text rangeOfString:query]; + if (queryRange.location != NSNotFound) { + [result removeAttribute:NSFontAttributeName range:queryRange]; + [result setAttributes:@{NSFontAttributeName : self.boldFont} range:queryRange]; + } + + [result endEditing]; + + self.descriptionLabel.attributedText = result; +} + +- (void)configureSearchErrorState { + self.invitationView.hidden = YES; + self.actionButton.hidden = YES; + + [self.iconImageView stopAnimating]; + self.iconImageView.animationImages = nil; + self.iconImageView.image = [UIImage imageNamed:@"network_unavailable"]; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + + NSAttributedString *title = + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Network Unavailable", nil) + attributes:@{NSFontAttributeName : self.boldFont}]; + [result appendAttributedString:title]; + + [result appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; + + NSAttributedString *subtitle = + [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Unable to search for a user", nil) + attributes:@{NSFontAttributeName : self.regularFont}]; + [result appendAttributedString:subtitle]; + + [result endEditing]; + + self.descriptionLabel.attributedText = result; +} + +- (void)configureActionButtonForSearchUsers { + self.invitationView.hidden = YES; + self.actionButton.hidden = NO; + self.actionButton.imageEdgeInsets = UIEdgeInsetsMake(0.0, -8.0, 0.0, 0.0); + [self.actionButton setImage:[UIImage imageNamed:@"dp_search_add_contact"] forState:UIControlStateNormal]; + [self.actionButton setTitle:NSLocalizedString(@"Search for a User on the Dash Network", nil) + forState:UIControlStateNormal]; +} + +- (UIFont *)regularFont { + return [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; +} + +- (UIFont *)boldFont { + return [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.h new file mode 100644 index 000000000..6d68ed081 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.h @@ -0,0 +1,50 @@ +// +// 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 + +#import "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol DWUserDetailsCellDelegate; +@class DWUserSearchResultViewController; + +@protocol DWUserSearchResultViewControllerDelegate + +- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller + willDisplayItemAtIndex:(NSInteger)index; +- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller + didSelectItemAtIndex:(NSInteger)index + cell:(UICollectionViewCell *)cell; + +- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller + acceptContactRequest:(id)item; +- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller + declineContactRequest:(id)item; + +@end + +@interface DWUserSearchResultViewController : UIViewController + +@property (nullable, nonatomic, copy) NSString *searchQuery; +@property (nullable, nonatomic, copy) NSArray> *items; +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.m new file mode 100644 index 000000000..4f2039eb0 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Children/DWUserSearchResultViewController.m @@ -0,0 +1,118 @@ +// +// 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 "DWUserSearchResultViewController.h" + +#import "DWUIKit.h" + +#import "DWDPBasicCell.h" +#import "DWDPNewIncomingRequestItem.h" +#import "DWListCollectionLayout.h" +#import "UICollectionView+DWDPItemDequeue.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserSearchResultViewController () + +@property (null_resettable, nonatomic, strong) UICollectionView *collectionView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserSearchResultViewController + +- (void)setItems:(NSArray> *)items { + _items = [items copy]; + + [self.collectionView reloadData]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + [self.view addSubview:self.collectionView]; +} + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.items.count; +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; + NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); + const CGFloat contentWidth = layout.contentWidth; + + id item = self.items[indexPath.row]; + + DWDPBasicCell *cell = [collectionView dw_dequeueReusableCellForItem:item atIndexPath:indexPath]; + cell.contentWidth = contentWidth; + cell.backgroundStyle = DWDPBasicCellBackgroundStyle_WhiteOnGray; + cell.delegate = self; + [cell setItem:item highlightedText:self.searchQuery]; + + return cell; +} + +#pragma mark - UICollectionViewDelegate + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + [self.delegate userSearchResultViewController:self willDisplayItemAtIndex:indexPath.row]; +} + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + [self.collectionView deselectItemAtIndexPath:indexPath animated:YES]; + + UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; + [self.delegate userSearchResultViewController:self didSelectItemAtIndex:indexPath.row cell:cell]; +} + +#pragma mark - DWDPNewIncomingRequestItemDelegate + +- (void)acceptIncomingRequest:(id)item { + [self.delegate userSearchResultViewController:self acceptContactRequest:item]; +} + +- (void)declineIncomingRequest:(id)item { + [self.delegate userSearchResultViewController:self declineContactRequest:item]; +} + +#pragma mark - Private + +- (UICollectionView *)collectionView { + if (_collectionView == nil) { + DWListCollectionLayout *layout = [[DWListCollectionLayout alloc] init]; + + UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds + collectionViewLayout:layout]; + collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + collectionView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + collectionView.delegate = self; + collectionView.dataSource = self; + collectionView.alwaysBounceVertical = YES; + [collectionView dw_registerDPItemCells]; + + _collectionView = collectionView; + } + return _collectionView; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.h new file mode 100644 index 000000000..1b9509224 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.h @@ -0,0 +1,40 @@ +// +// 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 "DWSearchViewController.h" + +#import "DWPayModelProtocol.h" +#import "DWSearchStateViewController.h" +#import "DWTransactionListDataProviderProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserSearchViewController : DWSearchViewController + +@property (readonly, nonatomic, strong) DWSearchStateViewController *stateController; + +- (instancetype)initWithPayModel:(id)payModel + dataProvider:(id)dataProvider NS_DESIGNATED_INITIALIZER; + +- (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 + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.m new file mode 100644 index 000000000..6ee5cf84e --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/DWUserSearchViewController.m @@ -0,0 +1,200 @@ +// +// 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 "DWUserSearchViewController.h" + +#import + +#import "DWDashPayConstants.h" +#import "DWUIKit.h" +#import "DWUserProfileViewController.h" +#import "DWUserSearchModel.h" +#import "DWUserSearchResultViewController.h" +#import "UIView+DWFindConstraints.h" +#import "UIViewController+DWEmbedding.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserSearchViewController () + +@property (readonly, nonatomic, strong) id payModel; +@property (readonly, nonatomic, strong) id dataProvider; + +@property (null_resettable, nonatomic, strong) DWUserSearchModel *model; + +@property (null_resettable, nonatomic, strong) DWSearchStateViewController *stateController; +@property (null_resettable, nonatomic, strong) DWUserSearchResultViewController *resultsController; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserSearchViewController + +- (instancetype)initWithPayModel:(id)payModel + dataProvider:(id)dataProvider { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _payModel = payModel; + _dataProvider = dataProvider; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = NSLocalizedString(@"Add a New Contact", nil); + + self.searchBar.placeholder = NSLocalizedString(@"Search for a username", nil); + + [self.stateController setPlaceholderGlobalState]; + [self dw_embedChild:self.stateController inContainer:self.contentView]; +} + +#pragma mark - UISearchBarDelegate + +- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { + [self.model searchWithQuery:self.searchBar.text]; + + if (self.model.trimmedQuery.length == 0) { + [self.stateController setPlaceholderGlobalState]; + } + + [self.resultsController dw_detachFromParent]; +} + +- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { + NSString *resultText = [searchBar.text stringByReplacingCharactersInRange:range withString:text]; + return resultText.length <= DW_MAX_USERNAME_LENGTH; +} + +#pragma mark - DWUserSearchModelDelegate + +- (void)userSearchModelDidStartSearch:(DWUserSearchModel *)model { + if (self.model.trimmedQuery.length == 0) { + [self.stateController setPlaceholderGlobalState]; + } + else { + [self.stateController setSearchingStateWithQuery:self.model.trimmedQuery]; + } +} + +- (void)userSearchModel:(DWUserSearchModel *)model completedWithItems:(NSArray> *)items { + if (items.count > 0) { + self.resultsController.searchQuery = model.trimmedQuery; + self.resultsController.items = items; + [self dw_embedChild:self.resultsController inContainer:self.contentView]; + [self updateContentKeyboardConstraintsIfNeeded]; + } + else { + [self.resultsController dw_detachFromParent]; + [self.stateController setNoResultsGlobalStateWithQuery:self.model.trimmedQuery]; + } +} + +- (void)userSearchModel:(DWUserSearchModel *)model completedWithError:(NSError *)error { + [self.resultsController dw_detachFromParent]; + [self.stateController setErrorState]; +} + +#pragma mark - DWUserSearchResultViewControllerDelegate + +- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller + willDisplayItemAtIndex:(NSInteger)index { + [self.model willDisplayItemAtIndex:index]; +} + +- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller + didSelectItemAtIndex:(NSInteger)index + cell:(UICollectionViewCell *)cell { + id item = [self.model itemAtIndex:index]; + if (!item) { + return; + } + + if (![self.model canOpenBlockchainIdentity:item.blockchainIdentity]) { + [cell dw_shakeView]; + return; + } + + DWUserProfileViewController *profileController = + [[DWUserProfileViewController alloc] initWithItem:item + payModel:self.payModel + dataProvider:self.dataProvider]; + [self.navigationController pushViewController:profileController animated:YES]; +} + +- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller + acceptContactRequest:(id)item { + [self.model acceptContactRequest:item]; +} + +- (void)userSearchResultViewController:(DWUserSearchResultViewController *)controller + declineContactRequest:(id)item { + [self.model declineContactRequest:item]; +} + +#pragma mark - Keyboard + +- (void)ka_keyboardShowOrHideAnimationWithHeight:(CGFloat)height + animationDuration:(NSTimeInterval)animationDuration + animationCurve:(UIViewAnimationCurve)animationCurve { + NSLayoutConstraint *constraint = [self.stateController.view dw_findConstraintWithAttribute:NSLayoutAttributeBottom]; + constraint.constant = height; + [self updateContentKeyboardConstraintsIfNeeded]; + [self.view layoutIfNeeded]; +} + +- (void)updateContentKeyboardConstraintsIfNeeded { + NSLayoutConstraint *constraint = [self.resultsController.view dw_findConstraintWithAttribute:NSLayoutAttributeBottom]; + if (self.ka_keyboardHeight > 0) { + constraint.constant = self.ka_keyboardHeight; // - DW_TABBAR_HEIGHT; + } + else { + constraint.constant = 0; + } +} + +#pragma mark - Private + +- (DWUserSearchModel *)model { + if (_model == nil) { + DWUserSearchModel *model = [[DWUserSearchModel alloc] init]; + model.delegate = self; + model.context = self; + _model = model; + } + return _model; +} + +- (DWSearchStateViewController *)stateController { + if (_stateController == nil) { + _stateController = [[DWSearchStateViewController alloc] init]; + } + return _stateController; +} + +- (DWUserSearchResultViewController *)resultsController { + if (_resultsController == nil) { + _resultsController = [[DWUserSearchResultViewController alloc] init]; + _resultsController.delegate = self; + } + return _resultsController; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.h b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.h new file mode 100644 index 000000000..fda455a48 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.h @@ -0,0 +1,54 @@ +// +// 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 + +#import "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWUserSearchModel; +@class DSBlockchainIdentity; + +@protocol DWUserSearchModelDelegate + +- (void)userSearchModelDidStartSearch:(DWUserSearchModel *)model; +- (void)userSearchModel:(DWUserSearchModel *)model completedWithItems:(NSArray> *)items; +- (void)userSearchModel:(DWUserSearchModel *)model completedWithError:(NSError *)error; + +@end + +@interface DWUserSearchModel : NSObject + +@property (readonly, nonatomic, copy) NSString *trimmedQuery; +@property (nullable, nonatomic, weak) id delegate; + +@property (nullable, nonatomic, weak) UIViewController *context; + +- (void)searchWithQuery:(NSString *)searchQuery; +- (void)willDisplayItemAtIndex:(NSInteger)index; + +- (id)itemAtIndex:(NSInteger)index; + +- (BOOL)canOpenBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; + +- (void)acceptContactRequest:(id)item; +- (void)declineContactRequest:(id)item; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.m b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.m new file mode 100644 index 000000000..be82f3762 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/GlobalSearch/Model/DWUserSearchModel.m @@ -0,0 +1,205 @@ +// +// 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 "DWUserSearchModel.h" + +#import "DWDPSearchItemsFactory.h" +#import "DWDashPayConstants.h" +#import "DWDashPayContactsActions.h" +#import "DWEnvironment.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserSearchRequest : NSObject + +@property (readonly, nonatomic, copy) NSString *trimmedQuery; +@property (nonatomic, assign) uint32_t offset; +@property (nullable, nonatomic, copy) NSArray> *items; +@property (nonatomic, assign) BOOL requestInProgress; +@property (nonatomic, assign) BOOL hasNextPage; +@property (nullable, nonatomic, copy) NSData *lastItem; + + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserSearchRequest + +- (instancetype)initWithTrimmedQuery:(NSString *)trimmedQuery { + self = [super init]; + if (self) { + _trimmedQuery = [trimmedQuery copy]; + _offset = 0; + } + return self; +} + +@end + +#pragma mark - Model + +NS_ASSUME_NONNULL_BEGIN + +static uint32_t const LIMIT = 100; +static NSTimeInterval SEARCH_DEBOUNCE_DELAY = 0.4; + +@interface DWUserSearchModel () + +@property (nullable, nonatomic, strong) DWUserSearchRequest *searchRequest; +@property (nullable, nonatomic, strong) id request; +@property (readonly, nonatomic, strong) DWDPSearchItemsFactory *itemsFactory; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserSearchModel + +- (instancetype)init { + self = [super init]; + if (self) { + _itemsFactory = [[DWDPSearchItemsFactory alloc] init]; + } + return self; +} + +- (NSString *)trimmedQuery { + return self.searchRequest.trimmedQuery ?: @""; +} + +- (void)searchWithQuery:(NSString *)searchQuery { + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(performInitialSearch) object:nil]; + + [self.request cancel]; + self.request = nil; + + self.searchRequest = nil; + + NSCharacterSet *whitespaces = [NSCharacterSet whitespaceCharacterSet]; + NSString *trimmedQuery = [searchQuery stringByTrimmingCharactersInSet:whitespaces] ?: @""; + if ([self.searchRequest.trimmedQuery isEqualToString:trimmedQuery]) { + return; + } + if (trimmedQuery.length < DW_MIN_USERNAME_LENGTH) { + return; + } + + self.searchRequest = [[DWUserSearchRequest alloc] initWithTrimmedQuery:trimmedQuery]; + + [self performSelector:@selector(performInitialSearch) withObject:nil afterDelay:SEARCH_DEBOUNCE_DELAY]; +} + +- (void)willDisplayItemAtIndex:(NSInteger)index { + const BOOL shouldRequestNextPage = self.searchRequest.items.count >= LIMIT && index >= self.searchRequest.items.count - LIMIT / 4; + if (shouldRequestNextPage && self.searchRequest.hasNextPage && !self.searchRequest.requestInProgress) { + self.searchRequest.offset += LIMIT; + [self performSearchAndNotify:NO]; + } +} + +- (id)itemAtIndex:(NSInteger)index { + if (index < 0 || self.searchRequest.items.count < index) { + NSAssert(NO, @"No blockchain identity for invalid index %ld", index); + return nil; + } + + return self.searchRequest.items[index]; +} + +- (BOOL)canOpenBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + return !uint256_eq(myBlockchainIdentity.uniqueID, blockchainIdentity.uniqueID); +} + +- (void)acceptContactRequest:(id)item { + __weak typeof(self) weakSelf = self; + [DWDashPayContactsActions + acceptContactRequest:item + context:self.context + completion:^(BOOL success, NSArray *_Nonnull errors) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + // TODO: DP update state more gently + [strongSelf performSearchAndNotify:YES]; + }]; +} + +- (void)declineContactRequest:(id)item { + [DWDashPayContactsActions declineContactRequest:item context:self.context completion:nil]; +} + +#pragma mark Private + +- (void)performInitialSearch { + [self performSearchAndNotify:YES]; +} + +- (void)performSearchAndNotify:(BOOL)notify { + if (notify) { + [self.delegate userSearchModelDidStartSearch:self]; + } + + if (self.searchRequest) { + [self performSearchWithQuery:self.searchRequest.trimmedQuery offset:self.searchRequest.offset]; + } +} + +- (void)performSearchWithQuery:(NSString *)query offset:(uint32_t)offset { + self.searchRequest.requestInProgress = YES; + + DSIdentitiesManager *manager = [DWEnvironment sharedInstance].currentChainManager.identitiesManager; + __weak typeof(self) weakSelf = self; + self.request = [manager searchIdentitiesByNamePrefix:query + inDomain:@"dash" + startAfter:self.searchRequest.lastItem + limit:LIMIT + withCompletion:^(BOOL success, NSArray *_Nullable blockchainIdentities, NSArray *_Nonnull errors) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + // search query was changed before results arrive, ignore results + if (!strongSelf.searchRequest || ![strongSelf.searchRequest.trimmedQuery isEqualToString:query]) { + return; + } + strongSelf.searchRequest.requestInProgress = NO; + if (success) { + NSMutableArray> *items = strongSelf.searchRequest.items ? [strongSelf.searchRequest.items mutableCopy] : [NSMutableArray array]; + for (DSBlockchainIdentity *blockchainIdentity in blockchainIdentities) { + id item = [strongSelf.itemsFactory itemForBlockchainIdentity:blockchainIdentity]; + [items addObject:item]; + } + strongSelf.searchRequest.hasNextPage = blockchainIdentities.count >= LIMIT; + strongSelf.searchRequest.items = items; + strongSelf.searchRequest.lastItem = [[[items lastObject] blockchainIdentity] uniqueIDData]; + [strongSelf.delegate userSearchModel:strongSelf completedWithItems:items]; + } + else { + strongSelf.searchRequest.hasNextPage = NO; + [strongSelf.delegate userSearchModel:strongSelf completedWithError:errors.firstObject]; + } + }]; + +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel+DWProtected.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel+DWProtected.h new file mode 100644 index 000000000..b07e84e14 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel+DWProtected.h @@ -0,0 +1,47 @@ +// +// 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 + +#import "DWBaseContactsModel.h" + +#import "DWFetchedResultsDataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWDPContactsItemsFactory; + +@interface DWBaseContactsModel () + +@property (readonly, nonatomic, strong) DWFetchedResultsDataSource *requestsDataSource; +@property (readonly, nonatomic, strong) DWFetchedResultsDataSource *contactsDataSource; + +@property (nullable, nonatomic, strong) id allDataSource; +@property (nullable, nonatomic, strong) id searchDataSource; + +@property (readonly, nonatomic, strong) DWDPContactsItemsFactory *itemsFactory; +@property (nullable, nonatomic, copy) NSString *trimmedQuery; + +@property (nonatomic, assign, getter=isActive) BOOL active; + +- (void)rebuildFRCDataSources; + +- (BOOL)shouldFetchData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.h new file mode 100644 index 000000000..e02cb8295 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.h @@ -0,0 +1,54 @@ +// +// 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 +#import + +#import "DWContactsDataSource.h" +#import "DWContactsSortModeProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWBaseContactsModel; + +@protocol DWContactsModelDelegate + +- (void)contactsModelDidUpdate:(DWBaseContactsModel *)model; + +@end + +@interface DWBaseContactsModel : NSObject + +@property (readonly, nonatomic, assign) BOOL hasBlockchainIdentity; +@property (readonly, nonatomic, strong) id dataSource; +@property (nullable, nonatomic, weak) id delegate; + +@property (nonatomic, assign) DWContactsSortMode sortMode; + +@property (nullable, nonatomic, weak) UIViewController *context; + +- (void)start; +- (void)stop; + +- (void)acceptContactRequest:(id)item; +- (void)declineContactRequest:(id)item; + +- (void)searchWithQuery:(NSString *)searchQuery; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.m new file mode 100644 index 000000000..472c226f7 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWBaseContactsModel.m @@ -0,0 +1,163 @@ +// +// 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 "DWBaseContactsModel+DWProtected.h" + +#import "DWContactsDataSourceObject.h" +#import "DWContactsSearchDataSourceObject.h" +#import "DWDPContactsItemsFactory.h" +#import "DWDashPayContactsActions.h" +#import "DWDashPayContactsUpdater.h" +#import "DWEnvironment.h" + +@implementation DWBaseContactsModel + +@synthesize sortMode; + +- (instancetype)init { + self = [super init]; + if (self) { + _itemsFactory = [[DWDPContactsItemsFactory alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didUpdateContacts) + name:DWDashPayContactsDidUpdateNotification + object:nil]; + } + return self; +} + +- (BOOL)hasBlockchainIdentity { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + return myBlockchainIdentity != nil; +} + +- (id)dataSource { + return [self isSearching] ? self.searchDataSource : self.allDataSource; +} + +- (void)rebuildFRCDataSources { + // to be overriden + // create FRC-backed datasources here +} + +- (BOOL)shouldFetchData { + return YES; +} + +- (void)start { + self.active = YES; + + if ([self shouldFetchData]) { + [[DWDashPayContactsUpdater sharedInstance] fetch]; + } + + if (!self.requestsDataSource) { + [self rebuildFRCDataSources]; + } + + [self.requestsDataSource start]; + [self.contactsDataSource start]; + + [self updateForced:YES]; +} + +- (void)stop { + self.active = NO; + + [self.requestsDataSource stop]; + [self.contactsDataSource stop]; +} + +- (void)acceptContactRequest:(id)item { + [DWDashPayContactsActions acceptContactRequest:item context:self.context completion:nil]; +} + +- (void)declineContactRequest:(id)item { + [DWDashPayContactsActions declineContactRequest:item context:self.context completion:nil]; +} + +- (void)searchWithQuery:(NSString *)searchQuery { + NSCharacterSet *whitespaces = [NSCharacterSet whitespaceCharacterSet]; + NSString *trimmedQuery = [searchQuery stringByTrimmingCharactersInSet:whitespaces] ?: @""; + if ([self.trimmedQuery isEqualToString:trimmedQuery]) { + return; + } + + self.trimmedQuery = trimmedQuery; + + [self updateForced:NO]; +} + +#pragma mark - DWFetchedResultsDataSourceDelegate + +- (void)fetchedResultsDataSourceDidUpdate:(DWFetchedResultsDataSource *)fetchedResultsDataSource { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + [self updateForced:YES]; +} + +#pragma mark - NSFetchedResultsControllerDelegate + +- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + [self updateForced:YES]; +} + +#pragma mark - Private + +- (void)updateForced:(BOOL)forced { + NSFetchedResultsController *requestsFRC = self.requestsDataSource.fetchedResultsController; + requestsFRC.delegate = self; + NSFetchedResultsController *contactsFRC = self.contactsDataSource.fetchedResultsController; + contactsFRC.delegate = self; + + if (forced) { + self.allDataSource = [[DWContactsDataSourceObject alloc] initWithRequestsFRC:requestsFRC + contactsFRC:contactsFRC + itemsFactory:self.itemsFactory + sortMode:self.sortMode]; + } + + if (self.isSearching) { + self.searchDataSource = [[DWContactsSearchDataSourceObject alloc] initWithContactRequestsFRC:requestsFRC + contactsFRC:contactsFRC + itemsFactory:self.itemsFactory + trimmedQuery:self.trimmedQuery]; + } + else { + self.searchDataSource = nil; + } + + NSParameterAssert(self.delegate); + [self.delegate contactsModelDidUpdate:self]; +} + +- (BOOL)isSearching { + return self.trimmedQuery.length > 0; +} + +- (void)didUpdateContacts { + if (self.isActive) { + [self stop]; + [self start]; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.h new file mode 100644 index 000000000..f92df43ff --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.h @@ -0,0 +1,35 @@ +// +// 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 "DWBaseContactsModel.h" + +#import "DWUserSearchModel.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWRequestsModel; + +@interface DWContactsModel : DWBaseContactsModel + +@property (readonly, nonatomic, strong) DWUserSearchModel *globalSearchModel; + +- (BOOL)canOpenBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; +- (DWRequestsModel *)contactRequestsModel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.m new file mode 100644 index 000000000..3a61194aa --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsModel.m @@ -0,0 +1,68 @@ +// +// 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 "DWContactsModel.h" + +#import "DWBaseContactsModel+DWProtected.h" +#import "DWContactsDataSourceObject.h" +#import "DWContactsFetchedDataSource.h" +#import "DWEnvironment.h" +#import "DWIncomingFetchedDataSource.h" +#import "DWRequestsModel.h" + +@implementation DWContactsModel + +@synthesize requestsDataSource = _requestsDataSource; +@synthesize contactsDataSource = _contactsDataSource; + +- (instancetype)init { + self = [super init]; + if (self) { + _globalSearchModel = [[DWUserSearchModel alloc] init]; + [self rebuildFRCDataSources]; + } + return self; +} + +- (BOOL)canOpenBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + return !uint256_eq(myBlockchainIdentity.uniqueID, blockchainIdentity.uniqueID); +} + +- (DWRequestsModel *)contactRequestsModel { + return [[DWRequestsModel alloc] initWithRequestsDataSource:self.requestsDataSource]; +} + +- (void)rebuildFRCDataSources { + DSBlockchainIdentity *blockchainIdentity = [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; + if (!blockchainIdentity) { + return; + } + + NSManagedObjectContext *context = [NSManagedObjectContext viewContext]; + + _requestsDataSource = [[DWIncomingFetchedDataSource alloc] initWithBlockchainIdentity:blockchainIdentity inContext:context]; + _requestsDataSource.shouldSubscribeToNotifications = YES; + _requestsDataSource.delegate = self; + + _contactsDataSource = [[DWContactsFetchedDataSource alloc] initWithBlockchainIdentity:blockchainIdentity inContext:context]; + _contactsDataSource.shouldSubscribeToNotifications = YES; + _contactsDataSource.delegate = self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsSortModeProtocol.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsSortModeProtocol.h new file mode 100644 index 000000000..8f0d6a1e0 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DWContactsSortModeProtocol.h @@ -0,0 +1,32 @@ +// +// 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 + +typedef NS_ENUM(NSUInteger, DWContactsSortMode) { + DWContactsSortMode_ByUsername, +}; + +@protocol DWContactsSortModeProtocol + +@property (readonly, nonatomic, assign) DWContactsSortMode sortMode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSource.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSource.h new file mode 100644 index 000000000..2ad16b271 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSource.h @@ -0,0 +1,41 @@ +// +// 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 + +#import "DWContactsSortModeProtocol.h" +#import "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol DWContactsDataSource + +@property (readonly, nonatomic, assign, getter=isEmpty) BOOL empty; + +@property (readonly, nonatomic, assign, getter=isSearching) BOOL searching; +@property (readonly, nullable, nonatomic, copy) NSString *trimmedQuery; + +/// First section +@property (readonly, nonatomic, assign) NSUInteger requestsCount; +/// Second section +@property (readonly, nonatomic, assign) NSUInteger contactsCount; + +- (id)itemAtIndexPath:(NSIndexPath *)indexPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.h new file mode 100644 index 000000000..c5fd9beb2 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.h @@ -0,0 +1,37 @@ +// +// 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 "DWContactsDataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWDPContactsItemsFactory; +@class NSFetchedResultsController; + +@interface DWContactsDataSourceObject : NSObject + +- (instancetype)initWithRequestsFRC:(nullable NSFetchedResultsController *)contactRequestsFRC + contactsFRC:(nullable NSFetchedResultsController *)contactsFRC + itemsFactory:(DWDPContactsItemsFactory *)itemsFactory + sortMode:(DWContactsSortMode)sortMode; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.m new file mode 100644 index 000000000..ab2ec215c --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsDataSourceObject.m @@ -0,0 +1,91 @@ +// +// 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 "DWContactsDataSourceObject.h" + +#import "DWDPContactsItemsFactory.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWContactsDataSourceObject () + +@property (nullable, readonly, nonatomic, strong) NSFetchedResultsController *requestsFRC; +@property (nullable, readonly, nonatomic, strong) NSFetchedResultsController *contactsFRC; +@property (readonly, nonatomic, strong) DWDPContactsItemsFactory *itemsFactory; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWContactsDataSourceObject + +@synthesize sortMode = _sortMode; + +- (instancetype)initWithRequestsFRC:(NSFetchedResultsController *)requestsFRC + contactsFRC:(NSFetchedResultsController *)contactsFRC + itemsFactory:(DWDPContactsItemsFactory *)itemsFactory + sortMode:(DWContactsSortMode)sortMode { + self = [super init]; + if (self) { + _requestsFRC = requestsFRC; + _contactsFRC = contactsFRC; + _itemsFactory = itemsFactory; + _sortMode = sortMode; + } + return self; +} + +- (BOOL)isEmpty { + if (self.requestsFRC == nil && self.contactsFRC == nil) { + return YES; + } + + const NSInteger count = self.requestsCount + self.contactsCount; + return count == 0; +} + +- (BOOL)isSearching { + return NO; +} + +- (NSString *)trimmedQuery { + return nil; +} + +- (NSUInteger)requestsCount { + return self.requestsFRC.sections.firstObject.numberOfObjects; +} + +- (NSUInteger)contactsCount { + return self.contactsFRC.sections.firstObject.numberOfObjects; +} + +- (id)itemAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == 0) { + NSManagedObject *entity = [self.requestsFRC objectAtIndexPath:indexPath]; + id item = [self.itemsFactory itemForEntity:entity]; + return item; + } + else { + NSIndexPath *transformedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:0]; + NSManagedObject *entity = [self.contactsFRC objectAtIndexPath:transformedIndexPath]; + id item = [self.itemsFactory itemForEntity:entity]; + return item; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.h new file mode 100644 index 000000000..84a1903ae --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.h @@ -0,0 +1,39 @@ +// +// 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 + +#import "DWContactsDataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWDPContactsItemsFactory; +@class NSFetchedResultsController; + +@interface DWContactsSearchDataSourceObject : NSObject + +- (instancetype)initWithContactRequestsFRC:(NSFetchedResultsController *)contactRequestsFRC + contactsFRC:(NSFetchedResultsController *)contactsFRC + itemsFactory:(DWDPContactsItemsFactory *)itemsFactory + trimmedQuery:(NSString *)trimmedQuery; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.m new file mode 100644 index 000000000..e0d6ff756 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/DataSource/DWContactsSearchDataSourceObject.m @@ -0,0 +1,105 @@ +// +// 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 "DWContactsSearchDataSourceObject.h" + +#import "DWContactsDataSource.h" +#import "DWDPContactsItemsFactory.h" +#import "NSPredicate+DWFullTextSearch.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DWContactsSearchDataSourceObject () + +@property (nullable, nonatomic, copy) NSArray> *filteredContactRequest; +@property (nullable, nonatomic, copy) NSArray> *filteredContacts; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWContactsSearchDataSourceObject + +@synthesize trimmedQuery = _trimmedQuery; + +- (instancetype)initWithContactRequestsFRC:(NSFetchedResultsController *)contactRequestsFRC + contactsFRC:(NSFetchedResultsController *)contactsFRC + itemsFactory:(DWDPContactsItemsFactory *)itemsFactory + trimmedQuery:(NSString *)trimmedQuery { + self = [super init]; + if (self) { + NSArray> *contactRequests = [self.class itemsWithFactory:itemsFactory frc:contactRequestsFRC]; + NSArray> *contacts = [self.class itemsWithFactory:itemsFactory frc:contactsFRC]; + _filteredContactRequest = [self.class filterItems:contactRequests trimmedQuery:trimmedQuery]; + _filteredContacts = [self.class filterItems:contacts trimmedQuery:trimmedQuery]; + _trimmedQuery = [trimmedQuery copy]; + } + return self; +} + +- (BOOL)isEmpty { + const NSInteger count = self.requestsCount + self.contactsCount; + return count == 0; +} + +- (BOOL)isSearching { + return YES; +} + +- (DWContactsSortMode)sortMode { + return DWContactsSortMode_ByUsername; +} + +- (NSUInteger)requestsCount { + return self.filteredContactRequest.count; +} + +- (NSUInteger)contactsCount { + return self.filteredContacts.count; +} + +- (id)itemAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == 0) { + return self.filteredContactRequest[indexPath.row]; + } + else { + return self.filteredContacts[indexPath.row]; + } +} + +#pragma mark - Private + ++ (NSArray> *)filterItems:(NSArray> *)items trimmedQuery:(NSString *)trimmedQuery { + id item = nil; + NSArray *searchKeyPaths = @[ DW_KEYPATH(item, username), DW_KEYPATH(item, displayName) ]; + NSPredicate *predicate = [NSPredicate dw_searchPredicateForTrimmedQuery:trimmedQuery + searchKeyPaths:searchKeyPaths]; + return [items filteredArrayUsingPredicate:predicate]; +} + ++ (NSArray> *)itemsWithFactory:(DWDPContactsItemsFactory *)factory frc:(NSFetchedResultsController *)frc { + NSMutableArray> *items = [NSMutableArray array]; + for (NSManagedObject *entity in frc.fetchedObjects) { + id item = [factory itemForEntity:entity]; + [items addObject:item]; + } + return [items copy]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.h new file mode 100644 index 000000000..e27c4553b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.h @@ -0,0 +1,35 @@ +// +// 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 "DWFetchedResultsDataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSBlockchainIdentity; + +@interface DWContactsFetchedDataSource : DWFetchedResultsDataSource + +@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + inContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.m new file mode 100644 index 000000000..06c579d56 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWContactsFetchedDataSource.m @@ -0,0 +1,49 @@ +// +// 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 "DWContactsFetchedDataSource.h" + +#import "DWEnvironment.h" + +@implementation DWContactsFetchedDataSource + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + inContext:(NSManagedObjectContext *)context { + self = [super initWithContext:context]; + if (self) { + _blockchainIdentity = blockchainIdentity; + } + return self; +} + +- (NSString *)entityName { + return NSStringFromClass(DSDashpayUserEntity.class); +} + +- (NSPredicate *)predicate { + return [NSPredicate + predicateWithFormat: + @"ANY friends == %@", + [self.blockchainIdentity matchingDashpayUserInContext:self.context]]; +} + +- (NSArray *)sortDescriptors { + NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"associatedBlockchainIdentity.dashpayUsername.stringValue" ascending:YES]; + return @[ sortDescriptor ]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.h new file mode 100644 index 000000000..323caf974 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.h @@ -0,0 +1,56 @@ +// +// 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 +#import + +NS_ASSUME_NONNULL_BEGIN + +@class DWFetchedResultsDataSource; + +@protocol DWFetchedResultsDataSourceDelegate + +- (void)fetchedResultsDataSourceDidUpdate:(DWFetchedResultsDataSource *)fetchedResultsDataSource; + +@end + +@interface DWFetchedResultsDataSource : NSObject + +@property (nonatomic, assign) BOOL shouldSubscribeToNotifications; + +@property (readonly, nonatomic, strong) NSManagedObjectContext *context; +@property (readonly, nonatomic, copy) NSString *entityName; +@property (readonly, nonatomic, strong) NSPredicate *predicate; +@property (nullable, readonly, nonatomic, copy) NSString *sectionNameKeyPath; +@property (nullable, readonly, nonatomic, strong) NSPredicate *invertedPredicate; +@property (nullable, readonly, nonatomic, copy) NSArray *sortDescriptors; + +@property (null_resettable, nonatomic, strong) NSFetchedResultsController *fetchedResultsController; + +@property (nullable, nonatomic, strong) id delegate; + +- (void)start; +- (void)stop; + +- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.m new file mode 100644 index 000000000..048ee704c --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWFetchedResultsDataSource.m @@ -0,0 +1,198 @@ +// +// 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 "DWFetchedResultsDataSource.h" + +#import +#import + +static NSUInteger const FETCH_BATCH_SIZE = 20; + +NS_ASSUME_NONNULL_BEGIN + +@interface DWFetchedResultsDataSource () + +@property (nonatomic, assign) BOOL subscribedToNotifications; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWFetchedResultsDataSource + +- (instancetype)initWithContext:(NSManagedObjectContext *)context { + self = [super init]; + if (self) { + _context = context; + } + return self; +} + +- (NSString *)entityName { + NSAssert(NO, @"Must be overriden"); + return nil; +} + +- (NSPredicate *)predicate { + NSAssert(NO, @"Must be overriden"); + return [NSPredicate predicateWithValue:YES]; +} + +- (NSString *)sectionNameKeyPath { + return nil; +} + +- (NSPredicate *)invertedPredicate { + return nil; +} + +- (NSArray *)sortDescriptors { + return nil; +} + +- (void)start { + NSParameterAssert(self.predicate); + NSParameterAssert(self.sortDescriptors); + // invertedPredicate is not mandatory + + if (self.shouldSubscribeToNotifications && !self.subscribedToNotifications) { + self.subscribedToNotifications = YES; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(backgroundManagedObjectContextDidSaveNotification:) + name:NSManagedObjectContextDidSaveNotification + object:self.context]; + } + + [self fetchedResultsController]; +} + +- (void)stop { + self.fetchedResultsController = nil; + + if (self.shouldSubscribeToNotifications && self.subscribedToNotifications) { + self.subscribedToNotifications = NO; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSManagedObjectContextDidSaveNotification + object:self.context]; + } +} + +- (NSFetchedResultsController *)fetchedResultsController { + if (_fetchedResultsController != nil) { + return _fetchedResultsController; + } + + DDLogVerbose(@"DWDP: Constructing FRC for %@", self.entityName); + + NSManagedObjectContext *context = self.context; + + NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; + fetchRequest.entity = [NSEntityDescription entityForName:self.entityName inManagedObjectContext:context]; + fetchRequest.fetchBatchSize = FETCH_BATCH_SIZE; + fetchRequest.sortDescriptors = self.sortDescriptors; + fetchRequest.predicate = self.predicate; + + NSFetchedResultsController *fetchedResultsController = + [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest + managedObjectContext:context + sectionNameKeyPath:self.sectionNameKeyPath + cacheName:nil]; + _fetchedResultsController = fetchedResultsController; + NSError *error = nil; + if (![fetchedResultsController performFetch:&error]) { + // Replace this implementation with code to handle the error appropriately. + // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + DSLog(@"Unresolved error %@, %@", error, [error userInfo]); + abort(); + } + + return _fetchedResultsController; +} + +#pragma mark - Private + +- (NSPredicate *)classPredicate { + return [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", NSClassFromString(self.entityName)]; +} + +- (NSPredicate *)predicateInContext { + return [self.predicate predicateInContext:self.context]; +} + +- (NSPredicate *)invertedPredicateInContext { + return [self.invertedPredicate predicateInContext:self.context]; +} + +- (NSPredicate *)fullPredicateInContext { + return [NSCompoundPredicate andPredicateWithSubpredicates:@[ [self classPredicate], [self predicateInContext] ]]; +} + +- (NSPredicate *)fullInvertedPredicateInContext { + return [NSCompoundPredicate andPredicateWithSubpredicates:@[ [self classPredicate], [self invertedPredicateInContext] ]]; +} + +- (void)backgroundManagedObjectContextDidSaveNotification:(NSNotification *)notification { + BOOL (^objectsHaveChanged)(NSSet *) = ^BOOL(NSSet *objects) { + NSSet *foundObjects = [objects filteredSetUsingPredicate:[self fullPredicateInContext]]; + if (foundObjects.count) { + return YES; + } + return NO; + }; + + BOOL (^objectsHaveChangedInverted)(NSSet *) = ^BOOL(NSSet *objects) { + if (!self.invertedPredicate) { + return NO; + } + NSSet *foundObjects = [objects filteredSetUsingPredicate:[self fullInvertedPredicateInContext]]; + if (foundObjects.count) { + return YES; + } + return NO; + }; + + + NSSet *insertedObjects = notification.userInfo[NSInsertedObjectsKey]; + NSSet *updatedObjects = notification.userInfo[NSUpdatedObjectsKey]; + NSSet *deletedObjects = notification.userInfo[NSDeletedObjectsKey]; + BOOL inserted = NO; + BOOL updated = NO; + BOOL deleted = NO; + BOOL insertedInverted = NO; + BOOL deletedInverted = NO; + if ((inserted = objectsHaveChanged(insertedObjects)) || + (updated = objectsHaveChanged(updatedObjects)) || + (deleted = objectsHaveChanged(deletedObjects)) || + (insertedInverted = objectsHaveChangedInverted(insertedObjects)) || + (deletedInverted = objectsHaveChangedInverted(deletedObjects))) { + if (inserted || updated || deleted) { + insertedInverted = objectsHaveChangedInverted(insertedObjects); + deletedInverted = objectsHaveChangedInverted(deletedObjects); + } + [self.context mergeChangesFromContextDidSaveNotification:notification]; + if (insertedInverted || deletedInverted) { + self.fetchedResultsController = nil; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate fetchedResultsDataSourceDidUpdate:self]; + }); + } + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.h b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.h new file mode 100644 index 000000000..9dcaa710e --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.h @@ -0,0 +1,35 @@ +// +// 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 "DWFetchedResultsDataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSBlockchainIdentity; + +@interface DWIncomingFetchedDataSource : DWFetchedResultsDataSource + +@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + inContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.m b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.m new file mode 100644 index 000000000..5f2002c39 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Models/Fetched DataSource/DWIncomingFetchedDataSource.m @@ -0,0 +1,58 @@ +// +// 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 "DWIncomingFetchedDataSource.h" + +#import "DWEnvironment.h" + +@implementation DWIncomingFetchedDataSource + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + inContext:(NSManagedObjectContext *)context { + self = [super initWithContext:context]; + if (self) { + _blockchainIdentity = blockchainIdentity; + } + return self; +} + +- (NSString *)entityName { + return NSStringFromClass(DSFriendRequestEntity.class); +} + +- (NSPredicate *)predicate { + return [NSPredicate + predicateWithFormat: + @"destinationContact == %@ && (SUBQUERY(destinationContact.outgoingRequests, $friendRequest, $friendRequest.destinationContact == SELF.sourceContact).@count == 0)", + [self.blockchainIdentity matchingDashpayUserInContext:self.context]]; +} + +- (NSPredicate *)invertedPredicate { + return [NSPredicate + predicateWithFormat: + @"sourceContact == %@ && (SUBQUERY(sourceContact.incomingRequests, $friendRequest, $friendRequest.sourceContact == SELF.destinationContact).@count > 0)", + [self.blockchainIdentity matchingDashpayUserInContext:self.context]]; +} + +- (NSArray *)sortDescriptors { + NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] + initWithKey:@"sourceContact.associatedBlockchainIdentity.dashpayUsername.stringValue" + ascending:YES]; + return @[ sortDescriptor ]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.m index fabcd61ac..60a86e8ac 100644 --- a/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.m +++ b/DashWallet/Sources/UI/DashPay/Contacts/Placeholders/DWNoContactsViewController.m @@ -40,7 +40,7 @@ - (DWInvitationSuggestionView *)invitationView { if (!_invitationView) { _invitationView = [[DWInvitationSuggestionView alloc] init]; _invitationView.translatesAutoresizingMaskIntoConstraints = NO; - _invitationView.alpha = [DWGlobalOptions sharedInstance].dpInvitationFlowEnabled ? 1.0 : 0.0; + //_invitationView.alpha = [DWGlobalOptions sharedInstance].dpInvitationFlowEnabled ? 1.0 : 0.0; TODO: DashPay } return _invitationView; } diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.h new file mode 100644 index 000000000..f88e39f1a --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.h @@ -0,0 +1,26 @@ +// +// 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 "DWBaseContactsContentViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWRequestsContentViewController : DWBaseContactsContentViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.m new file mode 100644 index 000000000..b35eb6af1 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsContentViewController.m @@ -0,0 +1,30 @@ +// +// 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 "DWRequestsContentViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWRequestsContentViewController () + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWRequestsContentViewController + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.h b/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.h new file mode 100644 index 000000000..07c2b0e49 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.h @@ -0,0 +1,39 @@ +// +// 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 "DWBaseContactsViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWRequestsModel; + +@interface DWRequestsViewController : DWBaseContactsViewController + +@property (nullable, nonatomic, weak) id contentDelegate; + +- (instancetype)initWithModel:(DWRequestsModel *)model + payModel:(id)payModel + dataProvider:(id)dataProvider; + +- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; +- (instancetype)initWithCoder:(nullable NSCoder *)coder NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.m b/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.m new file mode 100644 index 000000000..cb1206528 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Requests/DWRequestsViewController.m @@ -0,0 +1,72 @@ +// +// 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 "DWRequestsViewController.h" + +#import "DWBaseContactsViewController+DWProtected.h" +#import "DWRequestsContentViewController.h" +#import "DWRequestsModel.h" + +@implementation DWRequestsViewController + +@synthesize model = _model; +@synthesize stateController = _stateController; +@synthesize contentController = _contentController; + +- (instancetype)initWithModel:(DWRequestsModel *)model + payModel:(id)payModel + dataProvider:(id)dataProvider { + self = [super initWithPayModel:payModel dataProvider:dataProvider]; + if (self) { + _model = model; + _model.delegate = self; + _model.context = self; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = NSLocalizedString(@"Contact Requests", nil); + self.searchBar.placeholder = NSLocalizedString(@"Search for a contact request", nil); +} + +#pragma mark - Private + +- (DWSearchStateViewController *)stateController { + if (_stateController == nil) { + _stateController = [[DWSearchStateViewController alloc] init]; + _stateController.delegate = self; + } + return _stateController; +} + +- (DWBaseContactsContentViewController *)contentController { + if (_contentController == nil) { + DWRequestsContentViewController *controller = + [[DWRequestsContentViewController alloc] initWithPayModel:self.payModel + dataProvider:self.dataProvider]; + controller.itemsDelegate = self; + controller.delegate = self.contentDelegate ?: self; + controller.dataSource = self.model.dataSource; + _contentController = controller; + } + return _contentController; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.h b/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.h new file mode 100644 index 000000000..bb2677325 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.h @@ -0,0 +1,33 @@ +// +// 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 "DWBaseContactsModel.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWFetchedResultsDataSource; + +@interface DWRequestsModel : DWBaseContactsModel + +- (instancetype)initWithRequestsDataSource:(DWFetchedResultsDataSource *)requestsDataSource NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.m b/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.m new file mode 100644 index 000000000..a3980e54f --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Requests/Models/DWRequestsModel.m @@ -0,0 +1,43 @@ +// +// 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 "DWRequestsModel.h" + +#import "DWBaseContactsModel+DWProtected.h" + +@implementation DWRequestsModel + +@synthesize requestsDataSource = _requestsDataSource; + +- (instancetype)initWithRequestsDataSource:(DWFetchedResultsDataSource *)requestsDataSource { + self = [super init]; + if (self) { + _requestsDataSource = requestsDataSource; + } + return self; +} + +- (DWFetchedResultsDataSource *)contactsDataSource { + // Ignored requests are not implemented + return nil; +} + +- (BOOL)shouldFetchData { + return NO; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.h b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.h new file mode 100644 index 000000000..736973b32 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.h @@ -0,0 +1,28 @@ +// +// 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 "BaseCollectionReusableView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWContactsSearchInfoHeaderView : BaseCollectionReusableView + +@property (readonly, nonatomic, strong) UILabel *titleLabel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.m b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.m new file mode 100644 index 000000000..eb3b3a636 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWContactsSearchInfoHeaderView.m @@ -0,0 +1,55 @@ +// +// 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 "DWContactsSearchInfoHeaderView.h" + +#import "DWUIKit.h" + +@implementation DWContactsSearchInfoHeaderView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UILabel *label = [[UILabel alloc] init]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.backgroundColor = self.backgroundColor; + label.textColor = [UIColor dw_darkTitleColor]; + label.numberOfLines = 0; + [self addSubview:label]; + _titleLabel = label; + + [label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + + const CGFloat spacing = 10.0; + const CGFloat margin = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [label.topAnchor constraintEqualToAnchor:self.topAnchor + constant:spacing], + [label.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:margin], + [self.trailingAnchor constraintEqualToAnchor:label.trailingAnchor + constant:margin], + [self.bottomAnchor constraintEqualToAnchor:label.bottomAnchor + constant:spacing], + ]]; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.h b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.h new file mode 100644 index 000000000..3b16d5a37 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.h @@ -0,0 +1,39 @@ +// +// 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 "BaseCollectionReusableView.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWTitleActionHeaderView; + +@protocol DWTitleActionHeaderViewDelegate + +- (void)titleActionHeaderView:(DWTitleActionHeaderView *)view buttonAction:(UIView *)sender; + +@end + +@interface DWTitleActionHeaderView : BaseCollectionReusableView + +@property (strong, nonatomic) IBOutlet UILabel *titleLabel; +@property (strong, nonatomic) IBOutlet UIButton *actionButton; + +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.m b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.m new file mode 100644 index 000000000..c3c69e2bb --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.m @@ -0,0 +1,72 @@ +// +// 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 "DWTitleActionHeaderView.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWTitleActionHeaderView () + +@property (weak, nonatomic) IBOutlet UIView *contentView; + +@end + +@implementation DWTitleActionHeaderView + +- (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.actionButton.titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; +} + +- (IBAction)buttonAction:(UIButton *)sender { + [self.delegate titleActionHeaderView:self buttonAction:sender]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.xib b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.xib new file mode 100644 index 000000000..aaea31346 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Contacts/Views/DWTitleActionHeaderView.xib @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DashWallet/Sources/UI/DashPay/DWDashPayConstants.h b/DashWallet/Sources/UI/DashPay/DWDashPayConstants.h new file mode 100644 index 000000000..1bf642471 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/DWDashPayConstants.h @@ -0,0 +1,28 @@ +// +// 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 + +extern uint64_t DWDP_MIN_BALANCE_TO_CREATE_USERNAME; +extern uint64_t DWDP_MIN_BALANCE_TO_CREATE_INVITE; +extern NSInteger DW_MIN_USERNAME_LENGTH; +extern NSInteger DW_MAX_USERNAME_LENGTH; + +extern NSString *const DWDP_THUMBNAIL_SERVER; +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/DWDashPayConstants.m b/DashWallet/Sources/UI/DashPay/DWDashPayConstants.m new file mode 100644 index 000000000..612873a92 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/DWDashPayConstants.m @@ -0,0 +1,28 @@ +// +// 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 "DWDashPayConstants.h" + +#import + +uint64_t DWDP_MIN_BALANCE_TO_CREATE_USERNAME = (DUFFS / 100); // 0.01 Dash +uint64_t DWDP_MIN_BALANCE_TO_CREATE_INVITE = (DUFFS / 100); // 0.01 Dash + +NSInteger DW_MIN_USERNAME_LENGTH = 3; +NSInteger DW_MAX_USERNAME_LENGTH = 24; + +NSString *const DWDP_THUMBNAIL_SERVER = @"http://54.74.4.114"; diff --git a/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.m b/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.m deleted file mode 100644 index b7b859021..000000000 --- a/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.m +++ /dev/null @@ -1,107 +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 "DWNetworkErrorViewController.h" - -#import "DWModalPopupTransition.h" -#import "DWNetworkUnavailableView.h" -#import "DWUIKit.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DWNetworkErrorViewController () - -@property (nonatomic, strong) DWModalPopupTransition *modalTransition; -@property (nonatomic, assign) DWErrorDescriptionType type; - -@end - -NS_ASSUME_NONNULL_END - -@implementation DWNetworkErrorViewController - -- (instancetype)initWithType:(DWErrorDescriptionType)type { - self = [super initWithNibName:nil bundle:nil]; - if (self) { - _type = type; - - _modalTransition = [[DWModalPopupTransition alloc] initWithInteractiveTransitionAllowed:NO]; - - self.transitioningDelegate = self.modalTransition; - self.modalPresentationStyle = UIModalPresentationCustom; - } - return self; -} - -- (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]; - - DWNetworkUnavailableView *errorView = [[DWNetworkUnavailableView alloc] initWithFrame:CGRectZero]; - errorView.translatesAutoresizingMaskIntoConstraints = NO; - switch (self.type) { - case DWErrorDescriptionType_Profile: - errorView.error = NSLocalizedString(@"Unable to fetch contact details", nil); - break; - case DWErrorDescriptionType_AcceptContactRequest: - errorView.error = NSLocalizedString(@"Unable to accept contact request", nil); - break; - case DWErrorDescriptionType_SendContactRequest: - errorView.error = NSLocalizedString(@"Unable to send contact request", nil); - break; - } - [contentView addSubview:errorView]; - - UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeSystem]; - closeButton.translatesAutoresizingMaskIntoConstraints = NO; - [closeButton setTitle:NSLocalizedString(@"Close", nil) forState:UIControlStateNormal]; - [closeButton addTarget:self action:@selector(closeButtonAction:) forControlEvents:UIControlEventTouchUpInside]; - [contentView addSubview:closeButton]; - - - [NSLayoutConstraint activateConstraints:@[ - [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], - [contentView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], - - [errorView.topAnchor constraintEqualToAnchor:contentView.topAnchor - constant:32.0], - [errorView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], - [errorView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], - - [closeButton.topAnchor constraintEqualToAnchor:errorView.bottomAnchor - constant:32.0], - [closeButton.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], - [contentView.bottomAnchor constraintEqualToAnchor:closeButton.bottomAnchor - constant:16.0], - [closeButton.heightAnchor constraintGreaterThanOrEqualToConstant:44.0], - ]]; -} - -- (void)closeButtonAction:(UIButton *)sender { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h b/DashWallet/Sources/UI/DashPay/Explore/DWExploreTestnetViewController.h similarity index 86% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h rename to DashWallet/Sources/UI/DashPay/Explore/DWExploreTestnetViewController.h index ec496b710..e022f39b2 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.h +++ b/DashWallet/Sources/UI/DashPay/Explore/DWExploreTestnetViewController.h @@ -15,11 +15,11 @@ // limitations under the License. // -#import "DWUsernameValidationRule.h" +#import NS_ASSUME_NONNULL_BEGIN -@interface DWFirstUsernameSymbolValidationRule : DWUsernameValidationRule +@interface DWExploreTestnetViewController : UIViewController @end diff --git a/DashWallet/Sources/UI/DashPay/Explore/DWExploreTestnetViewController.m b/DashWallet/Sources/UI/DashPay/Explore/DWExploreTestnetViewController.m new file mode 100644 index 000000000..d879c97a5 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Explore/DWExploreTestnetViewController.m @@ -0,0 +1,133 @@ +// +// 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 "DWExploreTestnetViewController.h" + +#import "DWColoredButton.h" +#import "DWEnvironment.h" +#import "DWExploreHeaderView.h" +#import "DWExploreTestnetContentsView.h" +#import "DWScrollingViewController.h" +#import "DWUIKit.h" + +@implementation DWExploreTestnetViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_darkBlueColor]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:contentView]; + + UIView *footerView = [[UIView alloc] init]; + footerView.translatesAutoresizingMaskIntoConstraints = NO; + footerView.backgroundColor = [UIColor dw_backgroundColor]; + [self.view addSubview:footerView]; + + DWColoredButton *actionButton = [[DWColoredButton alloc] init]; + actionButton.translatesAutoresizingMaskIntoConstraints = NO; + actionButton.style = DWColoredButtonStyle_Black; + [actionButton setTitle:NSLocalizedString(@"Get Test Dash", nil) forState:UIControlStateNormal]; + [actionButton addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside]; + [footerView addSubview:actionButton]; + + CGFloat padding = 16.0; + [NSLayoutConstraint activateConstraints:@[ + [contentView.topAnchor constraintEqualToAnchor:self.view.topAnchor], + [contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + + [footerView.topAnchor constraintEqualToAnchor:contentView.bottomAnchor], + [footerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:footerView.trailingAnchor], + [self.view.bottomAnchor constraintEqualToAnchor:footerView.bottomAnchor], + + [actionButton.topAnchor constraintEqualToAnchor:footerView.topAnchor + constant:padding], + [actionButton.leadingAnchor constraintEqualToAnchor:footerView.leadingAnchor + constant:padding], + [footerView.trailingAnchor constraintEqualToAnchor:actionButton.trailingAnchor + constant:padding], + [footerView.bottomAnchor constraintEqualToAnchor:actionButton.bottomAnchor + constant:padding], + + [actionButton.heightAnchor constraintEqualToConstant:46.0], + ]]; + + // Contents + + DWExploreHeaderView *headerView = [[DWExploreHeaderView alloc] init]; + headerView.translatesAutoresizingMaskIntoConstraints = NO; + headerView.image = [UIImage imageNamed:@"image.explore.dash.wallet"]; + headerView.title = NSLocalizedString(@"Explore Dash", nil); + headerView.subtitle = NSLocalizedString(@"Test Dash doesn’t have any value in the real world but you can send and receive it with other DashPay Alpha users.", nil); + + DWExploreTestnetContentsView *contentsView = [[DWExploreTestnetContentsView alloc] init]; + contentsView.translatesAutoresizingMaskIntoConstraints = NO; + + DWScrollingViewController *scrollingController = [[DWScrollingViewController alloc] init]; + scrollingController.keyboardNotificationsEnabled = NO; + + [self dw_embedChild:scrollingController inContainer:contentView]; + + UIView *parentView = scrollingController.contentView; + + UIView *overscrollView = [[UIView alloc] init]; + overscrollView.translatesAutoresizingMaskIntoConstraints = NO; + overscrollView.backgroundColor = [UIColor dw_backgroundColor]; + [parentView addSubview:overscrollView]; + + [parentView addSubview:headerView]; + [parentView addSubview:contentsView]; + + [NSLayoutConstraint activateConstraints:@[ + [headerView.topAnchor constraintEqualToAnchor:parentView.topAnchor], + [headerView.leadingAnchor constraintEqualToAnchor:parentView.leadingAnchor], + [parentView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor], + + [contentsView.topAnchor constraintEqualToAnchor:headerView.bottomAnchor], + [contentsView.leadingAnchor constraintEqualToAnchor:parentView.leadingAnchor], + [parentView.trailingAnchor constraintEqualToAnchor:contentsView.trailingAnchor], + [parentView.bottomAnchor constraintEqualToAnchor:contentsView.bottomAnchor], + + [overscrollView.topAnchor constraintEqualToAnchor:contentsView.bottomAnchor + constant:-10], + [overscrollView.leadingAnchor constraintEqualToAnchor:parentView.leadingAnchor], + [parentView.trailingAnchor constraintEqualToAnchor:overscrollView.trailingAnchor], + [overscrollView.heightAnchor constraintEqualToConstant:500], + ]]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (void)buttonAction { + DSAccount *account = [DWEnvironment sharedInstance].currentAccount; + NSString *paymentAddress = account.receiveAddress; + if (paymentAddress == nil) { + return; + } + + [UIPasteboard generalPasteboard].string = paymentAddress; + NSURL *url = [NSURL URLWithString:@"https://testnet-faucet.dash.org/"]; + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreHeaderView.h b/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreHeaderView.h new file mode 100644 index 000000000..7bfccc47a --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreHeaderView.h @@ -0,0 +1,30 @@ +// +// 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 + +@interface DWExploreHeaderView : UIView + +@property (nullable, nonatomic, strong) UIImage *image; +@property (nullable, nonatomic, copy) NSString *title; +@property (nullable, nonatomic, copy) NSString *subtitle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreHeaderView.m b/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreHeaderView.m new file mode 100644 index 000000000..57aa7d90a --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreHeaderView.m @@ -0,0 +1,119 @@ +// +// 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 "DWExploreHeaderView.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWExploreHeaderView () + +@property (readonly, nonatomic, strong) UIImageView *iconImageView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) UILabel *descLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWExploreHeaderView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_darkBlueColor]; + + UIImageView *iconImageView = [[UIImageView alloc] init]; + iconImageView.translatesAutoresizingMaskIntoConstraints = NO; + iconImageView.contentMode = UIViewContentModeCenter; + [self addSubview:iconImageView]; + _iconImageView = iconImageView; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.textColor = [UIColor whiteColor]; // always white + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleLargeTitle]; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.numberOfLines = 0; + [self addSubview:titleLabel]; + _titleLabel = titleLabel; + + UILabel *descLabel = [[UILabel alloc] init]; + descLabel.translatesAutoresizingMaskIntoConstraints = NO; + descLabel.textColor = [UIColor whiteColor]; // always white + descLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; + descLabel.textAlignment = NSTextAlignmentCenter; + descLabel.numberOfLines = 0; + [self addSubview:descLabel]; + _descLabel = descLabel; + + [iconImageView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [titleLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [descLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + + CGFloat padding = 16.0; + CGFloat spacing = 4.0; + [NSLayoutConstraint activateConstraints:@[ + [iconImageView.topAnchor constraintEqualToAnchor:self.topAnchor], + [iconImageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:iconImageView.trailingAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:iconImageView.bottomAnchor], + [titleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor + constant:padding], + + [descLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:spacing], + [descLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor + constant:padding], + [self.trailingAnchor constraintEqualToAnchor:descLabel.trailingAnchor + constant:padding], + [self.bottomAnchor constraintEqualToAnchor:descLabel.bottomAnchor + constant:padding], + ]]; + } + return self; +} + +- (UIImage *)image { + return self.iconImageView.image; +} + +- (void)setImage:(UIImage *)image { + self.iconImageView.image = image; +} + +- (NSString *)title { + return self.titleLabel.text; +} + +- (void)setTitle:(NSString *)title { + self.titleLabel.text = title; +} + +- (NSString *)subtitle { + return self.descLabel.text; +} + +- (void)setSubtitle:(NSString *)subtitle { + self.descLabel.text = subtitle; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreTestnetContentsView.h b/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreTestnetContentsView.h new file mode 100644 index 000000000..bc9a5b8cf --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreTestnetContentsView.h @@ -0,0 +1,26 @@ +// +// 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 + +@interface DWExploreTestnetContentsView : UIView + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreTestnetContentsView.m b/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreTestnetContentsView.m new file mode 100644 index 000000000..1fc0f5772 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Explore/Views/DWExploreTestnetContentsView.m @@ -0,0 +1,84 @@ +// +// 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 "DWExploreTestnetContentsView.h" + +#import "DWUIKit.h" + +@implementation DWExploreTestnetContentsView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_darkBlueColor]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = [UIColor dw_backgroundColor]; + contentView.layer.cornerRadius = 8.0; + contentView.layer.masksToBounds = YES; + contentView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner; // TL | TR + [self addSubview:contentView]; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.text = NSLocalizedString(@"How do I get Test Dash?", nil); + titleLabel.numberOfLines = 0; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + [contentView addSubview:titleLabel]; + + UILabel *descLabel = [[UILabel alloc] init]; + descLabel.translatesAutoresizingMaskIntoConstraints = NO; + descLabel.textColor = [UIColor dw_darkTitleColor]; + descLabel.text = NSLocalizedString(@"Test Dash is free and can be obtained from what is called a faucet.\nCopy an address from the Receive screen of your wallet and click on the button bellow to get your Dash.", nil); + descLabel.numberOfLines = 0; + descLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleCallout]; + [contentView addSubview:descLabel]; + + [titleLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [descLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + + CGFloat padding = 16.0; + CGFloat spacing = 4.0; + [NSLayoutConstraint activateConstraints:@[ + [contentView.topAnchor constraintEqualToAnchor:self.topAnchor], + [contentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:30], + [titleLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:padding], + [contentView.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor + constant:padding], + + [descLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:spacing], + [descLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor + constant:padding], + [contentView.trailingAnchor constraintEqualToAnchor:descLabel.trailingAnchor + constant:padding], + [contentView.bottomAnchor constraintEqualToAnchor:descLabel.bottomAnchor + constant:padding], + ]]; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.h b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.h new file mode 100644 index 000000000..eb683399c --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.h @@ -0,0 +1,36 @@ +// +// 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 "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDashPayContactsActions : NSObject + ++ (void)acceptContactRequest:(id)item + context:(UIViewController *)context + completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; ++ (void)declineContactRequest:(id)item + context:(UIViewController *)context + completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.m b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.m new file mode 100644 index 000000000..e08e94725 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsActions.m @@ -0,0 +1,112 @@ +// +// 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 "DWDashPayContactsActions.h" + +#import "DWDPBlockchainIdentityBackedItem.h" +#import "DWDPFriendRequestBackedItem.h" +#import "DWDPNewIncomingRequestItem.h" +#import "DWDashPayContactsUpdater.h" +#import "DWEnvironment.h" +#import "DWNetworkErrorViewController.h" +#import "DWNotificationsProvider.h" + + +@implementation DWDashPayContactsActions + ++ (void)acceptContactRequest:(id)item + context:(UIViewController *)context + completion:(void (^)(BOOL success, NSArray *errors))completion { + NSAssert([item conformsToProtocol:@protocol(DWDPNewIncomingRequestItem)], @"Incompatible item"); + + const BOOL isBlockchainIdentityBacked = [item conformsToProtocol:@protocol(DWDPBlockchainIdentityBackedItem)]; + const BOOL isFriendRequestBacked = [item conformsToProtocol:@protocol(DWDPFriendRequestBackedItem)]; + NSAssert(isBlockchainIdentityBacked || isFriendRequestBacked, @"Invalid item to accept contact request"); + + __block id newRequestItem = (id)item; + newRequestItem.requestState = DWDPNewIncomingRequestItemState_Processing; + + void (^resultCompletion)(BOOL success, NSArray *errors) = ^(BOOL success, NSArray *errors) { + if (newRequestItem == nil) + { + NSLog(@"324 %lu", (unsigned long)((id)newRequestItem).requestState); + } + + NSLog(@"%lu", (unsigned long)((id)newRequestItem).requestState); + + ((id)newRequestItem).requestState = success ? DWDPNewIncomingRequestItemState_Accepted : DWDPNewIncomingRequestItemState_Failed; + + if (!success) { + DWNetworkErrorViewController *controller = [[DWNetworkErrorViewController alloc] initWithType:DWErrorDescriptionType_AcceptContactRequest]; + [context presentViewController:controller animated:YES completion:nil]; + } + + // TODO: DP temp workaround to update and force reload contact list + // This will trigger DWNotificationsProvider to reset + [[DWDashPayContactsUpdater sharedInstance] fetchWithCompletion:^(BOOL contactsSuccess, NSArray *_Nonnull contactsErrors) { + if (completion) { + completion(success, errors); + } + }]; + + DSLog(@"DWDP: accept contact request %@: %@", success ? @"Succeeded" : @"Failed", errors); + }; + + // Accepting request from a DSFriendRequestEntity doesn't require searching for associated blockchain identity. + // Since all DWDPBasicUserItem has associated BI, check if it's a DSFriendRequestEntity first. + if (isFriendRequestBacked && [(id)item friendRequestEntity] != nil) { + id backedItem = (id)item; + [self acceptContactRequestFromFriendRequest:backedItem.friendRequestEntity completion:resultCompletion]; + } + else if (isBlockchainIdentityBacked && [(id)item blockchainIdentity] != nil) { + id backedItem = (id)item; + [self acceptContactRequestFromBlockchainIdentity:backedItem.blockchainIdentity completion:resultCompletion]; + } +} + ++ (void)declineContactRequest:(id)item + context:(UIViewController *)context + completion:(void (^)(BOOL success, NSArray *errors))completion { + // TODO: DP dummy method + + NSAssert([item conformsToProtocol:@protocol(DWDPNewIncomingRequestItem)], @"Incompatible item"); + + id newRequestItem = (id)item; + newRequestItem.requestState = DWDPNewIncomingRequestItemState_Processing; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + newRequestItem.requestState = DWDPNewIncomingRequestItemState_Failed; + }); +} + +#pragma mark - Private + ++ (void)acceptContactRequestFromBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + completion:(void (^)(BOOL success, NSArray *errors))completion { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + [myBlockchainIdentity acceptFriendRequestFromBlockchainIdentity:blockchainIdentity completion:completion]; +} + ++ (void)acceptContactRequestFromFriendRequest:(DSFriendRequestEntity *)friendRequest + completion:(void (^)(BOOL success, NSArray *errors))completion { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + [myBlockchainIdentity acceptFriendRequest:friendRequest completion:completion]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.h b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.h new file mode 100644 index 000000000..f3438e2b0 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.h @@ -0,0 +1,43 @@ +// +// 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 + +extern NSNotificationName const DWDashPayContactsDidUpdateNotification; + +@interface DWDashPayContactsUpdater : NSObject + +/// Subscribe to update contacts every 30 seconds +- (void)beginUpdating; +/// Unsubscribe from updating contacts +- (void)endUpdating; + +/// Initiate update immediately +- (void)fetch; +/// Initiate update immediately +- (void)fetchWithCompletion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; + ++ (instancetype)sharedInstance; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.m b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.m new file mode 100644 index 000000000..87fbbf961 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/DWDashPayContactsUpdater.m @@ -0,0 +1,160 @@ +// +// 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 "DWDashPayContactsUpdater.h" + +#import "DWEnvironment.h" + +NS_ASSUME_NONNULL_BEGIN + +NSNotificationName const DWDashPayContactsDidUpdateNotification = @"org.dash.wallet.dp.contacts-did-update"; + +static NSTimeInterval const UPDATE_INTERVAL = 30; + +@interface DWDashPayContactsUpdater () + +@property (nonatomic, assign, getter=isUpdating) BOOL updating; +@property (nonatomic, assign, getter=isFetching) BOOL fetching; + +@property (nullable, nonatomic, copy) void (^fetchCompletion)(BOOL success, NSArray *errors); + +@property (nonatomic, strong) NSDate *lastFetch; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDashPayContactsUpdater + ++ (instancetype)sharedInstance { + static DWDashPayContactsUpdater *_sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _sharedInstance = [[self alloc] init]; + }); + return _sharedInstance; +} + +- (void)beginUpdating { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + if (myBlockchainIdentity == nil || myBlockchainIdentity.registered == NO) { + return; + } + + if (self.isUpdating) { + return; + } + self.updating = YES; + + [self fetch]; +} + +- (void)endUpdating { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + if (!self.isUpdating) { + return; + } + self.updating = NO; + + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fetchInternal) object:nil]; +} + +- (void)fetch { + [self fetchIntiatedInternally:NO completion:nil]; +} + +- (void)fetchWithCompletion:(void (^_Nullable)(BOOL success, NSArray *errors))completion { + [self fetchIntiatedInternally:NO completion:completion]; +} + +#pragma mark - Private + +- (void)fetchInternal { + [self fetchIntiatedInternally:YES completion:nil]; +} + +- (void)fetchIntiatedInternally:(BOOL)initiatedInternally completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fetchInternal) object:nil]; + + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + if (myBlockchainIdentity == nil || myBlockchainIdentity.registered == NO) { + if (completion) { + completion(YES, nil); + } + + [self performSelector:@selector(fetchInternal) withObject:nil afterDelay:UPDATE_INTERVAL]; + + return; + } + + if (!initiatedInternally) { + self.fetchCompletion = completion; + } + + if (self.lastFetch && [[NSDate date] timeIntervalSinceDate:self.lastFetch] < UPDATE_INTERVAL) { + if (completion) { + completion(YES, nil); + } + + [self performSelector:@selector(fetchInternal) withObject:nil afterDelay:UPDATE_INTERVAL]; + + return; + } + + if (self.isFetching) { + return; + } + self.fetching = YES; + + self.lastFetch = [NSDate date]; + + __weak typeof(self) weakSelf = self; + [myBlockchainIdentity fetchContactRequests:^(BOOL success, NSArray *_Nonnull errors) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + strongSelf.fetching = NO; + + if (strongSelf.isUpdating == NO) { + return; + } + + DSLog(@"DWDP: Fetch contact requests %@: %@", + success ? @"Succeeded" : @"Failed", + errors.count == 0 ? @"" : errors); + + if (strongSelf.fetchCompletion) { + strongSelf.fetchCompletion(success, errors); + strongSelf.fetchCompletion = nil; + } + + [[NSNotificationCenter defaultCenter] postNotificationName:DWDashPayContactsDidUpdateNotification object:nil]; + + [strongSelf performSelector:@selector(fetchInternal) withObject:nil afterDelay:UPDATE_INTERVAL]; + }]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.h b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.h new file mode 100644 index 000000000..a7cd48aa1 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.h @@ -0,0 +1,37 @@ +// +// 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 +#import + +#import "DWDPBasicUserItem.h" +#import "DWDPNotificationItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWNotificationsData : NSObject + +@property (readonly, nonatomic, assign) BOOL isEmpty; +@property (readonly, nonatomic, copy) NSArray> *unreadItems; +@property (readonly, nonatomic, copy) NSArray> *oldItems; + +- (instancetype)initWithUnreadItems:(NSArray> *)unreadItems + oldItems:(NSArray> *)oldItems NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.m b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.m new file mode 100644 index 000000000..70adf6395 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsData.m @@ -0,0 +1,45 @@ +// +// 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 "DWNotificationsData.h" + +@implementation DWNotificationsData + +- (instancetype)initWithUnreadItems:(NSArray> *)unreadItems + oldItems:(NSArray> *)oldItems { + NSParameterAssert(unreadItems); + NSParameterAssert(oldItems); + self = [super init]; + if (self) { + _unreadItems = [unreadItems copy]; + _oldItems = [oldItems copy]; + _isEmpty = _unreadItems.count == 0 && _oldItems.count == 0; + } + return self; +} + +- (instancetype)init { + return [self initWithUnreadItems:@[] oldItems:@[]]; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(nullable NSZone *)zone { + return [[self.class alloc] initWithUnreadItems:self.unreadItems oldItems:self.oldItems]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.h b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.h new file mode 100644 index 000000000..87bb1e446 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.h @@ -0,0 +1,35 @@ +// +// 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 "DWFetchedResultsDataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSBlockchainIdentity; + +@interface DWNotificationsFetchedDataSource : DWFetchedResultsDataSource + +@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + inContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.m b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.m new file mode 100644 index 000000000..bd8913e2e --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsFetchedDataSource.m @@ -0,0 +1,48 @@ +// +// 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 "DWNotificationsFetchedDataSource.h" + +#import "DWEnvironment.h" + +@implementation DWNotificationsFetchedDataSource + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + inContext:(NSManagedObjectContext *)context { + self = [super initWithContext:context]; + if (self) { + _blockchainIdentity = blockchainIdentity; + } + return self; +} + +- (NSString *)entityName { + return NSStringFromClass(DSFriendRequestEntity.class); +} + +- (NSPredicate *)predicate { + DSDashpayUserEntity *dashPayUser = [self.blockchainIdentity matchingDashpayUserInContext:self.context]; + return [NSPredicate predicateWithFormat:@"destinationContact == %@ || sourceContact == %@", dashPayUser, dashPayUser]; +} + +- (NSArray *)sortDescriptors { + // reversed order, from old to new + NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:YES]; + return @[ sortDescriptor ]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.h b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.h new file mode 100644 index 000000000..a331fd1e5 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.h @@ -0,0 +1,41 @@ +// +// 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 + +extern NSNotificationName const DWNotificationsProviderWillUpdateNotification; +extern NSNotificationName const DWNotificationsProviderDidUpdateNotification; + +@class DWNotificationsData; +@class NSManagedObjectID; + +@interface DWNotificationsProvider : NSObject + +@property (readonly, nonatomic, copy) DWNotificationsData *data; + ++ (instancetype)sharedInstance; + +- (void)forceUpdate; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.m b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.m new file mode 100644 index 000000000..e594d9316 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Global/Notifications/DWNotificationsProvider.m @@ -0,0 +1,209 @@ +// +// 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 "DWNotificationsProvider.h" + +#import "DWDashPayContactsUpdater.h" +#import "DWEnvironment.h" +#import "DWGlobalOptions.h" +#import "DWNotificationsData.h" +#import "DWNotificationsFetchedDataSource.h" + +#import "DWDPAcceptedRequestNotificationObject.h" +#import "DWDPEstablishedContactNotificationObject.h" +#import "DWDPNewIncomingRequestNotificationObject.h" +#import "DWDPOutgoingRequestNotificationObject.h" + +NS_ASSUME_NONNULL_BEGIN + +NSNotificationName const DWNotificationsProviderWillUpdateNotification = @"org.dash.wallet.dp.notifications-will-update"; +NSNotificationName const DWNotificationsProviderDidUpdateNotification = @"org.dash.wallet.dp.notifications-did-update"; + +@interface DWNotificationsProvider () + +@property (nonatomic, strong) DWFetchedResultsDataSource *fetchedDataSource; +@property (nonatomic, copy) DWNotificationsData *data; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWNotificationsProvider + ++ (instancetype)sharedInstance { + static DWNotificationsProvider *_sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _sharedInstance = [[self alloc] init]; + }); + return _sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _data = [[DWNotificationsData alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didUpdateContacts) + name:DWDashPayContactsDidUpdateNotification + object:nil]; + + // Defer initial reset of notifications because it would lead to a deadlock + // (since DWNotificationsProvider is a singleton and reset would produce a notification) + dispatch_async(dispatch_get_main_queue(), ^{ + [self forceUpdate]; + }); + } + return self; +} + +- (void)forceUpdate { + DSBlockchainIdentity *blockchainIdentity = [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; + if (!blockchainIdentity) { + return; + } + + NSManagedObjectContext *context = [NSManagedObjectContext viewContext]; + + _fetchedDataSource = [[DWNotificationsFetchedDataSource alloc] initWithBlockchainIdentity:blockchainIdentity inContext:context]; + _fetchedDataSource.shouldSubscribeToNotifications = YES; + _fetchedDataSource.delegate = self; + [_fetchedDataSource start]; + _fetchedDataSource.fetchedResultsController.delegate = self; + + [self reload]; +} + +#pragma mark - Private + +- (void)setData:(DWNotificationsData *)data { + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter postNotificationName:DWNotificationsProviderWillUpdateNotification object:self]; + _data = data; + [notificationCenter postNotificationName:DWNotificationsProviderDidUpdateNotification object:self]; +} + +- (void)reload { + // fetched objects come in a reversed order (from old to new) + NSArray *fetchedObjects = self.fetchedDataSource.fetchedResultsController.fetchedObjects; + + NSMutableDictionary *> *connections = + [NSMutableDictionary dictionary]; + for (DSFriendRequestEntity *request in fetchedObjects) { + NSManagedObjectID *sourceID = request.sourceContact.objectID; + NSMutableSet *sourceConnections = connections[sourceID]; + if (sourceConnections == nil) { + sourceConnections = [NSMutableSet set]; + connections[sourceID] = sourceConnections; + } + [sourceConnections addObject:request.destinationContact.objectID]; + } + + DSBlockchainIdentity *blockchainIdentity = [DWEnvironment sharedInstance].currentWallet.defaultBlockchainIdentity; + NSManagedObjectID *userID = blockchainIdentity.matchingDashpayUserInViewContext.objectID; + + DWGlobalOptions *options = [DWGlobalOptions sharedInstance]; + const NSTimeInterval mostRecentViewedTimestamp = [options.mostRecentViewedNotificationDate timeIntervalSince1970]; + + NSMutableArray> *newItems = [NSMutableArray array]; + NSMutableArray> *oldItems = [NSMutableArray array]; + + NSMutableSet *processed = [NSMutableSet set]; + + for (DSFriendRequestEntity *request in fetchedObjects) { + NSManagedObjectID *sourceID = request.sourceContact.objectID; + NSManagedObjectID *destinationID = request.destinationContact.objectID; + NSSet *destinationConnections = connections[destinationID]; + const BOOL isFriendship = [destinationConnections containsObject:sourceID]; + const BOOL isNew = request.timestamp > mostRecentViewedTimestamp; + NSMutableArray> *items = isNew ? newItems : oldItems; + + if ([sourceID isEqual:userID]) { // outoging + const BOOL isInitiatedByMe = ![processed containsObject:destinationID]; + [processed addObject:destinationID]; + + if (isFriendship) { + DSBlockchainIdentity *blockchainIdentity = [request.destinationContact.associatedBlockchainIdentity blockchainIdentity]; + DWDPOutgoingRequestNotificationObject *object = + [[DWDPOutgoingRequestNotificationObject alloc] initWithFriendRequestEntity:request + blockchainIdentity:blockchainIdentity + isInitiatedByMe:isInitiatedByMe]; + // all outgoing events should be in the Earlier section + [oldItems addObject:object]; + } + + // outgoing requests with no response (pending) are not shown in notifications + } + else { // incoming + const BOOL isInitiatedByThem = ![processed containsObject:sourceID]; + [processed addObject:sourceID]; + + DSBlockchainIdentity *blockchainIdentity = [request.sourceContact.associatedBlockchainIdentity blockchainIdentity]; + if (isFriendship) { + DWDPAcceptedRequestNotificationObject *object = + [[DWDPAcceptedRequestNotificationObject alloc] initWithFriendRequestEntity:request + blockchainIdentity:blockchainIdentity + isInitiatedByThem:isInitiatedByThem]; + // Don't add notifications about MY responses to the New section + if (isInitiatedByThem) { + [oldItems addObject:object]; + } + else { + [items addObject:object]; + } + } + else { + DWDPNewIncomingRequestNotificationObject *object = + [[DWDPNewIncomingRequestNotificationObject alloc] initWithFriendRequestEntity:request + blockchainIdentity:blockchainIdentity]; + [items addObject:object]; + } + } + } + + self.data = [[DWNotificationsData alloc] initWithUnreadItems:[newItems reverseObjectEnumerator].allObjects + oldItems:[oldItems reverseObjectEnumerator].allObjects]; +} + +#pragma mark - DWFetchedResultsDataSourceDelegate + +- (void)fetchedResultsDataSourceDidUpdate:(DWFetchedResultsDataSource *)fetchedResultsDataSource { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + // internal FRC might be niled out + self.fetchedDataSource.fetchedResultsController.delegate = self; + + [self reload]; +} + +- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + DSLog(@"DWDP: Notification provider's FRC did update"); + + [self reload]; +} + +#pragma mark - Notifications + +- (void)didUpdateContacts { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + [self forceUpdate]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.m b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.m index 89c11f516..71eb6bd04 100644 --- a/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.m +++ b/DashWallet/Sources/UI/DashPay/Invites/Confirmation/DWConfirmInvitationContentView.m @@ -20,7 +20,7 @@ #import "DWCheckbox.h" #import "DWDashPayConstants.h" #import "DWUIKit.h" -#import "NSAttributedString+DWBuilder.h" +#import "dashwallet-Swift.h" #import NS_ASSUME_NONNULL_BEGIN diff --git a/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.m b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.m index 409fcdb0b..4b5e5d518 100644 --- a/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.m +++ b/DashWallet/Sources/UI/DashPay/Invites/DWSendInviteFlowController.m @@ -20,7 +20,6 @@ #import "DPAlertViewController+DWInvite.h" #import "DWConfirmInvitationViewController.h" #import "DWFullScreenModalControllerViewController.h" -#import "DWNavigationController.h" #import "DWSendInviteFirstStepViewController.h" #import "DWUIKit.h" diff --git a/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.m b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.m index 1c2493422..1bf97e8a5 100644 --- a/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.m +++ b/DashWallet/Sources/UI/DashPay/Invites/History/Views/DWCreateInvitationButton.m @@ -18,6 +18,8 @@ #import "DWCreateInvitationButton.h" #import "DWUIKit.h" +#import "UIView+DWAutolayout.h" +#import "NSLayoutConstraint+DWAutolayout.h" NS_ASSUME_NONNULL_BEGIN diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/BaseInvitationViewController.swift b/DashWallet/Sources/UI/DashPay/Invites/Invitation/BaseInvitationViewController.swift index a315998d0..0422e1004 100644 --- a/DashWallet/Sources/UI/DashPay/Invites/Invitation/BaseInvitationViewController.swift +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/BaseInvitationViewController.swift @@ -101,10 +101,11 @@ class InvitationSourceItem: NSObject, UIActivityItemSource @objc func profileAction() { let item = DWDPUserObject(blockchainIdentity: invitation.identity) let payModel = DWPayModel() - let dataProvider = DWTransactionListDataProviderStub() - - let profileController = DWUserProfileViewController(item: item, payModel: payModel, dataProvider: dataProvider, shouldSkipUpdating: true, shownAfterPayment: false) - self.navigationController?.pushViewController(profileController, animated: true) + //TODO: DashPay +// let dataProvider = DWTransactionListDataProviderStub() +// +// let profileController = DWUserProfileViewController(item: item, payModel: payModel, dataProvider: dataProvider, shouldSkipUpdating: true, shownAfterPayment: false) +// self.navigationController?.pushViewController(profileController, animated: true) } //MARK: Hierarchy diff --git a/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.m b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.m index cef644418..115e3641b 100644 --- a/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.m +++ b/DashWallet/Sources/UI/DashPay/Invites/Invitation/Views/DWSuccessInvitationView.m @@ -54,7 +54,7 @@ - (instancetype)initWithFrame:(CGRect)frame { UIView *avatarContainer = [[UIView alloc] init]; avatarContainer.translatesAutoresizingMaskIntoConstraints = NO; - avatarContainer.backgroundColor = [UIColor dw_lightBlueColor]; + avatarContainer.backgroundColor = [UIColor dw_dashBlueColor]; //TODO: must be light blue [self addSubview:avatarContainer]; _avatarContainer = avatarContainer; diff --git a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.h b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.h new file mode 100644 index 000000000..612d6177b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.h @@ -0,0 +1,31 @@ +// +// 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 +#import + +#import "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPContactsItemsFactory : NSObject + +- (id)itemForEntity:(NSManagedObject *)entity; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.m b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.m new file mode 100644 index 000000000..7bf21b4f0 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPContactsItemsFactory.m @@ -0,0 +1,52 @@ +// +// 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 "DWDPContactsItemsFactory.h" + +#import "DWDPContactObject.h" +#import "DWDPDashpayUserBackedItem.h" +#import "DWDPFriendRequestBackedItem.h" +#import "DWDPNewIncomingRequestObject.h" +#import "DWDPRespondedIncomingRequestObject.h" + +@implementation DWDPContactsItemsFactory + +- (id)itemForEntity:(NSManagedObject *)entity { + if ([entity isKindOfClass:DSFriendRequestEntity.class]) { + return [self itemForFriendRequestEntity:(DSFriendRequestEntity *)entity]; + } + if ([entity isKindOfClass:DSDashpayUserEntity.class]) { + return [self itemForDashpayUserEntity:(DSDashpayUserEntity *)entity]; + } + + NSAssert(NO, @"Unsupported entity type"); + return nil; +} + +#pragma mark - Private + +- (id)itemForFriendRequestEntity:(DSFriendRequestEntity *)entity { + // TODO: DP impl case `if entity.isIgnored` + DSBlockchainIdentity *blockchainIdentity = [entity.sourceContact.associatedBlockchainIdentity blockchainIdentity]; + return [[DWDPNewIncomingRequestObject alloc] initWithFriendRequestEntity:entity blockchainIdentity:blockchainIdentity]; +} + +- (id)itemForDashpayUserEntity:(DSDashpayUserEntity *)entity { + return [[DWDPContactObject alloc] initWithDashpayUserEntity:entity]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.h b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.h new file mode 100644 index 000000000..c4a40fc93 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.h @@ -0,0 +1,33 @@ +// +// 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 + +#import "DWDPBasicUserItem.h" +#import "DWDPBlockchainIdentityBackedItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSBlockchainIdentity; + +@interface DWDPSearchItemsFactory : NSObject + +- (id)itemForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.m b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.m new file mode 100644 index 000000000..9ea47c77c --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Factory/DWDPSearchItemsFactory.m @@ -0,0 +1,47 @@ +// +// 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 "DWDPSearchItemsFactory.h" + +#import "DWEnvironment.h" + +#import "DWDPEstablishedContactObject.h" +#import "DWDPNewIncomingRequestObject.h" +#import "DWDPPendingRequestObject.h" +#import "DWDPUserObject.h" + +@implementation DWDPSearchItemsFactory + +- (id)itemForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + DSBlockchainIdentityFriendshipStatus friendshipStatus = [myBlockchainIdentity friendshipStatusForRelationshipWithBlockchainIdentity:blockchainIdentity]; + + switch (friendshipStatus) { + case DSBlockchainIdentityFriendshipStatus_Unknown: + case DSBlockchainIdentityFriendshipStatus_None: + return [[DWDPUserObject alloc] initWithBlockchainIdentity:blockchainIdentity]; + case DSBlockchainIdentityFriendshipStatus_Outgoing: + return [[DWDPPendingRequestObject alloc] initWithBlockchainIdentity:blockchainIdentity]; + case DSBlockchainIdentityFriendshipStatus_Incoming: + return [[DWDPNewIncomingRequestObject alloc] initWithBlockchainIdentity:blockchainIdentity]; + case DSBlockchainIdentityFriendshipStatus_Friends: + return [[DWDPEstablishedContactObject alloc] initWithBlockchainIdentity:blockchainIdentity]; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.h new file mode 100644 index 000000000..ef47cfb74 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.h @@ -0,0 +1,36 @@ +// +// 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 "DWDPBasicUserItem.h" +#import "DWDPDashpayUserBackedItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSDashpayUserEntity; + +@interface DWDPContactObject : NSObject + +@property (readonly, nonatomic, strong) DSDashpayUserEntity *userEntity; + +@property (nonatomic, strong) NSString *username; +@property (nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +- (instancetype)initWithDashpayUserEntity:(DSDashpayUserEntity *)userEntity; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.m new file mode 100644 index 000000000..c64d7ea65 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPContactObject.m @@ -0,0 +1,72 @@ +// +// 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 "DWDPContactObject.h" + +#import + +#import "DWEnvironment.h" +#import "UIFont+DWDPItem.h" + +@implementation DWDPContactObject + +@synthesize displayName = _displayName; + +- (instancetype)initWithDashpayUserEntity:(DSDashpayUserEntity *)userEntity { + self = [super init]; + if (self) { + _userEntity = userEntity; + _blockchainIdentity = [userEntity.associatedBlockchainIdentity blockchainIdentity]; + } + return self; +} + +- (NSString *)username { + if (_username == nil) { + _username = [self.userEntity.username copy]; + } + return _username; +} + +- (NSString *)displayName { + if (_displayName == nil) { + NSString *userName = [self.userEntity.displayName copy]; + _displayName = [userName isEqualToString:@""] ? nil : userName; + } + return _displayName; +} + +- (NSAttributedString *)title { + NSDictionary *attributes = @{NSFontAttributeName : [UIFont dw_itemTitleFont]}; + return [[NSAttributedString alloc] initWithString:(self.displayName ?: self.username) ?: @"" attributes:attributes]; +} + +- (NSString *)subtitle { + return self.displayName ? self.username : nil; +} + +- (DSFriendRequestEntity *)friendRequestToPay { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + NSPredicate *predicate = [NSPredicate predicateWithFormat: + @"destinationContact.associatedBlockchainIdentity.uniqueID == %@", + myBlockchainIdentity.uniqueIDData]; + DSFriendRequestEntity *friendRequest = [[self.userEntity.outgoingRequests filteredSetUsingPredicate:predicate] anyObject]; + return friendRequest; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.h new file mode 100644 index 000000000..76287025d --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.h @@ -0,0 +1,32 @@ +// +// 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 "DWDPContactObject.h" +#import "DWDPEstablishedContactItem.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Established contact may come from search or notifications +@interface DWDPEstablishedContactObject : DWDPContactObject + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithDashpayUserEntity:(DSDashpayUserEntity *)userEntity NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.m new file mode 100644 index 000000000..f00f7c000 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPEstablishedContactObject.m @@ -0,0 +1,33 @@ +// +// 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 "DWDPEstablishedContactObject.h" + +#import + +@implementation DWDPEstablishedContactObject + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self = [super init]; + if (self) { + self.blockchainIdentity = blockchainIdentity; + self.username = blockchainIdentity.currentDashpayUsername; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.h new file mode 100644 index 000000000..b14c0ed1f --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.h @@ -0,0 +1,38 @@ +// +// 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 "DWDPFriendRequestBackedItem.h" +#import "DWDPIncomingRequestItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSFriendRequestEntity; + +@interface DWDPIncomingRequestObject : NSObject + +@property (readonly, nullable, strong, nonatomic) DSFriendRequestEntity *friendRequestEntity; + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.m new file mode 100644 index 000000000..37b5743ec --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPIncomingRequestObject.m @@ -0,0 +1,79 @@ +// +// 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 "DWDPIncomingRequestObject.h" + +#import + +#import "UIFont+DWDPItem.h" + +@implementation DWDPIncomingRequestObject + +@synthesize blockchainIdentity = _blockchainIdentity; +@synthesize username = _username; + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self = [super init]; + if (self) { + _blockchainIdentity = blockchainIdentity; + _friendRequestEntity = friendRequestEntity; + } + return self; +} + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self = [super init]; + if (self) { + _blockchainIdentity = blockchainIdentity; + _username = blockchainIdentity.currentDashpayUsername; + } + return self; +} + +- (NSString *)username { + if (_username == nil) { + // incoming request, use source + DSDashpayUserEntity *contact = self.friendRequestEntity.sourceContact; + _username = [contact.username copy]; + } + return _username; +} + +- (NSString *)displayName { + if (_username == nil) { + BOOL hasDisplayName = _blockchainIdentity.displayName.length > 0; + _username = hasDisplayName ? [_blockchainIdentity.displayName copy] : nil; + } + + return _username; +} + +- (NSAttributedString *)title { + NSDictionary *attributes = @{NSFontAttributeName : [UIFont dw_itemTitleFont]}; + return [[NSAttributedString alloc] initWithString:(self.displayName ?: self.username) ?: @"" attributes:attributes]; +} + +- (NSString *)subtitle { + return self.displayName ? self.username : nil; +} + +- (DSFriendRequestEntity *)friendRequestToPay { + return self.friendRequestEntity; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.h new file mode 100644 index 000000000..c34166359 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.h @@ -0,0 +1,28 @@ +// +// 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 "DWDPIncomingRequestObject.h" + +#import "DWDPNewIncomingRequestItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPNewIncomingRequestObject : DWDPIncomingRequestObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.m new file mode 100644 index 000000000..65535bf06 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPNewIncomingRequestObject.m @@ -0,0 +1,24 @@ +// +// 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 "DWDPNewIncomingRequestObject.h" + +@implementation DWDPNewIncomingRequestObject + +@synthesize requestState; + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.h new file mode 100644 index 000000000..9f6490adb --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.h @@ -0,0 +1,27 @@ +// +// 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 "DWDPPendingRequestItem.h" +#import "DWDPUserObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPPendingRequestObject : DWDPUserObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.m new file mode 100644 index 000000000..88875fa62 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPPendingRequestObject.m @@ -0,0 +1,28 @@ +// +// 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 "DWDPPendingRequestObject.h" + +#import + +@implementation DWDPPendingRequestObject + +- (DSFriendRequestEntity *)friendRequestToPay { + return nil; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.h new file mode 100644 index 000000000..d19bc1b8e --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.h @@ -0,0 +1,27 @@ +// +// 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 "DWDPIncomingRequestObject.h" +#import "DWDPRespondedRequestItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPRespondedIncomingRequestObject : DWDPIncomingRequestObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.m new file mode 100644 index 000000000..04cc776a5 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPRespondedIncomingRequestObject.m @@ -0,0 +1,24 @@ +// +// 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 "DWDPRespondedIncomingRequestObject.h" + +#import + +@implementation DWDPRespondedIncomingRequestObject + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.h new file mode 100644 index 000000000..ce919cf08 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.h @@ -0,0 +1,36 @@ +// +// 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 "DWDPTxItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSTransaction; +@protocol DWTransactionListDataProviderProtocol; + +@interface DWDPTxObject : NSObject + +- (instancetype)initWithTransaction:(DSTransaction *)tx + dataProvider:(id)dataProvider + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.m new file mode 100644 index 000000000..378fa5a77 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPTxObject.m @@ -0,0 +1,95 @@ +// +// 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 "DWDPTxObject.h" + +#import "DWTransactionListDataProviderProtocol.h" +#import "UIColor+DWStyle.h" +#import "UIFont+DWDPItem.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPTxObject () + +@property (readonly, nonatomic, strong) id dataProvider; +@property (readonly, nonatomic, strong) id dataItem; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPTxObject + +@synthesize displayName = _displayName; +@synthesize subtitle = _subtitle; +@synthesize username = _username; +@synthesize transaction = _transaction; +@synthesize blockchainIdentity = _blockchainIdentity; + +- (instancetype)initWithTransaction:(DSTransaction *)tx + dataProvider:(id)dataProvider + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self = [super init]; + if (self) { + _transaction = tx; + _dataProvider = dataProvider; + _blockchainIdentity = blockchainIdentity; + _username = blockchainIdentity.currentDashpayUsername; + + BOOL hasDisplayName = blockchainIdentity.displayName.length > 0; + _displayName = hasDisplayName ? blockchainIdentity.displayName : nil; + + _subtitle = [dataProvider shortDateStringForTransaction:tx]; + _dataItem = [dataProvider transactionDataForTransaction:tx]; + } + return self; +} + +- (NSAttributedString *)title { + NSDictionary *attributes = @{NSFontAttributeName : [UIFont dw_itemTitleFont]}; + return [[NSAttributedString alloc] initWithString:self.dataItem.directionText attributes:attributes]; +} + +- (NSAttributedString *)amountString { + UIFont *titleFont = [UIFont dw_itemTitleFont]; + UIFont *subtitleFont = [UIFont dw_itemSubtitleFont]; + + NSAttributedString *dashAmountString = [self.dataProvider dashAmountStringFrom:self.dataItem font:titleFont]; + + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + style.maximumLineHeight = 4.0; + NSAttributedString *spacingString = [[NSAttributedString alloc] initWithString:@"\n\n" + attributes:@{NSParagraphStyleAttributeName : style}]; + + NSAttributedString *fiatString = [[NSAttributedString alloc] initWithString:self.dataItem.fiatAmount + attributes:@{ + NSFontAttributeName : subtitleFont, + NSForegroundColorAttributeName : [UIColor dw_tertiaryTextColor], + }]; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + [result appendAttributedString:dashAmountString]; + [result appendAttributedString:spacingString]; + [result appendAttributedString:fiatString]; + [result endEditing]; + return [result copy]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.h new file mode 100644 index 000000000..8ebd89ea3 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.h @@ -0,0 +1,39 @@ +// +// 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 "DWDPBasicUserItem.h" +#import "DWDPFriendRequestBackedItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSFriendRequestEntity; + +/// User from search +@interface DWDPUserObject : NSObject + +@property (readonly, nullable, strong, nonatomic) DSFriendRequestEntity *friendRequestEntity; + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.m new file mode 100644 index 000000000..6ea24a000 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/DWDPUserObject.m @@ -0,0 +1,87 @@ +// +// 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 "DWDPUserObject.h" + +#import + +#import "DWEnvironment.h" +#import "UIFont+DWDPItem.h" + +@implementation DWDPUserObject + +@synthesize blockchainIdentity = _blockchainIdentity; +@synthesize username = _username; +@synthesize displayName = _displayName; + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self = [super init]; + if (self) { + _blockchainIdentity = blockchainIdentity; + _username = blockchainIdentity.currentDashpayUsername; + } + return self; +} + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self = [super init]; + if (self) { + _blockchainIdentity = blockchainIdentity; + _friendRequestEntity = friendRequestEntity; + } + return self; +} + +- (NSString *)displayName { + if (_displayName == nil) { + BOOL hasDisplayName = _blockchainIdentity.displayName.length > 0; + _displayName = hasDisplayName ? _blockchainIdentity.displayName : nil; + } + + return _displayName; +} + +- (NSString *)username { + if (_username == nil) { + // outgoing request, use destination + DSDashpayUserEntity *contact = self.friendRequestEntity.destinationContact; + _username = [contact.username copy]; + } + return _username; +} + +- (NSAttributedString *)title { + NSDictionary *attributes = @{NSFontAttributeName : [UIFont dw_itemTitleFont]}; + return [[NSAttributedString alloc] initWithString:(self.displayName ?: self.username) ?: @"" attributes:attributes]; +} + +- (NSString *)subtitle { + return self.displayName ? self.username : nil; +} + +- (DSFriendRequestEntity *)friendRequestToPay { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + NSPredicate *predicate = [NSPredicate predicateWithFormat: + @"destinationContact.associatedBlockchainIdentity.uniqueID == %@", + uint256_data(myBlockchainIdentity.uniqueID)]; + DSFriendRequestEntity *friendRequest = [[self.blockchainIdentity.matchingDashpayUserInViewContext.outgoingRequests filteredSetUsingPredicate:predicate] anyObject]; + return friendRequest; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.h new file mode 100644 index 000000000..fadb3646d --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.h @@ -0,0 +1,35 @@ +// +// 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 "DWDPNotificationItem.h" +#import "DWDPRespondedIncomingRequestObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPAcceptedRequestNotificationObject : DWDPRespondedIncomingRequestObject + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + isInitiatedByThem:(BOOL)isInitiatedByThem NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.m new file mode 100644 index 000000000..81a77a1a6 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPAcceptedRequestNotificationObject.m @@ -0,0 +1,78 @@ +// +// 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 "DWDPAcceptedRequestNotificationObject.h" + +#import "DWDateFormatter.h" +#import "DWEnvironment.h" +#import "UIFont+DWDPItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPAcceptedRequestNotificationObject () + +@property (readonly, nonatomic, assign, getter=isInitiatedByThem) BOOL initiatedByThem; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPAcceptedRequestNotificationObject + +@synthesize title = _title; +@synthesize subtitle = _subtitle; +@synthesize date = _date; + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + isInitiatedByThem:(BOOL)isInitiatedByThem { + self = [super initWithFriendRequestEntity:friendRequestEntity blockchainIdentity:blockchainIdentity]; + if (self) { + _date = [NSDate dateWithTimeIntervalSince1970:friendRequestEntity.timestamp]; + _initiatedByThem = isInitiatedByThem; + } + return self; +} + +- (NSAttributedString *)title { + if (_title == nil) { + NSString *name = self.displayName ?: (self.username ?: NSLocalizedString(@"User (Fetching Info)", nil)); + NSString *format = self.isInitiatedByThem + ? NSLocalizedString(@"%@ has sent you a contact request", nil) + : NSLocalizedString(@"%@ has accepted your contact request", nil); + NSString *plainTitle = [NSString stringWithFormat:format, name]; + + NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:plainTitle attributes:@{NSFontAttributeName : [UIFont dw_itemSubtitleFont]}]; + + NSRange range = [plainTitle rangeOfString:name]; + if (range.location != NSNotFound) { + [title setAttributes:@{NSFontAttributeName : [UIFont dw_itemTitleFont]} range:range]; + } + + _title = [title copy]; + } + return _title; +} + +- (NSString *)subtitle { + if (_subtitle == nil) { + _subtitle = [[DWDateFormatter sharedInstance] shortStringFromDate:self.date]; + } + return _subtitle; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.h new file mode 100644 index 000000000..258f97b32 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.h @@ -0,0 +1,36 @@ +// +// 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 "DWDPEstablishedContactObject.h" +#import "DWDPNotificationItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSFriendRequestEntity; + +// TODO: DP consider removing this notification type + +@interface DWDPEstablishedContactNotificationObject : DWDPEstablishedContactObject + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.m new file mode 100644 index 000000000..5ec58b2f9 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPEstablishedContactNotificationObject.m @@ -0,0 +1,80 @@ +// +// 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 "DWDPEstablishedContactNotificationObject.h" + +#import + +#import "DWDateFormatter.h" +#import "UIFont+DWDPItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPEstablishedContactNotificationObject () + +@property (readonly, nonatomic, strong) DSFriendRequestEntity *friendRequestEntity; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPEstablishedContactNotificationObject + +@synthesize title = _title; +@synthesize subtitle = _subtitle; +@synthesize date = _date; + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self = [super initWithBlockchainIdentity:blockchainIdentity]; + if (self) { + _friendRequestEntity = friendRequestEntity; + _date = [NSDate dateWithTimeIntervalSince1970:friendRequestEntity.timestamp]; + } + return self; +} + +- (NSAttributedString *)title { + if (_title == nil) { + NSString *name = self.displayName ?: self.username; + NSString *format = NSLocalizedString(@"%@ has sent you a contact request", nil); + NSString *plainTitle = [NSString stringWithFormat:format, name]; + + NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:plainTitle attributes:@{NSFontAttributeName : [UIFont dw_itemSubtitleFont]}]; + + NSRange range = [plainTitle rangeOfString:name]; + if (range.location != NSNotFound) { + [title setAttributes:@{NSFontAttributeName : [UIFont dw_itemTitleFont]} range:range]; + } + + _title = [title copy]; + } + return _title; +} + +- (NSString *)subtitle { + if (_subtitle == nil) { + _subtitle = [[DWDateFormatter sharedInstance] shortStringFromDate:self.date]; + } + return _subtitle; +} + +- (DSFriendRequestEntity *)friendRequestToPay { + return self.friendRequestEntity; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.h new file mode 100644 index 000000000..a5afc1cba --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.h @@ -0,0 +1,29 @@ +// +// 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 "DWDPNewIncomingRequestObject.h" +#import "DWDPNotificationItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPNewIncomingRequestNotificationObject : DWDPNewIncomingRequestObject + +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.m new file mode 100644 index 000000000..711988ae3 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPNewIncomingRequestNotificationObject.m @@ -0,0 +1,45 @@ +// +// 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 "DWDPNewIncomingRequestNotificationObject.h" + +#import + +#import "DWDateFormatter.h" + +@implementation DWDPNewIncomingRequestNotificationObject + +@synthesize subtitle = _subtitle; +@synthesize date = _date; + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self = [super initWithFriendRequestEntity:friendRequestEntity blockchainIdentity:blockchainIdentity]; + if (self) { + _date = [NSDate dateWithTimeIntervalSince1970:friendRequestEntity.timestamp]; + } + return self; +} + +- (NSString *)subtitle { + if (_subtitle == nil) { + _subtitle = [[DWDateFormatter sharedInstance] shortStringFromDate:self.date]; + } + return _subtitle; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.h b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.h new file mode 100644 index 000000000..fdfbacf9a --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.h @@ -0,0 +1,35 @@ +// +// 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 "DWDPNotificationItem.h" +#import "DWDPUserObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPOutgoingRequestNotificationObject : DWDPUserObject + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + isInitiatedByMe:(BOOL)isInitiatedByMe NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; +- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.m b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.m new file mode 100644 index 000000000..f9a4a5775 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Objects/Notifications/DWDPOutgoingRequestNotificationObject.m @@ -0,0 +1,83 @@ +// +// 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 "DWDPOutgoingRequestNotificationObject.h" + +#import "DWDateFormatter.h" +#import "DWEnvironment.h" +#import "UIFont+DWDPItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPOutgoingRequestNotificationObject () + +@property (readonly, nonatomic, assign, getter=isInitiatedByMe) BOOL initiatedByMe; + +@end + +NS_ASSUME_NONNULL_END + + +@implementation DWDPOutgoingRequestNotificationObject + +@synthesize title = _title; +@synthesize subtitle = _subtitle; +@synthesize date = _date; + +- (instancetype)initWithFriendRequestEntity:(DSFriendRequestEntity *)friendRequestEntity + blockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity + isInitiatedByMe:(BOOL)isInitiatedByMe { + self = [super initWithFriendRequestEntity:friendRequestEntity blockchainIdentity:blockchainIdentity]; + if (self) { + _date = [NSDate dateWithTimeIntervalSince1970:friendRequestEntity.timestamp]; + _initiatedByMe = isInitiatedByMe; + } + return self; +} + +- (NSAttributedString *)title { + if (_title == nil) { + NSString *name = self.displayName ?: (self.username ?: NSLocalizedString(@"them (Fetching Info)", nil)); + NSString *format = self.isInitiatedByMe + ? NSLocalizedString(@"You sent the contact request to %@", nil) + : NSLocalizedString(@"You accepted the contact request from %@", nil); + NSString *plainTitle = [NSString stringWithFormat:format, name]; + + NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:plainTitle attributes:@{NSFontAttributeName : [UIFont dw_itemSubtitleFont]}]; + + NSRange range = [plainTitle rangeOfString:name]; + if (range.location != NSNotFound) { + [title setAttributes:@{NSFontAttributeName : [UIFont dw_itemTitleFont]} range:range]; + } + + _title = [title copy]; + } + return _title; +} + +- (NSString *)subtitle { + if (_subtitle == nil) { + _subtitle = [[DWDateFormatter sharedInstance] shortStringFromDate:self.date]; + } + return _subtitle; +} + +- (DSFriendRequestEntity *)friendRequestToPay { + return nil; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicItem.h new file mode 100644 index 000000000..8d72580d5 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicItem.h @@ -0,0 +1,36 @@ +// +// 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 + +#import "DWDPBlockchainIdentityBackedItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol DWDPItemCellDelegate +@end + +@protocol DWDPBasicItem + +@property (readonly, nonatomic) NSString *username; +@property (nullable, readonly, nonatomic) NSString *displayName; +@property (nullable, readonly, nonatomic) NSAttributedString *title; +@property (nullable, readonly, nonatomic) NSString *subtitle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicUserItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicUserItem.h new file mode 100644 index 000000000..c06cf0013 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPBasicUserItem.h @@ -0,0 +1,33 @@ +// +// 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 + +#import "DWDPBasicItem.h" +#import "DWDPBlockchainIdentityBackedItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSFriendRequestEntity; + +@protocol DWDPBasicUserItem + +- (nullable DSFriendRequestEntity *)friendRequestToPay; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPEstablishedContactItem.h similarity index 86% rename from DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h rename to DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPEstablishedContactItem.h index b2b3f77da..8634e2482 100644 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.h +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPEstablishedContactItem.h @@ -15,11 +15,11 @@ // limitations under the License. // -#import "DWUsernameValidationRule.h" +#import "DWDPBasicUserItem.h" NS_ASSUME_NONNULL_BEGIN -@interface DWLengthUsernameValidationRule : DWUsernameValidationRule +@protocol DWDPEstablishedContactItem @end diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPIncomingRequestItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPIncomingRequestItem.h new file mode 100644 index 000000000..286dc79ab --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPIncomingRequestItem.h @@ -0,0 +1,26 @@ +// +// 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 "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol DWDPIncomingRequestItem + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNewIncomingRequestItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNewIncomingRequestItem.h new file mode 100644 index 000000000..7e462378b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNewIncomingRequestItem.h @@ -0,0 +1,43 @@ +// +// 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 "DWDPIncomingRequestItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol DWDPNewIncomingRequestItemDelegate + +- (void)acceptIncomingRequest:(id)item; +- (void)declineIncomingRequest:(id)item; + +@end + +typedef NS_ENUM(NSUInteger, DWDPNewIncomingRequestItemState) { + DWDPNewIncomingRequestItemState_Ready, + DWDPNewIncomingRequestItemState_Processing, + DWDPNewIncomingRequestItemState_Accepted, // succeeded + DWDPNewIncomingRequestItemState_Declined, // succeeded + DWDPNewIncomingRequestItemState_Failed, +}; + +@protocol DWDPNewIncomingRequestItem + +@property (nonatomic, assign) DWDPNewIncomingRequestItemState requestState; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNotificationItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNotificationItem.h new file mode 100644 index 000000000..9e5d8ec2f --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPNotificationItem.h @@ -0,0 +1,28 @@ +// +// 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 + +@protocol DWDPNotificationItem + +@property (readonly, nonatomic, strong) NSDate *date; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPPendingRequestItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPPendingRequestItem.h new file mode 100644 index 000000000..44d3526a5 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPPendingRequestItem.h @@ -0,0 +1,26 @@ +// +// 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 "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol DWDPPendingRequestItem + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPRespondedRequestItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPRespondedRequestItem.h new file mode 100644 index 000000000..78f80b7c2 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPRespondedRequestItem.h @@ -0,0 +1,26 @@ +// +// 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 "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol DWDPRespondedRequestItem + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPTxItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPTxItem.h new file mode 100644 index 000000000..3ea70f881 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/DWDPTxItem.h @@ -0,0 +1,32 @@ +// +// 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 "DWDPBasicItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSTransaction; + +@protocol DWDPTxItem + +/// Contains both Dash and fiat amounts +@property (readonly, nonatomic, copy) NSAttributedString *amountString; +@property (readonly, nonatomic, strong) DSTransaction *transaction; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPBlockchainIdentityBackedItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPBlockchainIdentityBackedItem.h new file mode 100644 index 000000000..7124e0d72 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPBlockchainIdentityBackedItem.h @@ -0,0 +1,30 @@ +// +// 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 + +@class DSBlockchainIdentity; + +@protocol DWDPBlockchainIdentityBackedItem + +@property (readonly, nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPDashpayUserBackedItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPDashpayUserBackedItem.h new file mode 100644 index 000000000..4d5b3283b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPDashpayUserBackedItem.h @@ -0,0 +1,30 @@ +// +// 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 + +@class DSDashpayUserEntity; + +@protocol DWDPDashpayUserBackedItem + +@property (readonly, nonatomic, strong) DSDashpayUserEntity *userEntity; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPFriendRequestBackedItem.h b/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPFriendRequestBackedItem.h new file mode 100644 index 000000000..f400a6991 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Protocols/Items Associated Data/DWDPFriendRequestBackedItem.h @@ -0,0 +1,30 @@ +// +// 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 + +@class DSFriendRequestEntity; + +@protocol DWDPFriendRequestBackedItem + +@property (readonly, strong, nonatomic) DSFriendRequestEntity *friendRequestEntity; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.h b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.h new file mode 100644 index 000000000..24b6bf9f7 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.h @@ -0,0 +1,33 @@ +// +// 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 "DWDPGenericItemView.h" + +#import "DWDPNewIncomingRequestItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPGenericContactRequestItemView : DWDPGenericItemView + +@property (readonly, nonatomic, strong) UIButton *acceptButton; +@property (readonly, nonatomic, strong) UIButton *declineButton; + +@property (nonatomic, assign) DWDPNewIncomingRequestItemState requestState; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.m new file mode 100644 index 000000000..4112b9e6b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericContactRequestItemView.m @@ -0,0 +1,182 @@ +// +// 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 "DWDPGenericContactRequestItemView.h" + +#import "DWActionButton.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPGenericContactRequestItemView () + +@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; +@property (readonly, nonatomic, strong) UIImageView *statusImageView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPGenericContactRequestItemView + +@synthesize acceptButton = _acceptButton; +@synthesize declineButton = _declineButton; + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setup_contactRequestItemView]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self setup_contactRequestItemView]; + } + return self; +} + +- (void)setup_contactRequestItemView { + DWActionButton *acceptButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + acceptButton.translatesAutoresizingMaskIntoConstraints = NO; + acceptButton.small = YES; + [acceptButton setTitle:NSLocalizedString(@"Accept", nil) forState:UIControlStateNormal]; + [self.accessoryView addSubview:acceptButton]; + _acceptButton = acceptButton; + + DWActionButton *declineButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + declineButton.translatesAutoresizingMaskIntoConstraints = NO; + declineButton.accentColor = [UIColor dw_declineButtonColor]; + [declineButton setImage:[UIImage imageNamed:@"icon_decline"] forState:UIControlStateNormal]; + [self.accessoryView addSubview:declineButton]; + _declineButton = declineButton; + + UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; + activityIndicatorView.color = [UIColor dw_tertiaryTextColor]; + [self.accessoryView addSubview:activityIndicatorView]; + _activityIndicatorView = activityIndicatorView; + + UIImageView *statusImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + statusImageView.translatesAutoresizingMaskIntoConstraints = NO; + statusImageView.contentMode = UIViewContentModeCenter; + statusImageView.image = [UIImage imageNamed:@"dp_established_contact"]; + [self.accessoryView addSubview:statusImageView]; + _statusImageView = statusImageView; + + const CGFloat buttonHeight = 30.0; + const CGFloat spacing = 10.0; + + [statusImageView setContentCompressionResistancePriority:UILayoutPriorityRequired - 10 forAxis:UILayoutConstraintAxisHorizontal]; + [statusImageView setContentCompressionResistancePriority:UILayoutPriorityRequired - 10 forAxis:UILayoutConstraintAxisVertical]; + + NSLayoutConstraint *acceptTopConstraint = [acceptButton.topAnchor constraintGreaterThanOrEqualToAnchor:self.accessoryView.topAnchor]; + acceptTopConstraint.priority = UILayoutPriorityRequired - 20; + NSLayoutConstraint *acceptBottomConstraint = [self.accessoryView.bottomAnchor constraintGreaterThanOrEqualToAnchor:acceptButton.bottomAnchor]; + acceptBottomConstraint.priority = UILayoutPriorityRequired - 19; + NSLayoutConstraint *declineTopConstraint = [declineButton.topAnchor constraintGreaterThanOrEqualToAnchor:self.accessoryView.topAnchor]; + declineTopConstraint.priority = UILayoutPriorityRequired - 18; + NSLayoutConstraint *declineBottomConstraint = [self.accessoryView.bottomAnchor constraintGreaterThanOrEqualToAnchor:declineButton.bottomAnchor]; + declineBottomConstraint.priority = UILayoutPriorityRequired - 17; + + [NSLayoutConstraint activateConstraints:@[ + acceptTopConstraint, + [acceptButton.leadingAnchor constraintEqualToAnchor:self.accessoryView.leadingAnchor], + acceptBottomConstraint, + [acceptButton.centerYAnchor constraintEqualToAnchor:self.accessoryView.centerYAnchor], + [acceptButton.heightAnchor constraintEqualToConstant:buttonHeight], + + declineTopConstraint, + [declineButton.leadingAnchor constraintEqualToAnchor:acceptButton.trailingAnchor + constant:spacing], + [self.accessoryView.trailingAnchor constraintEqualToAnchor:declineButton.trailingAnchor], + declineBottomConstraint, + [declineButton.centerYAnchor constraintEqualToAnchor:self.accessoryView.centerYAnchor], + [declineButton.heightAnchor constraintEqualToConstant:buttonHeight], + [declineButton.widthAnchor constraintEqualToConstant:buttonHeight], + + [self.accessoryView.trailingAnchor constraintEqualToAnchor:activityIndicatorView.trailingAnchor], + [activityIndicatorView.centerYAnchor constraintEqualToAnchor:self.accessoryView.centerYAnchor], + + [statusImageView.topAnchor constraintEqualToAnchor:self.accessoryView.topAnchor], + [self.accessoryView.trailingAnchor constraintEqualToAnchor:statusImageView.trailingAnchor], + [self.accessoryView.bottomAnchor constraintEqualToAnchor:statusImageView.bottomAnchor], + ]]; +} + +- (void)setRequestState:(DWDPNewIncomingRequestItemState)requestState { + _requestState = requestState; + + switch (requestState) { + case DWDPNewIncomingRequestItemState_Ready: + [self setReadyState]; + break; + case DWDPNewIncomingRequestItemState_Processing: + [self setProcessingState]; + break; + case DWDPNewIncomingRequestItemState_Accepted: + [self setAcceptedState]; + break; + case DWDPNewIncomingRequestItemState_Declined: + [self setDeclinedState]; + break; + case DWDPNewIncomingRequestItemState_Failed: + [self setFailedState]; + break; + } +} + +- (void)setReadyState { + self.acceptButton.hidden = NO; + self.declineButton.hidden = NO; + + self.statusImageView.hidden = YES; + + [self.activityIndicatorView stopAnimating]; +} + +- (void)setProcessingState { + [self.activityIndicatorView startAnimating]; + + self.acceptButton.hidden = YES; + self.declineButton.hidden = YES; + + self.statusImageView.hidden = YES; +} + +- (void)setAcceptedState { + [self.activityIndicatorView stopAnimating]; + + self.acceptButton.hidden = YES; + self.declineButton.hidden = YES; + + self.statusImageView.hidden = NO; +} + +- (void)setDeclinedState { + // TODO: DP impl + [self setAcceptedState]; +} + +- (void)setFailedState { + // TODO: DP impl + [self setReadyState]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.h b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.h new file mode 100644 index 000000000..4dccd1a58 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.h @@ -0,0 +1,28 @@ +// +// 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 "DWDPGenericItemView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPGenericImageItemView : DWDPGenericItemView + +@property (readonly, nonatomic, strong) UIImageView *imageView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.m new file mode 100644 index 000000000..050463d9a --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericImageItemView.m @@ -0,0 +1,58 @@ +// +// 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 "DWDPGenericImageItemView.h" + +@implementation DWDPGenericImageItemView + +@synthesize imageView = _imageView; + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setup_imageItemView]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self setup_imageItemView]; + } + return self; +} + +- (void)setup_imageItemView { + UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.contentMode = UIViewContentModeCenter; + [self.accessoryView addSubview:imageView]; + _imageView = imageView; + + [imageView setContentCompressionResistancePriority:UILayoutPriorityRequired - 10 forAxis:UILayoutConstraintAxisHorizontal]; + [imageView setContentCompressionResistancePriority:UILayoutPriorityRequired - 10 forAxis:UILayoutConstraintAxisVertical]; + + [NSLayoutConstraint activateConstraints:@[ + [imageView.topAnchor constraintEqualToAnchor:self.accessoryView.topAnchor], + [imageView.leadingAnchor constraintEqualToAnchor:self.accessoryView.leadingAnchor], + [self.accessoryView.trailingAnchor constraintEqualToAnchor:imageView.trailingAnchor], + [self.accessoryView.bottomAnchor constraintEqualToAnchor:imageView.bottomAnchor], + ]]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.h b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.h new file mode 100644 index 000000000..800730cc7 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.h @@ -0,0 +1,34 @@ +// +// 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 + +#import "DWDPAvatarView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPGenericItemView : UIView + +@property (nonatomic, assign, getter=isAvatarHidden) BOOL avatarHidden; + +@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; +@property (readonly, nonatomic, strong) UILabel *textLabel; +@property (readonly, nonatomic, strong) UIView *accessoryView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.m new file mode 100644 index 000000000..30a057d45 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericItemView.m @@ -0,0 +1,131 @@ +// +// 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 "DWDPGenericItemView.h" + +#import "DWUIKit.h" +#import "UIFont+DWDPItem.h" + +static CGFloat const AVATAR_SIZE = 36.0; +static CGFloat const SPACING = 10.0; + +@interface DWDPGenericItemView () + +@property (readonly, nonatomic, strong) NSLayoutConstraint *textLabelLeadingConstraint; + +@end + +@implementation DWDPGenericItemView + +@synthesize avatarView = _avatarView; +@synthesize textLabel = _textLabel; +@synthesize accessoryView = _accessoryView; + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setup_genericItemView]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self setup_genericItemView]; + } + return self; +} + +- (void)setup_genericItemView { + DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:CGRectZero]; + avatarView.translatesAutoresizingMaskIntoConstraints = NO; + avatarView.small = YES; + avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; + [self addSubview:avatarView]; + _avatarView = avatarView; + + UILabel *textLabel = [[UILabel alloc] init]; + textLabel.translatesAutoresizingMaskIntoConstraints = NO; + textLabel.font = [UIFont dw_itemTitleFont]; + textLabel.adjustsFontForContentSizeCategory = YES; + textLabel.textColor = [UIColor dw_darkTitleColor]; + textLabel.numberOfLines = 0; + [self addSubview:textLabel]; + _textLabel = textLabel; + + UIView *accessoryView = [[UIView alloc] init]; + accessoryView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:accessoryView]; + _accessoryView = accessoryView; + + [textLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [accessoryView setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 forAxis:UILayoutConstraintAxisVertical]; + + [textLabel setContentHuggingPriority:UILayoutPriorityDefaultLow - 1 forAxis:UILayoutConstraintAxisHorizontal]; + [accessoryView setContentHuggingPriority:UILayoutPriorityDefaultLow + 1 forAxis:UILayoutConstraintAxisHorizontal]; + + [textLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 3 forAxis:UILayoutConstraintAxisHorizontal]; + [accessoryView setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 forAxis:UILayoutConstraintAxisHorizontal]; + + NSLayoutConstraint *avatarTopConstraint = [avatarView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor]; + avatarTopConstraint.priority = UILayoutPriorityRequired - 10; + NSLayoutConstraint *avatarBottomConstraint = [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:avatarView.bottomAnchor]; + avatarBottomConstraint.priority = UILayoutPriorityRequired - 11; + + _textLabelLeadingConstraint = [textLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:AVATAR_SIZE + SPACING]; + + [NSLayoutConstraint activateConstraints:@[ + [avatarView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [avatarView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + avatarTopConstraint, + avatarBottomConstraint, + [avatarView.widthAnchor constraintEqualToConstant:AVATAR_SIZE], + [avatarView.heightAnchor constraintEqualToConstant:AVATAR_SIZE], + + [textLabel.topAnchor constraintEqualToAnchor:self.topAnchor], + _textLabelLeadingConstraint, + [self.bottomAnchor constraintEqualToAnchor:textLabel.bottomAnchor], + + [accessoryView.topAnchor constraintEqualToAnchor:self.topAnchor], + [accessoryView.leadingAnchor constraintEqualToAnchor:textLabel.trailingAnchor + constant:SPACING], + [self.trailingAnchor constraintEqualToAnchor:accessoryView.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:accessoryView.bottomAnchor], + ]]; +} + +- (void)setBackgroundColor:(UIColor *)backgroundColor { + [super setBackgroundColor:backgroundColor]; + + self.textLabel.backgroundColor = backgroundColor; + self.accessoryView.backgroundColor = backgroundColor; +} + +- (void)setAvatarHidden:(BOOL)avatarHidden { + _avatarHidden = avatarHidden; + + self.avatarView.hidden = avatarHidden; + if (avatarHidden) { + self.textLabelLeadingConstraint.constant = 0.0; + } + else { + self.textLabelLeadingConstraint.constant = AVATAR_SIZE + SPACING; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.h b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.h new file mode 100644 index 000000000..9ffcc3568 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.h @@ -0,0 +1,28 @@ +// +// 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 "DWDPGenericItemView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPGenericStatusItemView : DWDPGenericItemView + +@property (readonly, nonatomic, strong) UILabel *statusLabel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.m new file mode 100644 index 000000000..81679a4c1 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPGenericStatusItemView.m @@ -0,0 +1,68 @@ +// +// 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 "DWDPGenericStatusItemView.h" + +#import "DWUIKit.h" +#import "UIFont+DWDPItem.h" + +@implementation DWDPGenericStatusItemView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setup_statusItemView]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self setup_statusItemView]; + } + return self; +} + +- (void)setup_statusItemView { + UILabel *statusLabel = [[UILabel alloc] init]; + statusLabel.translatesAutoresizingMaskIntoConstraints = NO; + statusLabel.font = [UIFont dw_itemSubtitleFont]; + statusLabel.adjustsFontForContentSizeCategory = YES; + statusLabel.textColor = [UIColor dw_tertiaryTextColor]; + statusLabel.numberOfLines = 0; + statusLabel.textAlignment = NSTextAlignmentRight; + [self.accessoryView addSubview:statusLabel]; + _statusLabel = statusLabel; + + [statusLabel setContentHuggingPriority:UILayoutPriorityDefaultLow - 3 forAxis:UILayoutConstraintAxisHorizontal]; + + [NSLayoutConstraint activateConstraints:@[ + [statusLabel.topAnchor constraintEqualToAnchor:self.accessoryView.topAnchor], + [statusLabel.leadingAnchor constraintEqualToAnchor:self.accessoryView.leadingAnchor], + [self.accessoryView.trailingAnchor constraintEqualToAnchor:statusLabel.trailingAnchor], + [self.accessoryView.bottomAnchor constraintEqualToAnchor:statusLabel.bottomAnchor], + ]]; +} + +- (void)setBackgroundColor:(UIColor *)backgroundColor { + [super setBackgroundColor:backgroundColor]; + + self.statusLabel.backgroundColor = backgroundColor; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.h b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.h new file mode 100644 index 000000000..574b9d54f --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.h @@ -0,0 +1,28 @@ +// +// 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 "DWDPGenericItemView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPTxItemView : DWDPGenericItemView + +@property (readonly, nonatomic, strong) UILabel *amountLabel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.m b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.m new file mode 100644 index 000000000..29a4dba4b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/ContentViews/DWDPTxItemView.m @@ -0,0 +1,67 @@ +// +// 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 "DWDPTxItemView.h" + +#import "DWUIKit.h" +#import "UIFont+DWDPItem.h" + +@implementation DWDPTxItemView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setup_statusItemView]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self setup_statusItemView]; + } + return self; +} + +- (void)setup_statusItemView { + UILabel *amountLabel = [[UILabel alloc] init]; + amountLabel.translatesAutoresizingMaskIntoConstraints = NO; + amountLabel.font = [UIFont dw_itemSubtitleFont]; + amountLabel.adjustsFontForContentSizeCategory = YES; + amountLabel.textColor = [UIColor dw_darkTitleColor]; + amountLabel.numberOfLines = 0; + amountLabel.textAlignment = NSTextAlignmentRight; + [self.accessoryView addSubview:amountLabel]; + _amountLabel = amountLabel; + + [amountLabel setContentHuggingPriority:UILayoutPriorityDefaultLow - 3 forAxis:UILayoutConstraintAxisHorizontal]; + + [NSLayoutConstraint activateConstraints:@[ + [amountLabel.topAnchor constraintEqualToAnchor:self.accessoryView.topAnchor], + [amountLabel.leadingAnchor constraintEqualToAnchor:self.accessoryView.leadingAnchor], + [self.accessoryView.trailingAnchor constraintEqualToAnchor:amountLabel.trailingAnchor], + [self.accessoryView.bottomAnchor constraintEqualToAnchor:amountLabel.bottomAnchor], + ]]; +} + +- (void)setBackgroundColor:(UIColor *)backgroundColor { + [super setBackgroundColor:backgroundColor]; + + self.amountLabel.backgroundColor = backgroundColor; +} +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.h b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.h new file mode 100644 index 000000000..2ea7fdebe --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.h @@ -0,0 +1,51 @@ +// +// 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 + +#import "DWDPGenericItemView.h" + +// supports displaying: +#import "DWDPBasicItem.h" +#import "DWDPRespondedRequestItem.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, DWDPBasicCellBackgroundStyle) { + DWDPBasicCellBackgroundStyle_GrayOnGray, + DWDPBasicCellBackgroundStyle_WhiteOnGray, + DWDPBasicCellBackgroundStyle_GrayOnWhite, +}; + +@interface DWDPBasicCell : KVOUICollectionViewCell + +@property (readonly, class, nonatomic) Class itemViewClass; + +@property (readonly, nonatomic, strong) DWDPGenericItemView *itemView; +@property (nonatomic, assign) CGFloat contentWidth; +@property (nonatomic, assign) DWDPBasicCellBackgroundStyle backgroundStyle; + +@property (nullable, nonatomic, weak) id delegate; + +@property (nullable, nonatomic, strong) id item; +- (void)setItem:(id)item highlightedText:(nullable NSString *)highlightedText NS_REQUIRES_SUPER; + +- (void)reloadAttributedData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.m b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.m new file mode 100644 index 000000000..6ab674204 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPBasicCell.m @@ -0,0 +1,222 @@ +// +// 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 "DWDPBasicCell.h" + +#import "DWShadowView.h" +#import "DWUIKit.h" +#import "NSAttributedString+DWHighlightText.h" +#import "UIFont+DWDPItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPBasicCell () + +@property (readonly, nonatomic, strong) DWShadowView *shadowView; +@property (readonly, nonatomic, strong) UIView *roundedContentView; +@property (nullable, nonatomic, copy) NSString *highlightedText; +@property (nullable, nonatomic, strong) NSLayoutConstraint *contentWidthConstraint; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPBasicCell + ++ (Class)itemViewClass { + return DWDPGenericItemView.class; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.contentView.backgroundColor = self.backgroundColor; + + DWShadowView *shadowView = [[DWShadowView alloc] initWithFrame:CGRectZero]; + shadowView.translatesAutoresizingMaskIntoConstraints = NO; + [self.contentView addSubview:shadowView]; + _shadowView = shadowView; + + UIView *roundedContentView = [[UIView alloc] initWithFrame:CGRectZero]; + roundedContentView.translatesAutoresizingMaskIntoConstraints = NO; + roundedContentView.backgroundColor = [UIColor dw_backgroundColor]; + roundedContentView.layer.cornerRadius = 8.0; + roundedContentView.layer.masksToBounds = YES; + _roundedContentView = roundedContentView; + [shadowView addSubview:roundedContentView]; + + Class klass = [self.class itemViewClass]; + DWDPGenericItemView *itemView = [[klass alloc] initWithFrame:CGRectZero]; + itemView.translatesAutoresizingMaskIntoConstraints = NO; + itemView.backgroundColor = self.backgroundColor; + [self.contentView addSubview:itemView]; + _itemView = itemView; + + const CGFloat horizontalPadding = 10.0; + const CGFloat verticalPadding = 10.0; + const CGFloat itemVerticalPadding = 23.0; + const CGFloat itemHorizontalPadding = verticalPadding + 10.0; + + UILayoutGuide *guide = self.contentView.layoutMarginsGuide; + [NSLayoutConstraint activateConstraints:@[ + [shadowView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor + constant:verticalPadding], + [shadowView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor + constant:horizontalPadding], + [guide.trailingAnchor constraintEqualToAnchor:shadowView.trailingAnchor + constant:horizontalPadding], + [self.contentView.bottomAnchor constraintEqualToAnchor:shadowView.bottomAnchor + constant:verticalPadding], + + [roundedContentView.topAnchor constraintEqualToAnchor:shadowView.topAnchor], + [roundedContentView.leadingAnchor constraintEqualToAnchor:shadowView.leadingAnchor], + [shadowView.trailingAnchor constraintEqualToAnchor:roundedContentView.trailingAnchor], + [shadowView.bottomAnchor constraintEqualToAnchor:roundedContentView.bottomAnchor], + + [itemView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor + constant:itemVerticalPadding], + [itemView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor + constant:itemHorizontalPadding], + [guide.trailingAnchor constraintEqualToAnchor:itemView.trailingAnchor + constant:itemHorizontalPadding], + [self.contentView.bottomAnchor constraintEqualToAnchor:itemView.bottomAnchor + constant:itemVerticalPadding], + (_contentWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:200]), + ]]; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(contentSizeCategoryDidChangeNotification) + name:UIContentSizeCategoryDidChangeNotification + object:nil]; + } + return self; +} + +- (void)setBackgroundStyle:(DWDPBasicCellBackgroundStyle)backgroundStyle { + _backgroundStyle = backgroundStyle; + + switch (backgroundStyle) { + case DWDPBasicCellBackgroundStyle_GrayOnGray: + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.itemView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.shadowView.hidden = YES; + break; + case DWDPBasicCellBackgroundStyle_WhiteOnGray: + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.itemView.backgroundColor = [UIColor dw_backgroundColor]; + self.roundedContentView.backgroundColor = [UIColor dw_backgroundColor]; + self.shadowView.hidden = NO; + break; + case DWDPBasicCellBackgroundStyle_GrayOnWhite: + self.backgroundColor = [UIColor dw_backgroundColor]; + self.itemView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.roundedContentView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.shadowView.hidden = NO; + break; + } + self.contentView.backgroundColor = self.backgroundColor; +} + +- (CGFloat)contentWidth { + return self.contentWidthConstraint.constant; +} + +- (void)setContentWidth:(CGFloat)contentWidth { + self.contentWidthConstraint.constant = contentWidth; +} + +- (void)setHighlighted:(BOOL)highlighted { + [super setHighlighted:highlighted]; + + [self dw_pressedAnimation:DWPressedAnimationStrength_Light pressed:highlighted]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + [self reloadAttributedData]; +} + +- (void)setItem:(id)item { + [self setItem:item highlightedText:nil]; +} + +- (void)setItem:(id)item highlightedText:(NSString *)highlightedText { + NSString *key = DW_KEYPATH(self, item); + [self willChangeValueForKey:key]; + _item = item; + [self didChangeValueForKey:key]; + + self.highlightedText = highlightedText; + + self.itemView.avatarView.blockchainIdentity = item.blockchainIdentity; + + [self reloadAttributedData]; +} + +#pragma mark - Notifications + +- (void)contentSizeCategoryDidChangeNotification { + [self reloadAttributedData]; +} + +#pragma mark - Private + +- (void)reloadAttributedData { + UIColor *highlightedTextColor = [UIColor dw_dashBlueColor]; + + NSAttributedString *titleString = [NSAttributedString + attributedText:self.item.title + textColor:[UIColor dw_darkTitleColor] + highlightedText:self.highlightedText + highlightedTextColor:highlightedTextColor]; + + NSAttributedString *subtitleString = [NSAttributedString + attributedText:self.item.subtitle + font:[UIFont dw_itemSubtitleFont] + textColor:[UIColor dw_tertiaryTextColor] + highlightedText:self.highlightedText + highlightedTextColor:highlightedTextColor]; + + NSAttributedString *resultString = nil; + if (titleString && subtitleString) { + NSMutableAttributedString *mutableResultString = [[NSMutableAttributedString alloc] init]; + [mutableResultString beginEditing]; + + [mutableResultString appendAttributedString:titleString]; + + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + style.maximumLineHeight = 4.0; + NSAttributedString *spacingString = [[NSAttributedString alloc] initWithString:@"\n\n" + attributes:@{NSParagraphStyleAttributeName : style}]; + [mutableResultString appendAttributedString:spacingString]; + + [mutableResultString appendAttributedString:subtitleString]; + + [mutableResultString endEditing]; + resultString = [mutableResultString copy]; + } + else { + resultString = titleString; + } + + self.itemView.textLabel.attributedText = resultString; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.h b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.h new file mode 100644 index 000000000..2261a2e79 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.h @@ -0,0 +1,26 @@ +// +// 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 "DWDPBasicCell.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPImageStatusCell : DWDPBasicCell + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.m b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.m new file mode 100644 index 000000000..8bac21ebc --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPImageStatusCell.m @@ -0,0 +1,49 @@ +// +// 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 "DWDPImageStatusCell.h" + +#import "DWDPGenericImageItemView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPImageStatusCell () + +@property (readonly, nonatomic, strong) DWDPGenericImageItemView *itemView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPImageStatusCell + +@dynamic itemView; + ++ (Class)itemViewClass { + return DWDPGenericImageItemView.class; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + UIImage *image = [UIImage imageNamed:@"dp_established_contact"]; + self.itemView.imageView.image = image; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.h b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.h new file mode 100644 index 000000000..c63676acb --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.h @@ -0,0 +1,31 @@ +// +// 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 "DWDPBasicCell.h" + +#import "DWDPNewIncomingRequestItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPIncomingRequestCell : DWDPBasicCell + +@property (nullable, nonatomic, strong) id item; +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.m b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.m new file mode 100644 index 000000000..8508c1969 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPIncomingRequestCell.m @@ -0,0 +1,78 @@ +// +// 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 "DWDPIncomingRequestCell.h" + +#import "DWDPGenericContactRequestItemView.h" + +#import "DWDPNewIncomingRequestObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPIncomingRequestCell () + +@property (readonly, nonatomic, strong) DWDPGenericContactRequestItemView *itemView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPIncomingRequestCell + +@dynamic item; +@dynamic itemView; +@dynamic delegate; + ++ (Class)itemViewClass { + return DWDPGenericContactRequestItemView.class; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self.itemView.acceptButton addTarget:self action:@selector(acceptButtonAction) forControlEvents:UIControlEventTouchUpInside]; + [self.itemView.declineButton addTarget:self action:@selector(declineButtonAction) forControlEvents:UIControlEventTouchUpInside]; + + [self mvvm_observe:@"item.requestState" + with:^(typeof(self) self, id value) { + [self updateItemRequestState]; + }]; + } + return self; +} + +#pragma mark - Actions + +- (void)acceptButtonAction { + [self.delegate acceptIncomingRequest:self.item]; +} + +- (void)declineButtonAction { + [self.delegate declineIncomingRequest:self.item]; +} + +#pragma mark - Private + +- (void)updateItemRequestState { + id requestItem = (id)self.item; + self.itemView.requestState = requestItem.requestState; +} + +- (void)dealloc { + [self mvvm_unobserveAll]; +} +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.h b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.h new file mode 100644 index 000000000..8cc123a3e --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.h @@ -0,0 +1,26 @@ +// +// 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 "DWDPBasicCell.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPTextStatusCell : DWDPBasicCell + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.m b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.m new file mode 100644 index 000000000..1c626f005 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTextStatusCell.m @@ -0,0 +1,49 @@ +// +// 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 "DWDPTextStatusCell.h" + +#import "DWDPGenericStatusItemView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPTextStatusCell () + +@property (readonly, nonatomic, strong) DWDPGenericStatusItemView *itemView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPTextStatusCell + +@dynamic itemView; + ++ (Class)itemViewClass { + return DWDPGenericStatusItemView.class; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + NSString *text = NSLocalizedString(@"Pending", nil); + self.itemView.statusLabel.text = text; + } + return self; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.h b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.h new file mode 100644 index 000000000..abb7f94fd --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.h @@ -0,0 +1,26 @@ +// +// 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 "DWDPBasicCell.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPTxListCell : DWDPBasicCell + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.m b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.m new file mode 100644 index 000000000..641c2bae6 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/DWDPTxListCell.m @@ -0,0 +1,49 @@ +// +// 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 "DWDPTxListCell.h" + +#import "DWDPTxItem.h" +#import "DWDPTxItemView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPTxListCell () + +@property (readonly, nonatomic, strong) DWDPTxItemView *itemView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPTxListCell + +@dynamic itemView; + ++ (Class)itemViewClass { + return DWDPTxItemView.class; +} + +- (void)reloadAttributedData { + [super reloadAttributedData]; + + id txItem = (id)self.item; + NSAssert([txItem conformsToProtocol:@protocol(DWDPTxItem)], @"Invalid item type"); + self.itemView.amountLabel.attributedText = txItem.amountString; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.h b/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.h new file mode 100644 index 000000000..e52129260 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.h @@ -0,0 +1,31 @@ +// +// 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 + +#import "DWDPBasicItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UICollectionView (DWDPItemDequeue) + +- (void)dw_registerDPItemCells; +- (__kindof UICollectionViewCell *)dw_dequeueReusableCellForItem:(id)item atIndexPath:(NSIndexPath *)indexPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.m b/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.m new file mode 100644 index 000000000..4104b9384 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/UICollectionView+DWDPItemDequeue.m @@ -0,0 +1,69 @@ +// +// 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 "UICollectionView+DWDPItemDequeue.h" + +#import "DWUIKit.h" + +#import "DWDPEstablishedContactItem.h" +#import "DWDPNewIncomingRequestItem.h" +#import "DWDPPendingRequestItem.h" +#import "DWDPRespondedRequestItem.h" +#import "DWDPTxItem.h" + +#import "DWDPBasicCell.h" +#import "DWDPImageStatusCell.h" +#import "DWDPIncomingRequestCell.h" +#import "DWDPTextStatusCell.h" +#import "DWDPTxListCell.h" + +@implementation UICollectionView (DWDPItemDequeue) + +- (void)dw_registerDPItemCells { + [self registerClass:DWDPBasicCell.class forCellWithReuseIdentifier:DWDPBasicCell.dw_reuseIdentifier]; + [self registerClass:DWDPIncomingRequestCell.class forCellWithReuseIdentifier:DWDPIncomingRequestCell.dw_reuseIdentifier]; + [self registerClass:DWDPImageStatusCell.class forCellWithReuseIdentifier:DWDPImageStatusCell.dw_reuseIdentifier]; + [self registerClass:DWDPTextStatusCell.class forCellWithReuseIdentifier:DWDPTextStatusCell.dw_reuseIdentifier]; + [self registerClass:DWDPTxListCell.class forCellWithReuseIdentifier:DWDPTxListCell.dw_reuseIdentifier]; +} + +- (__kindof UICollectionViewCell *)dw_dequeueReusableCellForItem:(id)item atIndexPath:(NSIndexPath *)indexPath { + // DWDPRespondedRequestItem should come before DWDPIncomingRequestItem since the first one also conforms to DWDPIncomingRequestItem + NSString *cellID = nil; + if ([item conformsToProtocol:@protocol(DWDPEstablishedContactItem)]) { + cellID = DWDPImageStatusCell.dw_reuseIdentifier; + } + else if ([item conformsToProtocol:@protocol(DWDPPendingRequestItem)]) { + cellID = DWDPTextStatusCell.dw_reuseIdentifier; + } + else if ([item conformsToProtocol:@protocol(DWDPRespondedRequestItem)]) { + cellID = DWDPBasicCell.dw_reuseIdentifier; + } + else if ([item conformsToProtocol:@protocol(DWDPNewIncomingRequestItem)]) { + cellID = DWDPIncomingRequestCell.dw_reuseIdentifier; + } + else if ([item conformsToProtocol:@protocol(DWDPTxItem)]) { + cellID = DWDPTxListCell.dw_reuseIdentifier; + } + else { // any DWDPBasicUserItem + cellID = DWDPBasicCell.dw_reuseIdentifier; + } + + return [self dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.h b/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.h new file mode 100644 index 000000000..a190dd566 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.h @@ -0,0 +1,29 @@ +// +// 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 UIFont (DWDPItem) + ++ (UIFont *)dw_itemTitleFont; ++ (UIFont *)dw_itemSubtitleFont; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.m b/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.m new file mode 100644 index 000000000..f964f4ec0 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Items/Views/UIFont+DWDPItem.m @@ -0,0 +1,32 @@ +// +// 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 "UIFont+DWDPItem.h" + +#import "UIFont+DWFont.h" + +@implementation UIFont (DWDPItem) + ++ (UIFont *)dw_itemTitleFont { + return [UIFont dw_fontForTextStyle:UIFontTextStyleSubheadline]; +} + ++ (UIFont *)dw_itemSubtitleFont { + return [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.h b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.h new file mode 100644 index 000000000..5f88df09d --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.h @@ -0,0 +1,28 @@ +// +// 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 DWNoNotificationsCell : UICollectionViewCell + +@property (nonatomic, assign) CGFloat contentWidth; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.m b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.m new file mode 100644 index 000000000..56c76b57b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNoNotificationsCell.m @@ -0,0 +1,109 @@ +// +// 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 "DWNoNotificationsCell.h" + +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWNoNotificationsCell () + +@property (nullable, nonatomic, strong) NSLayoutConstraint *contentWidthConstraint; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWNoNotificationsCell + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.contentView.backgroundColor = self.backgroundColor; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + [self.contentView addSubview:contentView]; + + UIImage *image = [UIImage imageNamed:@"dp_no_notifications"]; + UIImageView *iconImageView = [[UIImageView alloc] initWithImage:image]; + iconImageView.translatesAutoresizingMaskIntoConstraints = NO; + iconImageView.contentMode = UIViewContentModeScaleAspectFit; + [contentView addSubview:iconImageView]; + + UILabel *label = [[UILabel alloc] init]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.backgroundColor = self.backgroundColor; + label.numberOfLines = 0; + label.textAlignment = NSTextAlignmentCenter; + label.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + label.adjustsFontForContentSizeCategory = YES; + label.textColor = [UIColor dw_tertiaryTextColor]; + label.text = NSLocalizedString(@"There are no new notifications", nil); + [contentView addSubview:label]; + + [iconImageView setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + [label setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + [iconImageView setContentHuggingPriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + [label setContentHuggingPriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + + const CGFloat spacing = 30.0; + + NSLayoutConstraint *heightConstraint = [contentView.heightAnchor constraintLessThanOrEqualToConstant:200]; + heightConstraint.priority = UILayoutPriorityRequired - 1; + + [NSLayoutConstraint activateConstraints:@[ + [contentView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor], + [contentView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor + constant:16], + [self.contentView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + [self.contentView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor + constant:16], + heightConstraint, + + [iconImageView.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:spacing], + [iconImageView.centerXAnchor constraintEqualToAnchor:contentView.centerXAnchor], + + [label.topAnchor constraintEqualToAnchor:iconImageView.bottomAnchor + constant:spacing], + [label.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor], + [contentView.bottomAnchor constraintEqualToAnchor:label.bottomAnchor + constant:spacing], + [contentView.trailingAnchor constraintEqualToAnchor:label.trailingAnchor], + + (_contentWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:200]), + ]]; + } + return self; +} + +- (CGFloat)contentWidth { + return self.contentWidthConstraint.constant; +} + +- (void)setContentWidth:(CGFloat)contentWidth { + self.contentWidthConstraint.constant = contentWidth; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.m b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.m index 3b1847fb4..64c34ac55 100644 --- a/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.m +++ b/DashWallet/Sources/UI/DashPay/Notifications/Cells/DWNotificationsInvitationCell.m @@ -35,7 +35,7 @@ - (instancetype)initWithFrame:(CGRect)frame { if (self) { UIView *view = [[UIView alloc] init]; view.translatesAutoresizingMaskIntoConstraints = NO; - view.backgroundColor = [UIColor dw_lightBlueColor]; + view.backgroundColor = [UIColor dw_dashNavigationBlueColor]; view.layer.cornerRadius = 8; view.layer.masksToBounds = YES; [self.contentView addSubview:view]; diff --git a/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.h b/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.h new file mode 100644 index 000000000..5db6931e8 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.h @@ -0,0 +1,29 @@ +// +// 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 + +/// UITableView-like collection layout. +@interface DWListCollectionLayout : UICollectionViewFlowLayout + +@property (readonly, nonatomic, assign) CGFloat contentWidth; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.m b/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.m new file mode 100644 index 000000000..b27dba19b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Notifications/DWListCollectionLayout.m @@ -0,0 +1,41 @@ +// +// 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 "DWListCollectionLayout.h" + +@implementation DWListCollectionLayout + +- (instancetype)init { + self = [super init]; + if (self) { + self.scrollDirection = UICollectionViewScrollDirectionVertical; + // Using UICollectionViewFlowLayoutAutomaticSize leads to layout issues on reloadData + self.estimatedItemSize = CGSizeMake(320, 150); + self.sectionInset = UIEdgeInsetsZero; + self.minimumInteritemSpacing = 0; + self.minimumLineSpacing = 0; + // disabled due to scrolling issues. needs further investigation + // self.sectionHeadersPinToVisibleBounds = YES; + } + return self; +} + +- (CGFloat)contentWidth { + return ceil(CGRectGetWidth(self.collectionView.safeAreaLayoutGuide.layoutFrame) - self.sectionInset.left - self.sectionInset.right); +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.h b/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.h similarity index 69% rename from DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.h rename to DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.h index 6fb0c9902..18eb561e7 100644 --- a/DashWallet/Sources/UI/DashPay/Error/DWNetworkErrorViewController.h +++ b/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.h @@ -17,22 +17,20 @@ #import -NS_ASSUME_NONNULL_BEGIN +#import "DWPayModelProtocol.h" +#import "DWTransactionListDataProviderProtocol.h" -typedef NS_ENUM(NSUInteger, DWErrorDescriptionType) { - DWErrorDescriptionType_Profile, - DWErrorDescriptionType_AcceptContactRequest, - DWErrorDescriptionType_SendContactRequest, -}; +NS_ASSUME_NONNULL_BEGIN -@interface DWNetworkErrorViewController : UIViewController +@interface DWNotificationsViewController : UIViewController -- (instancetype)initWithType:(DWErrorDescriptionType)type; +- (instancetype)initWithPayModel:(id)payModel + dataProvider:(id)dataProvider NS_DESIGNATED_INITIALIZER; +- (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; -- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE; -- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; @end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.m b/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.m new file mode 100644 index 000000000..1f9a4f986 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Notifications/DWNotificationsViewController.m @@ -0,0 +1,385 @@ +// +// 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 "DWNotificationsViewController.h" + +#import "DWDPBasicCell.h" +#import "DWDPNewIncomingRequestItem.h" +#import "DWDashPayConstants.h" +#import "DWEnvironment.h" +#import "DWGlobalOptions.h" +#import "DWListCollectionLayout.h" +#import "DWNoNotificationsCell.h" +#import "DWNotificationsInvitationCell.h" +#import "DWNotificationsModel.h" +#import "DWSendInviteFlowController.h" +#import "DWTitleActionHeaderView.h" +#import "DWUIKit.h" +#import "DWUserProfileViewController.h" +#import "UICollectionView+DWDPItemDequeue.h" + +NS_ASSUME_NONNULL_BEGIN + +static NSString *const NotificationsInvitationMessageHiddenKey = @"NotificationsInvitationMessageHiddenKey"; + +@interface DWNotificationsViewController () + +@property (readonly, nonatomic, strong) id payModel; +@property (readonly, nonatomic, strong) id dataProvider; + +@property (null_resettable, nonatomic, strong) DWNotificationsModel *model; +@property (null_resettable, nonatomic, strong) UICollectionView *collectionView; +@property (null_resettable, nonatomic, strong) DWTitleActionHeaderView *measuringHeaderView; +@property (null_resettable, nonatomic, strong) DWNotificationsInvitationCell *measuringInvitationView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWNotificationsViewController + +- (instancetype)initWithPayModel:(id)payModel + dataProvider:(id)dataProvider { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _payModel = payModel; + _dataProvider = dataProvider; + + self.hidesBottomBarWhenPushed = YES; + } + return self; +} + +- (void)dealloc { + DSLog(@"☠️ %@", NSStringFromClass(self.class)); +} + +- (BOOL)invitationMessageHidden { + //TODO: DashPay +// if ([DWGlobalOptions sharedInstance].dpInvitationFlowEnabled == NO) { +// return YES; +// } + + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + const uint64_t balanceValue = wallet.balance; + BOOL isEnoughBalance = balanceValue > DWDP_MIN_BALANCE_TO_CREATE_INVITE; + if (!isEnoughBalance) { + return YES; + } + + return [[NSUserDefaults standardUserDefaults] boolForKey:NotificationsInvitationMessageHiddenKey]; +} + +- (void)setInvitationMessageHidden:(BOOL)isHidden { + [[NSUserDefaults standardUserDefaults] setBool:isHidden forKey:NotificationsInvitationMessageHiddenKey]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self updateTitle]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + [self.view addSubview:self.collectionView]; + + [NSLayoutConstraint activateConstraints:@[ + [self.collectionView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], + [self.collectionView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:self.collectionView.trailingAnchor], + [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:self.collectionView.bottomAnchor], + ]]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + //TODO: DashPay + //[DWGlobalOptions sharedInstance].shouldShowInvitationsBadge = NO; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + if (self.isMovingFromParentViewController) { + [self.model saveMostRecentViewedNotificationDate]; + } +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return 3; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + DWNotificationsData *data = self.model.data; + if (section == 0) { + if ([self invitationMessageHidden]) { + return 0; + } + else { + return 1; + } + } + else if (section == 1) { + if (data.unreadItems.count == 0) { + return 1; // empty state + } + else { + return data.unreadItems.count; + } + } + else { // 2 + return data.oldItems.count; + } +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; + NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); + const CGFloat contentWidth = layout.contentWidth; + + if (indexPath.section == 0) { + DWNotificationsInvitationCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:DWNotificationsInvitationCell.dw_reuseIdentifier forIndexPath:indexPath]; + cell.contentWidth = contentWidth; + cell.delegate = self; + return cell; + } + + if (indexPath.section == 1 && self.model.data.unreadItems.count == 0) { + DWNoNotificationsCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:DWNoNotificationsCell.dw_reuseIdentifier + forIndexPath:indexPath]; + cell.contentWidth = contentWidth; + return cell; + } + + id item = [self itemAtIndexPath:indexPath]; + + DWDPBasicCell *cell = [collectionView dw_dequeueReusableCellForItem:item atIndexPath:indexPath]; + if (indexPath.section == 1) { + cell.backgroundStyle = DWDPBasicCellBackgroundStyle_WhiteOnGray; + } + else { + cell.backgroundStyle = DWDPBasicCellBackgroundStyle_GrayOnGray; + } + cell.contentWidth = contentWidth; + cell.delegate = self; + cell.item = item; + return cell; +} + +#pragma mark - UICollectionViewDelegate + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + [collectionView deselectItemAtIndexPath:indexPath animated:YES]; + + if (indexPath.section == 0) { + DWSendInviteFlowController *controller = [[DWSendInviteFlowController alloc] init]; + controller.delegate = self; + [self presentViewController:controller animated:YES completion:nil]; + + return; + } + + DWNotificationsData *data = self.model.data; + if (indexPath.section == 1 && data.unreadItems.count == 0) + { + return; // empty state + } + + id item = [self itemAtIndexPath:indexPath]; + DWUserProfileViewController *profileController = + [[DWUserProfileViewController alloc] initWithItem:item + payModel:self.payModel + dataProvider:self.dataProvider + shouldSkipUpdating:YES + shownAfterPayment:NO]; + [self.navigationController pushViewController:profileController animated:YES]; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + const NSInteger section = indexPath.section; + // hide Earlier section header if it's empty + + if (section == 0) { + return [[UICollectionReusableView alloc] init]; + } + + if (section == 2 && [collectionView numberOfItemsInSection:section] == 0) { + return [[UICollectionReusableView alloc] init]; + } + + DWTitleActionHeaderView *view = (DWTitleActionHeaderView *)[collectionView + dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:DWTitleActionHeaderView.dw_reuseIdentifier + forIndexPath:indexPath]; + view.titleLabel.text = [self titleForSection:section]; + view.actionButton.hidden = YES; + return view; +} + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == 1 && self.model.data.unreadItems.count > 0) { // unread items + id item = [self itemAtIndexPath:indexPath]; + [self.model markNotificationAsRead:item]; + } +} + +#pragma mark - UICollectionViewDelegateFlowLayout + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { + if (section == 0) { + return CGSizeZero; + } + + // hide Earlier section header if it's empty + if (section == 2 && [collectionView numberOfItemsInSection:section] == 0) { + return CGSizeZero; + } + + DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; + NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); + const CGFloat contentWidth = layout.contentWidth; + + self.measuringHeaderView.titleLabel.text = [self titleForSection:section]; + self.measuringHeaderView.frame = CGRectMake(0, 0, contentWidth, 300); + CGSize size = [self.measuringHeaderView systemLayoutSizeFittingSize:CGSizeMake(contentWidth, UILayoutFittingExpandedSize.height) + withHorizontalFittingPriority:UILayoutPriorityRequired + verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; + + return size; +} + +#pragma mark - DWSendInviteFlowControllerDelegate + +- (void)sendInviteFlowControllerDidFinish:(DWSendInviteFlowController *)controller { + [controller dismissViewControllerAnimated:YES completion:nil]; +} + + +#pragma mark - DWNotificationsModelDelegate + +- (void)notificationsModelDidUpdate:(DWNotificationsModel *)model { + [self.collectionView reloadData]; + [self updateTitle]; +} + +#pragma mark - DWDPNewIncomingRequestItemDelegate + +- (void)acceptIncomingRequest:(id)item { + [self.model acceptContactRequest:item]; +} + +- (void)declineIncomingRequest:(id)item { + [self.model declineContactRequest:item]; +} + +#pragma mark - DWNotificationsInvitationCellDelegate + +- (void)notificationsInvitationCellCloseAction:(DWNotificationsInvitationCell *)cell { + [self setInvitationMessageHidden:YES]; + + [self.collectionView reloadData]; +} + +#pragma mark - Private + +- (UICollectionView *)collectionView { + if (_collectionView == nil) { + DWListCollectionLayout *layout = [[DWListCollectionLayout alloc] init]; + + UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds + collectionViewLayout:layout]; + collectionView.translatesAutoresizingMaskIntoConstraints = NO; + collectionView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + collectionView.delegate = self; + collectionView.dataSource = self; + collectionView.alwaysBounceVertical = YES; + [collectionView dw_registerDPItemCells]; + [collectionView registerClass:DWNoNotificationsCell.class + forCellWithReuseIdentifier:DWNoNotificationsCell.dw_reuseIdentifier]; + [collectionView registerClass:DWTitleActionHeaderView.class + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:DWTitleActionHeaderView.dw_reuseIdentifier]; + [collectionView registerClass:DWNotificationsInvitationCell.class + forCellWithReuseIdentifier:DWNotificationsInvitationCell.dw_reuseIdentifier]; + + _collectionView = collectionView; + } + return _collectionView; +} + +- (DWNotificationsModel *)model { + if (!_model) { + _model = [[DWNotificationsModel alloc] init]; + _model.delegate = self; + _model.context = self; + } + return _model; +} + +- (DWTitleActionHeaderView *)measuringHeaderView { + if (_measuringHeaderView == nil) { + DWTitleActionHeaderView *view = [[DWTitleActionHeaderView alloc] initWithFrame:CGRectZero]; + view.translatesAutoresizingMaskIntoConstraints = NO; + view.actionButton.hidden = YES; + _measuringHeaderView = view; + } + return _measuringHeaderView; +} + +- (DWNotificationsInvitationCell *)measuringInvitationView { + if (_measuringInvitationView == nil) { + DWNotificationsInvitationCell *view = [[DWNotificationsInvitationCell alloc] initWithFrame:CGRectZero]; + view.translatesAutoresizingMaskIntoConstraints = NO; + _measuringInvitationView = view; + } + return _measuringInvitationView; +} + +- (void)updateTitle { + const NSUInteger unreadCount = self.model.data.unreadItems.count; + NSString *title = NSLocalizedString(@"Notifications", nil); + if (unreadCount > 0) { + self.title = [NSString stringWithFormat:@"%@ (%ld)", title, unreadCount]; + } + else { + self.title = title; + } +} + +- (id)itemAtIndexPath:(NSIndexPath *)indexPath { + DWNotificationsData *data = self.model.data; + NSArray> *items = indexPath.section == 1 ? data.unreadItems : data.oldItems; + return items[indexPath.row]; +} + +- (NSString *)titleForSection:(NSInteger)section { + if (section == 1) { + return NSLocalizedString(@"New", @"(List of) New (notifications)"); + } + else { + return NSLocalizedString(@"Earlier", @"(List of notifications happened) Earlier (some time ago)"); + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.h b/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.h new file mode 100644 index 000000000..5d8c3f3ef --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.h @@ -0,0 +1,47 @@ +// +// 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 + +#import "DWNotificationsData.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DWNotificationsModel; + +@protocol DWNotificationsModelDelegate + +- (void)notificationsModelDidUpdate:(DWNotificationsModel *)model; + +@end + +@interface DWNotificationsModel : NSObject + +@property (readonly, nonatomic, copy) DWNotificationsData *data; + +@property (nullable, nonatomic, weak) id delegate; +@property (nullable, nonatomic, weak) UIViewController *context; + +- (void)acceptContactRequest:(id)item; +- (void)declineContactRequest:(id)item; + +- (void)markNotificationAsRead:(id)item; +- (void)saveMostRecentViewedNotificationDate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.m b/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.m new file mode 100644 index 000000000..e45d8575f --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Notifications/Models/DWNotificationsModel.m @@ -0,0 +1,92 @@ +// +// 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 "DWNotificationsModel.h" + +#import "DWDashPayContactsActions.h" +#import "DWEnvironment.h" +#import "DWGlobalOptions.h" +#import "DWNotificationsProvider.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWNotificationsModel () + +@property (nullable, nonatomic, copy) DWNotificationsData *data; +@property (nullable, nonatomic, strong) NSDate *mostRecentViewedNotificationDate; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWNotificationsModel + +- (instancetype)init { + self = [super init]; + if (self) { + _data = [[DWNotificationsData alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(notificationsDidUpdate) + name:DWNotificationsProviderDidUpdateNotification + object:nil]; + [self notificationsDidUpdate]; // initial update (when notification was missed) + } + return self; +} + +- (void)dealloc { + [[DWNotificationsProvider sharedInstance] forceUpdate]; +} + +- (void)acceptContactRequest:(id)item { + [DWDashPayContactsActions acceptContactRequest:item context:self.context completion:nil]; +} + +- (void)declineContactRequest:(id)item { + [DWDashPayContactsActions declineContactRequest:item context:self.context completion:nil]; +} + +- (void)markNotificationAsRead:(id)item { + if (self.mostRecentViewedNotificationDate == nil || + [item.date compare:self.mostRecentViewedNotificationDate] == NSOrderedDescending) { + self.mostRecentViewedNotificationDate = item.date; + } +} + +- (void)saveMostRecentViewedNotificationDate { + if (self.mostRecentViewedNotificationDate == nil) { + return; + } + + DWGlobalOptions *options = [DWGlobalOptions sharedInstance]; + if (options.mostRecentViewedNotificationDate == nil || + [self.mostRecentViewedNotificationDate compare:options.mostRecentViewedNotificationDate] == NSOrderedDescending) { + options.mostRecentViewedNotificationDate = self.mostRecentViewedNotificationDate; + } +} + +#pragma mark - Private + +- (void)notificationsDidUpdate { + DWNotificationsProvider *provider = [DWNotificationsProvider sharedInstance]; + self.data = provider.data; + + [self.delegate notificationsModelDidUpdate:self]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.h b/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.h new file mode 100644 index 000000000..b044080cb --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.h @@ -0,0 +1,41 @@ +// +// 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 + +#import "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol DWPayModelProtocol; +@protocol DWTransactionListDataProviderProtocol; + +@interface DWModalUserProfileViewController : UIViewController + +- (instancetype)initWithItem:(id)item + payModel:(id)payModel + dataProvider:(id)dataProvider NS_DESIGNATED_INITIALIZER; + +- (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 + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.m b/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.m new file mode 100644 index 000000000..558aef636 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/DWModalUserProfileViewController.m @@ -0,0 +1,74 @@ +// +// 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 "DWModalUserProfileViewController.h" + +#import "DWUserProfileViewController.h" +#import "UIViewController+DWEmbedding.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWModalUserProfileViewController () + +@property (nonatomic, strong) DWUserProfileViewController *profileController; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWModalUserProfileViewController + +- (instancetype)initWithItem:(id)item + payModel:(id)payModel + dataProvider:(id)dataProvider { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _profileController = + [[DWUserProfileViewController alloc] initWithItem:item + payModel:payModel + dataProvider:dataProvider + shouldSkipUpdating:YES + shownAfterPayment:YES]; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"payments_nav_cross"] + style:UIBarButtonItemStyleDone + target:self + action:@selector(cancelButtonAction)]; + self.profileController.navigationItem.rightBarButtonItem = cancelButton; + + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self.profileController]; + [self dw_embedChild:navigationController]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self.profileController.view setNeedsLayout]; + [self.profileController.view layoutIfNeeded]; +} + +- (void)cancelButtonAction { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.h b/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.h new file mode 100644 index 000000000..e33a5300e --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.h @@ -0,0 +1,43 @@ +// +// 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 "DWBasePayViewController.h" + +#import "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserProfileViewController : DWBasePayViewController + +- (instancetype)initWithItem:(id)item + payModel:(id)payModel + dataProvider:(id)dataProvider; + +- (instancetype)initWithItem:(id)item + payModel:(id)payModel + dataProvider:(id)dataProvider + shouldSkipUpdating:(BOOL)shouldSkipUpdating + shownAfterPayment:(BOOL)shownAfterPayment NS_DESIGNATED_INITIALIZER; + +- (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 + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.m b/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.m new file mode 100644 index 000000000..c2aca9b18 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/DWUserProfileViewController.m @@ -0,0 +1,498 @@ +// +// 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 "DWUserProfileViewController.h" + +#import "DWDPBasicCell.h" +#import "DWDPTxItem.h" +#import "DWFilterHeaderView.h" +#import "DWInfoPopupViewController.h" +#import "DWNetworkErrorViewController.h" +#import "DWStretchyHeaderListCollectionLayout.h" +#import "DWTxDetailPopupViewController.h" +#import "DWUIKit.h" +#import "DWUserProfileContactActionsCell.h" +#import "DWUserProfileHeaderView.h" +#import "DWUserProfileModel.h" +#import "DWUserProfileNavigationTitleView.h" +#import "DWUserProfileSendRequestCell.h" +#import "UICollectionView+DWDPItemDequeue.h" +#import "UIViewController+DWTxFilter.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const FILTER_PADDING = 15.0; // same as horizontal padding for itemView inside DWDPBasicCell + +@interface DWUserProfileViewController () + +@property (readonly, nonatomic, strong) DWUserProfileModel *model; + +@property (readonly, nonatomic, strong) UIView *topOverscrollView; +@property (null_resettable, nonatomic, strong) UICollectionView *collectionView; +@property (nullable, nonatomic, weak) DWUserProfileHeaderView *headerView; + +@property (null_resettable, nonatomic, strong) DWFilterHeaderView *measuringFilterHeaderView; +@property (null_resettable, nonatomic, strong) DWUserProfileHeaderView *measuringProfileHeaderView; + +@property (null_resettable, nonatomic, strong) DWUserProfileSendRequestCell *measuringSendCell; +@property (null_resettable, nonatomic, strong) DWUserProfileContactActionsCell *measuringActionsCell; +@property (null_resettable, nonatomic, strong) DWDPBasicCell *measuringBasicCell; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileViewController + +- (instancetype)initWithItem:(id)item + payModel:(id)payModel + dataProvider:(id)dataProvider { + return [self initWithItem:item payModel:payModel dataProvider:dataProvider shouldSkipUpdating:NO shownAfterPayment:NO]; +} + +- (instancetype)initWithItem:(id)item + payModel:(id)payModel + dataProvider:(id)dataProvider + shouldSkipUpdating:(BOOL)shouldSkipUpdating + shownAfterPayment:(BOOL)shownAfterPayment { + self = [super initWithNibName:nil bundle:nil]; + if (self) { + _model = [[DWUserProfileModel alloc] initWithItem:item + txDataProvider:dataProvider]; + _model.context = self; + _model.delegate = self; + _model.shownAfterPayment = shownAfterPayment; + if (shouldSkipUpdating) { + [_model skipUpdating]; + } + else { + [_model update]; + } + + self.payModel = payModel; + self.dataProvider = dataProvider; + + self.hidesBottomBarWhenPushed = YES; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + DWUserProfileNavigationTitleView *titleView = [[DWUserProfileNavigationTitleView alloc] initWithFrame:CGRectZero]; + [titleView updateWithBlockchainIdentity:self.model.item.blockchainIdentity]; + CGSize titleSize = [titleView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; + titleView.frame = CGRectMake(0, 0, titleSize.width, titleSize.height); + self.navigationItem.titleView = titleView; + + [self.view addSubview:self.collectionView]; + [NSLayoutConstraint activateConstraints:@[ + [self.collectionView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], + [self.collectionView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.view.trailingAnchor constraintEqualToAnchor:self.collectionView.trailingAnchor], + [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:self.collectionView.bottomAnchor], + ]]; +} + +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + + CGSize size = self.view.bounds.size; + self.topOverscrollView.frame = CGRectMake(0.0, -size.height, size.width, size.height); +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self.collectionView setNeedsLayout]; + [self.collectionView layoutIfNeeded]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [self.collectionView flashScrollIndicators]; +} + +- (id)contactItem { + return self.model.item; +} + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return 2; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + if (section == 0) { + const BOOL shouldDisplayActions = [self.model shouldShowActions]; + return shouldDisplayActions ? 1 : 0; + } + else { + return self.model.dataSource.count; + } +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == 0) { + DWUserProfileHeaderView *headerView = (DWUserProfileHeaderView *)[collectionView + dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:DWUserProfileHeaderView.dw_reuseIdentifier + forIndexPath:indexPath]; + headerView.model = self.model; + headerView.delegate = self; + self.headerView = headerView; + return headerView; + } + else { + DWFilterHeaderView *headerView = (DWFilterHeaderView *)[collectionView + dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:DWFilterHeaderView.dw_reuseIdentifier + forIndexPath:indexPath]; + headerView.padding = FILTER_PADDING; + headerView.infoButton.hidden = (self.model.friendshipStatus == DSBlockchainIdentityFriendshipStatus_Friends); + headerView.titleLabel.text = NSLocalizedString(@"Activity", nil); + headerView.delegate = self; + [headerView.filterButton setTitle:[self titleForFilterButton] forState:UIControlStateNormal]; + return headerView; + } +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; + NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); + const CGFloat contentWidth = layout.contentWidth; + + if (indexPath.section == 0) { + if ([self.model shouldShowSendRequestAction]) { + DWUserProfileSendRequestCell *cell = [collectionView + dequeueReusableCellWithReuseIdentifier:DWUserProfileSendRequestCell.dw_reuseIdentifier + forIndexPath:indexPath]; + cell.contentWidth = contentWidth; + cell.model = self.model; + cell.delegate = self; + return cell; + } + DWUserProfileContactActionsCell *cell = [collectionView + dequeueReusableCellWithReuseIdentifier:DWUserProfileContactActionsCell.dw_reuseIdentifier + forIndexPath:indexPath]; + cell.contentWidth = contentWidth; + cell.model = self.model; + cell.delegate = self; + return cell; + } + else { + id item = [self itemAtIndexPath:indexPath]; + + DWDPBasicCell *cell = [collectionView dw_dequeueReusableCellForItem:item atIndexPath:indexPath]; + cell.contentWidth = contentWidth; + cell.itemView.avatarHidden = YES; + cell.backgroundStyle = DWDPBasicCellBackgroundStyle_GrayOnGray; + cell.item = item; + return cell; + } +} + +#pragma mark - UICollectionViewDelegateFlowLayout + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { + DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; + NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); + const CGFloat contentWidth = layout.contentWidth; + + if (section == 1 && self.model.dataSource.count == 0) { + return CGSizeZero; + } + + UIView *measuringView = section == 0 ? self.measuringProfileHeaderView : self.measuringFilterHeaderView; + measuringView.frame = CGRectMake(0, 0, contentWidth, 300); + CGSize size = [measuringView systemLayoutSizeFittingSize:CGSizeMake(contentWidth, UILayoutFittingCompressedSize.height) + withHorizontalFittingPriority:UILayoutPriorityRequired + verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; + + return size; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + DWListCollectionLayout *layout = (DWListCollectionLayout *)collectionView.collectionViewLayout; + NSAssert([layout isKindOfClass:DWListCollectionLayout.class], @"Invalid layout"); + const CGFloat contentWidth = layout.contentWidth; + + UICollectionViewCell *measuringCell = nil; + if (indexPath.section == 0) { + if ([self.model shouldShowSendRequestAction]) { + DWUserProfileSendRequestCell *cell = self.measuringSendCell; + cell.contentWidth = contentWidth; + cell.model = self.model; + measuringCell = cell; + } + DWUserProfileContactActionsCell *cell = self.measuringActionsCell; + cell.contentWidth = contentWidth; + cell.model = self.model; + measuringCell = cell; + } + else { + id item = [self itemAtIndexPath:indexPath]; + + DWDPBasicCell *cell = self.measuringBasicCell; + cell.contentWidth = contentWidth; + cell.itemView.avatarHidden = YES; + cell.backgroundStyle = DWDPBasicCellBackgroundStyle_GrayOnGray; + cell.item = item; + measuringCell = cell; + } + + measuringCell.frame = CGRectMake(0, 0, contentWidth, 300); + CGSize size = [measuringCell systemLayoutSizeFittingSize:CGSizeMake(contentWidth, UILayoutFittingCompressedSize.height) + withHorizontalFittingPriority:UILayoutPriorityRequired + verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; + + return size; +} + +#pragma mark - UICollectionViewDelegate + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + [collectionView deselectItemAtIndexPath:indexPath animated:YES]; + + if (indexPath.section != 1) { + return; + } + + id item = [self itemAtIndexPath:indexPath]; + if (![item conformsToProtocol:@protocol(DWDPTxItem)]) { + return; + } + + DSTransaction *transaction = ((id)item).transaction; + id dataProvider = self.dataProvider; + DWTxDetailPopupViewController *controller = + [[DWTxDetailPopupViewController alloc] initWithTransaction:transaction + dataProvider:dataProvider]; + [self presentViewController:controller animated:YES completion:nil]; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + const CGFloat contentOffsetY = scrollView.contentOffset.y; + const CGFloat headerHeight = CGRectGetHeight(self.headerView.bounds); + const float percent = headerHeight > 0.0 ? contentOffsetY / headerHeight : 0.0; + + [self.headerView setScrollingPercent:percent]; + + DWUserProfileNavigationTitleView *titleView = (DWUserProfileNavigationTitleView *)self.navigationItem.titleView; + NSAssert([titleView isKindOfClass:DWUserProfileNavigationTitleView.class], @"Invalid titleView"); + [titleView setScrollingPercent:percent]; +} + +#pragma mark - DWUserProfileModelDelegate + +- (void)userProfileModelDidUpdate:(DWUserProfileModel *)model { + [self.collectionView reloadData]; + + if (model.state == DWUserProfileModelState_Error) { + DWNetworkErrorViewController *controller = [[DWNetworkErrorViewController alloc] initWithType:DWErrorDescriptionType_Profile]; + [self presentViewController:controller animated:YES completion:nil]; + } +} + +#pragma mark - DWUserProfileHeaderViewDelegate + +- (void)userProfileHeaderView:(DWUserProfileHeaderView *)view actionButtonAction:(UIButton *)sender { + if ([self.model shouldShowSendRequestAction]) { + [self sendContactRequest]; + return; + } + + const BOOL canPay = self.model.friendshipStatus == DSBlockchainIdentityFriendshipStatus_Incoming || + self.model.friendshipStatus == DSBlockchainIdentityFriendshipStatus_Friends; + NSParameterAssert(canPay); + if (canPay) { + [self performPayToUser:self.model.item]; + } +} + +#pragma mark - DWUserProfileSendRequestCellDelegate + +- (void)userProfileSendRequestCell:(DWUserProfileSendRequestCell *)cell sendRequestButtonAction:(UIButton *)sender { + [self sendContactRequest]; +} + +#pragma mark - DWUserProfileContactActionsCellDelegate + +- (void)userProfileContactActionsCell:(DWUserProfileContactActionsCell *)cell mainButtonAction:(UIButton *)sender { + const BOOL canAcceptRequest = self.model.friendshipStatus == DSBlockchainIdentityFriendshipStatus_Incoming; + NSParameterAssert(canAcceptRequest); + if (canAcceptRequest) { + [self.model acceptContactRequest]; + } +} + +- (void)userProfileContactActionsCell:(DWUserProfileContactActionsCell *)cell secondaryButtonAction:(UIButton *)sender { + // TODO: DP decline request +} + +#pragma mark - DWFilterHeaderViewDelegate + +- (void)filterHeaderView:(DWFilterHeaderView *)view filterButtonAction:(UIView *)sender { + [self showTxFilterWithSender:sender displayModeProvider:self.model shouldShowRewards:NO]; +} + +- (void)filterHeaderView:(DWFilterHeaderView *)view infoButtonAction:(UIView *)sender { + CGPoint offset = [self.view.window convertRect:sender.frame fromView:sender.superview].origin; + DWInfoPopupViewController *controller = + [[DWInfoPopupViewController alloc] initWithText:NSLocalizedString(@"Payments made directly to addresses won’t be retained in activity.", nil) + offset:offset]; + controller.modalPresentationStyle = UIModalPresentationOverCurrentContext; + controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + [self presentViewController:controller animated:YES completion:nil]; +} + +#pragma mark - DWTxDetailFullscreenViewControllerDelegate + +//TODO: DashPay +//- (void)detailFullscreenViewControllerDidFinish:(DWTxDetailsViewController *)controller { +// if (self.model.shouldAcceptIncomingAfterPayment) { +// [self.model acceptContactRequest]; +// } +// +// [super detailFullscreenViewControllerDidFinish:controller]; +//} + +#pragma mark - Private + +- (void)sendContactRequest { + const BOOL canSendRequest = self.model.friendshipStatus == DSBlockchainIdentityFriendshipStatus_None; + if (canSendRequest) { + [self.model sendContactRequest:^(BOOL success) { + if (!success) { + DWNetworkErrorViewController *controller = [[DWNetworkErrorViewController alloc] initWithType:DWErrorDescriptionType_SendContactRequest]; + [self presentViewController:controller animated:YES completion:nil]; + } + }]; + } +} + +- (id)itemAtIndexPath:(NSIndexPath *)indexPath { + NSAssert(indexPath.section > 0, @"Section 0 is empty and should not have any data items"); + NSIndexPath *dataIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:0]; + id item = [self.model.dataSource itemAtIndexPath:dataIndexPath]; + return item; +} + +- (UICollectionView *)collectionView { + if (_collectionView == nil) { + UIView *topOverscrollView = [[UIView alloc] initWithFrame:CGRectZero]; + topOverscrollView.backgroundColor = [UIColor dw_backgroundColor]; + _topOverscrollView = topOverscrollView; + + DWStretchyHeaderListCollectionLayout *layout = [[DWStretchyHeaderListCollectionLayout alloc] init]; + + UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds + collectionViewLayout:layout]; + collectionView.translatesAutoresizingMaskIntoConstraints = NO; + collectionView.dataSource = self; + collectionView.delegate = self; + collectionView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + collectionView.alwaysBounceVertical = YES; + + [collectionView dw_registerDPItemCells]; + + [collectionView registerClass:DWUserProfileContactActionsCell.class + forCellWithReuseIdentifier:DWUserProfileContactActionsCell.dw_reuseIdentifier]; + [collectionView registerClass:DWUserProfileSendRequestCell.class + forCellWithReuseIdentifier:DWUserProfileSendRequestCell.dw_reuseIdentifier]; + + [collectionView registerClass:DWUserProfileHeaderView.class + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:DWUserProfileHeaderView.dw_reuseIdentifier]; + [collectionView registerClass:DWFilterHeaderView.class + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:DWFilterHeaderView.dw_reuseIdentifier]; + + [collectionView addSubview:topOverscrollView]; + _collectionView = collectionView; + } + return _collectionView; +} + +- (DWUserProfileHeaderView *)measuringProfileHeaderView { + if (_measuringProfileHeaderView == nil) { + _measuringProfileHeaderView = [[DWUserProfileHeaderView alloc] initWithFrame:CGRectZero]; + } + _measuringProfileHeaderView.model = self.model; + return _measuringProfileHeaderView; +} + +- (DWFilterHeaderView *)measuringFilterHeaderView { + if (_measuringFilterHeaderView == nil) { + _measuringFilterHeaderView = [[DWFilterHeaderView alloc] initWithFrame:CGRectZero]; + _measuringFilterHeaderView.padding = FILTER_PADDING; + _measuringFilterHeaderView.titleLabel.text = NSLocalizedString(@"Activity", nil); + } + [_measuringFilterHeaderView.filterButton setTitle:[self titleForFilterButton] forState:UIControlStateNormal]; + return _measuringFilterHeaderView; +} + +- (DWUserProfileSendRequestCell *)measuringSendCell { + if (_measuringSendCell == nil) { + _measuringSendCell = [[DWUserProfileSendRequestCell alloc] initWithFrame:CGRectZero]; + } + return _measuringSendCell; +} + +- (DWUserProfileContactActionsCell *)measuringActionsCell { + if (_measuringActionsCell == nil) { + _measuringActionsCell = [[DWUserProfileContactActionsCell alloc] initWithFrame:CGRectZero]; + } + return _measuringActionsCell; +} + +- (DWDPBasicCell *)measuringBasicCell { + if (_measuringBasicCell == nil) { + _measuringBasicCell = [[DWDPBasicCell alloc] initWithFrame:CGRectZero]; + } + return _measuringBasicCell; +} + +- (NSString *)titleForFilterButton { + switch (self.model.displayMode) { + case DWHomeTxDisplayMode_All: + return NSLocalizedString(@"All", nil); + case DWHomeTxDisplayMode_Received: + return NSLocalizedString(@"Received", nil); + case DWHomeTxDisplayMode_Sent: + return NSLocalizedString(@"Sent", nil); + case DWHomeTxDisplayMode_Rewards: + NSAssert(NO, @"Not implemented here"); + return nil; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.h b/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.h new file mode 100644 index 000000000..235e55638 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.h @@ -0,0 +1,81 @@ +// +// 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 + +#import +#import + +#import "DWDPBasicUserItem.h" +#import "DWDPBlockchainIdentityBackedItem.h" +#import "DWTxDisplayModeProtocol.h" +#import "DWUserProfileDataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, DWUserProfileModelState) { + DWUserProfileModelState_None, + DWUserProfileModelState_Loading, + DWUserProfileModelState_Error, + DWUserProfileModelState_Done, +}; + +@class DWUserProfileModel; +@protocol DWTransactionListDataProviderProtocol; + +@protocol DWUserProfileModelDelegate + +- (void)userProfileModelDidUpdate:(DWUserProfileModel *)model; + +@end + +@interface DWUserProfileModel : NSObject + +@property (readonly, nonatomic, strong) id item; +@property (readonly, nonatomic, assign) DWUserProfileModelState state; +@property (readonly, nonatomic, copy) NSString *username; +@property (readonly, nonatomic, assign) DSBlockchainIdentityFriendshipStatus friendshipStatus; +@property (readonly, nonatomic, strong) id dataSource; +@property (readonly, nonatomic, assign) DWUserProfileModelState sendRequestState; +@property (readonly, nonatomic, assign) DWUserProfileModelState acceptRequestState; +@property (readonly, nonatomic, assign) BOOL shouldAcceptIncomingAfterPayment; + +@property (nullable, nonatomic, weak) id delegate; + +@property (nonatomic, assign) BOOL shownAfterPayment; + +@property (nullable, nonatomic, weak) UIViewController *context; + +- (BOOL)shouldShowActions; +- (BOOL)shouldShowSendRequestAction; +- (BOOL)shouldShowAcceptDeclineRequestAction; + +- (void)skipUpdating; +- (void)update; + +- (void)sendContactRequest:(void (^)(BOOL success))completion; +- (void)acceptContactRequest; + +- (instancetype)initWithItem:(id)item + txDataProvider:(id)txDataProvider; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.m b/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.m new file mode 100644 index 000000000..1b05592c9 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Model/DWUserProfileModel.m @@ -0,0 +1,292 @@ +// +// 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 "DWUserProfileModel.h" + +#import "DWDashPayContactsActions.h" +#import "DWDashPayContactsUpdater.h" +#import "DWEnvironment.h" +#import "DWGlobalOptions.h" +#import "DWProfileTxsFetchedDataSource.h" +#import "DWUserProfileDataSourceObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserProfileModel () + +@property (nonatomic, assign) DWUserProfileModelState state; +@property (nullable, nonatomic, strong) DWProfileTxsFetchedDataSource *txsFetchedDataSource; +@property (nonatomic, strong) id dataSource; +@property (readonly, nonatomic, strong) id txDataProvider; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileModel + +@synthesize displayMode = _displayMode; + +- (instancetype)initWithItem:(id)item + txDataProvider:(id)txDataProvider { + self = [super init]; + if (self) { + _item = item; + _txDataProvider = txDataProvider; + _dataSource = [[DWUserProfileDataSourceObject alloc] init]; // empty data source + + // TODO: DP global notification is used temporary. Remove its usage once FRC delegate issue is resolved + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(transactionManagerTransactionStatusDidChangeNotification) + name:DSTransactionManagerTransactionStatusDidChangeNotification + object:nil]; + } + return self; +} + +- (BOOL)shouldAcceptIncomingAfterPayment { + //TODO: DashPay + //return ([DWGlobalOptions sharedInstance].confirmationAcceptContactRequestIsOn && [self friendshipStatusInternal] == DSBlockchainIdentityFriendshipStatus_Incoming); + return YES; +} + +- (void)setDisplayMode:(DWHomeTxDisplayMode)displayMode { + _displayMode = displayMode; + + [self updateDataSource]; +} + +- (void)skipUpdating { + [self updateDataSource]; + + + if (self.shownAfterPayment && self.shouldAcceptIncomingAfterPayment) { + [self acceptContactRequest]; + } + else { + self.state = DWUserProfileModelState_Done; + } +} + +- (void)setState:(DWUserProfileModelState)state { + _state = state; + + [self.delegate userProfileModelDidUpdate:self]; +} + +- (void)setSendRequestState:(DWUserProfileModelState)sendRequestState { + _sendRequestState = sendRequestState; + + [self.delegate userProfileModelDidUpdate:self]; +} + +- (void)setAcceptRequestState:(DWUserProfileModelState)acceptRequestState { + _acceptRequestState = acceptRequestState; + + [self.delegate userProfileModelDidUpdate:self]; +} + +- (NSString *)username { + return self.item.username; +} + +- (void)update { + self.state = DWUserProfileModelState_Loading; + + __weak typeof(self) weakSelf = self; + [[DWDashPayContactsUpdater sharedInstance] fetchWithCompletion:^(BOOL success, NSArray *_Nonnull errors) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf updateDataSource]; + strongSelf.state = success ? DWUserProfileModelState_Done : DWUserProfileModelState_Error; + }]; +} + +- (DSBlockchainIdentityFriendshipStatus)friendshipStatus { + if (self.state == DWUserProfileModelState_None || self.state == DWUserProfileModelState_Loading) { + return DSBlockchainIdentityFriendshipStatus_Unknown; + } + + return [self friendshipStatusInternal]; +} + +- (BOOL)shouldShowActions { + if (self.state != DWUserProfileModelState_Done) { + return NO; + } + + const DSBlockchainIdentityFriendshipStatus status = self.friendshipStatus; + return (status == DSBlockchainIdentityFriendshipStatus_Incoming || + status == DSBlockchainIdentityFriendshipStatus_None || + status == DSBlockchainIdentityFriendshipStatus_Outgoing); +} + +- (BOOL)shouldShowSendRequestAction { + NSParameterAssert(self.state == DWUserProfileModelState_Done); + + const DSBlockchainIdentityFriendshipStatus status = self.friendshipStatus; + return (status == DSBlockchainIdentityFriendshipStatus_None || + status == DSBlockchainIdentityFriendshipStatus_Outgoing); +} + +- (BOOL)shouldShowAcceptDeclineRequestAction { + NSParameterAssert(self.state == DWUserProfileModelState_Done); + + const DSBlockchainIdentityFriendshipStatus status = self.friendshipStatus; + return status == DSBlockchainIdentityFriendshipStatus_Incoming; +} + +- (void)sendContactRequest:(void (^)(BOOL success))completion { + self.sendRequestState = DWUserProfileModelState_Loading; + + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + __weak typeof(self) weakSelf = self; + [myBlockchainIdentity sendNewFriendRequestToBlockchainIdentity:self.item.blockchainIdentity + completion:^(BOOL success, NSArray *_Nullable errors) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf updateDataSource]; + strongSelf.sendRequestState = success ? DWUserProfileModelState_Done : DWUserProfileModelState_Error; + + if (completion) { + completion(success); + } + }]; +} + +- (void)acceptContactRequest { + self.acceptRequestState = DWUserProfileModelState_Loading; + + __weak typeof(self) weakSelf = self; + [DWDashPayContactsActions acceptContactRequest:self.item + context:self.context + completion:^(BOOL success, NSArray *_Nonnull errors) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf updateDataSource]; + strongSelf.acceptRequestState = success ? DWUserProfileModelState_Done : DWUserProfileModelState_Error; + }]; +} + +#pragma mark - DWFetchedResultsDataSourceDelegate + +- (void)fetchedResultsDataSourceDidUpdate:(DWFetchedResultsDataSource *)fetchedResultsDataSource { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + // TODO: DP fix me, not firing + // global txs notifications was used instead to workaround this issue + + [self updateDataSource]; +} + +#pragma mark - NSFetchedResultsControllerDelegate + +- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { + NSAssert([NSThread isMainThread], @"Main thread is assumed here"); + + // TODO: DP fix me, not firing + // global txs notifications was used instead to workaround this issue + + [self updateDataSource]; +} + +#pragma mark - Notifications + +- (void)transactionManagerTransactionStatusDidChangeNotification { + [self updateDataSource]; +} + +#pragma mark - Private + +- (DSBlockchainIdentityFriendshipStatus)friendshipStatusInternal { + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + DSBlockchainIdentity *blockchainIdentity = self.item.blockchainIdentity; + return [myBlockchainIdentity friendshipStatusForRelationshipWithBlockchainIdentity:blockchainIdentity]; +} + +- (void)updateDataSource { + NSManagedObjectContext *context = [NSManagedObjectContext viewContext]; + + DSWallet *wallet = [DWEnvironment sharedInstance].currentWallet; + DSBlockchainIdentity *myBlockchainIdentity = wallet.defaultBlockchainIdentity; + if (myBlockchainIdentity == nil) { + return; + } + + DSBlockchainIdentity *friendBlockchainIdentity = self.item.blockchainIdentity; + NSAssert(myBlockchainIdentity.matchingDashpayUserInViewContext, @"Invalid DSBlockchainIdentity: myBlockchainIdentity"); + DSDashpayUserEntity *me = [myBlockchainIdentity matchingDashpayUserInContext:context]; + DSDashpayUserEntity *friend = nil; + if (friendBlockchainIdentity.matchingDashpayUserInViewContext) { + friend = [friendBlockchainIdentity matchingDashpayUserInContext:context]; + } + + DSFriendRequestEntity *meToFriend = nil; + if (friend != nil) { + meToFriend = [[me.outgoingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"destinationContact == %@", friend]] anyObject]; + } + + DSFriendRequestEntity *friendToMe = nil; + if (friend != nil) { + friendToMe = [[me.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", friend]] anyObject]; + } + + BOOL shouldShowContactRequests = YES; + if (self.displayMode == DWHomeTxDisplayMode_Sent) { + meToFriend = nil; + shouldShowContactRequests = NO; + } + else if (self.displayMode == DWHomeTxDisplayMode_Received) { + friendToMe = nil; + shouldShowContactRequests = NO; + } + + if (meToFriend || friendToMe) { + self.txsFetchedDataSource = [[DWProfileTxsFetchedDataSource alloc] initWithMeToFriendRequest:meToFriend + friendToMeRequest:friendToMe + inContext:context]; + self.txsFetchedDataSource.delegate = self; + [self.txsFetchedDataSource start]; + self.txsFetchedDataSource.fetchedResultsController.delegate = self; + } + else { + self.txsFetchedDataSource = nil; + } + + self.dataSource = [[DWUserProfileDataSourceObject alloc] initWithTxFRC:self.txsFetchedDataSource.fetchedResultsController + txDataProvider:self.txDataProvider + friendToMeRequest:shouldShowContactRequests ? friendToMe : nil + meToFriendRequest:shouldShowContactRequests ? meToFriend : nil + friendBlockchainIdentity:friendBlockchainIdentity + myBlockchainIdentity:myBlockchainIdentity]; + + [self.delegate userProfileModelDidUpdate:self]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.h b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.h new file mode 100644 index 000000000..ea8967158 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.h @@ -0,0 +1,34 @@ +// +// 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 "DWFetchedResultsDataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSFriendRequestEntity; + +@interface DWProfileTxsFetchedDataSource : DWFetchedResultsDataSource + +- (instancetype)initWithMeToFriendRequest:(nullable DSFriendRequestEntity *)meToFriend + friendToMeRequest:(nullable DSFriendRequestEntity *)friendToMe + inContext:(NSManagedObjectContext *)context NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithContext:(NSManagedObjectContext *)context NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.m b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.m new file mode 100644 index 000000000..6a6ef3fce --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWProfileTxsFetchedDataSource.m @@ -0,0 +1,85 @@ +// +// 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 "DWProfileTxsFetchedDataSource.h" + +#import "DWEnvironment.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWProfileTxsFetchedDataSource () + +@property (nullable, readonly, strong, nonatomic) DSFriendRequestEntity *meToFriend; +@property (nullable, readonly, strong, nonatomic) DSFriendRequestEntity *friendToMe; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWProfileTxsFetchedDataSource + +- (instancetype)initWithMeToFriendRequest:(DSFriendRequestEntity *)meToFriend + friendToMeRequest:(DSFriendRequestEntity *)friendToMe + inContext:(NSManagedObjectContext *)context { + NSAssert(meToFriend || friendToMe, @"Either of requests should exist"); // otherwise FRC would return all DSTxOutputEntity due to empty predicate + self = [super initWithContext:context]; + if (self) { + _meToFriend = meToFriend; + _friendToMe = friendToMe; + } + return self; +} + +- (NSString *)entityName { + return NSStringFromClass(DSTxOutputEntity.class); +} + +- (NSPredicate *)predicate { + NSPredicate *meToFriendPredicate = nil; + NSPredicate *friendToMePredicate = nil; + if (self.meToFriend != nil) { + meToFriendPredicate = [NSPredicate predicateWithFormat:@"localAddress.derivationPath.friendRequest == %@", self.meToFriend]; + } + if (self.friendToMe != nil) { + friendToMePredicate = [NSPredicate predicateWithFormat:@"localAddress.derivationPath.friendRequest == %@", self.friendToMe]; + } + + NSPredicate *predicate = nil; + if (meToFriendPredicate != nil && friendToMePredicate != nil) { + predicate = [NSCompoundPredicate orPredicateWithSubpredicates:@[ meToFriendPredicate, friendToMePredicate ]]; + } + else if (meToFriendPredicate != nil) { + predicate = meToFriendPredicate; + } + else if (friendToMePredicate != nil) { + predicate = friendToMePredicate; + } + NSParameterAssert(predicate); + return predicate; +} + +- (NSArray *)sortDescriptors { + NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] + initWithKey:@"transaction.transactionHash.blockHeight" + ascending:NO]; + NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] + initWithKey:@"transaction.transactionHash.timestamp" + ascending:NO]; + return @[ sortDescriptor1, sortDescriptor2 ]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSource.h b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSource.h new file mode 100644 index 000000000..7d90ffe26 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSource.h @@ -0,0 +1,32 @@ +// +// 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 "DWDPBasicItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol DWUserProfileDataSource + +@property (readonly, nonatomic, assign, getter=isEmpty) BOOL empty; + +@property (readonly, nonatomic, assign) NSUInteger count; + +- (id)itemAtIndexPath:(NSIndexPath *)indexPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.h b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.h new file mode 100644 index 000000000..49b692af6 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.h @@ -0,0 +1,41 @@ +// +// 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 "DWTransactionListDataProviderProtocol.h" +#import "DWUserProfileDataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@class NSFetchedResultsController; +@class DSFriendRequestEntity; +@class DSBlockchainIdentity; + +@interface DWUserProfileDataSourceObject : NSObject + +- (instancetype)initWithTxFRC:(NSFetchedResultsController *)frc + txDataProvider:(id)txDataProvider + friendToMeRequest:(nullable DSFriendRequestEntity *)friendToMe + meToFriendRequest:(nullable DSFriendRequestEntity *)meToFriend + friendBlockchainIdentity:(DSBlockchainIdentity *)friendBlockchainIdentity + myBlockchainIdentity:(DSBlockchainIdentity *)myBlockchainIdentity; + +/// Initialize as empty +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.m b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.m new file mode 100644 index 000000000..b54d4ff9b --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Model/DataSource/DWUserProfileDataSourceObject.m @@ -0,0 +1,216 @@ +// +// 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 "DWUserProfileDataSourceObject.h" + +#import "DWDPAcceptedRequestNotificationObject.h" +#import "DWDPOutgoingRequestNotificationObject.h" +#import "DWDPTxObject.h" +#import "DWEnvironment.h" +#import "dashwallet-Swift.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserProfileDataSourceObject () + +@property (readonly, nonatomic, assign) BOOL hasDataToShow; +@property (nullable, readonly, nonatomic, strong) NSFetchedResultsController *frc; +@property (readonly, nonatomic, strong) id txDataProvider; +@property (readonly, nonatomic, strong) DSBlockchainIdentity *friendBlockchainIdentity; + +@property (readonly, nonatomic, strong) NSMutableArray> *items; +@property (nullable, readonly, nonatomic, strong) DWDPAcceptedRequestNotificationObject *incomingNotification; +@property (nullable, readonly, nonatomic, strong) DWDPOutgoingRequestNotificationObject *outgoingNotification; +@property (nonatomic, assign) BOOL incomingNotificationAdded; +@property (nonatomic, assign) BOOL outgoingNotificationAdded; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileDataSourceObject + +- (instancetype)initWithTxFRC:(NSFetchedResultsController *)frc + txDataProvider:(id)txDataProvider + friendToMeRequest:(nullable DSFriendRequestEntity *)friendToMe + meToFriendRequest:(nullable DSFriendRequestEntity *)meToFriend + friendBlockchainIdentity:(DSBlockchainIdentity *)friendBlockchainIdentity + myBlockchainIdentity:(DSBlockchainIdentity *)myBlockchainIdentity { + self = [super init]; + if (self) { + _frc = frc; + _txDataProvider = txDataProvider; + _friendBlockchainIdentity = friendBlockchainIdentity; + _items = [NSMutableArray array]; + _hasDataToShow = (frc != nil) || (friendToMe != nil) || (meToFriend != nil); + + BOOL isFriendInitiated; + if (friendToMe && meToFriend) { + isFriendInitiated = friendToMe.timestamp < meToFriend.timestamp; + } + else if (friendToMe) { + isFriendInitiated = YES; + } + else { + isFriendInitiated = NO; + } + + if (friendToMe) { + _incomingNotification = + [[DWDPAcceptedRequestNotificationObject alloc] initWithFriendRequestEntity:friendToMe + blockchainIdentity:friendBlockchainIdentity + isInitiatedByThem:isFriendInitiated]; + } + else { + // mark it as added to the list to skip + _incomingNotificationAdded = YES; + } + + if (meToFriend) { + _outgoingNotification = + [[DWDPOutgoingRequestNotificationObject alloc] initWithFriendRequestEntity:meToFriend + blockchainIdentity:friendBlockchainIdentity + isInitiatedByMe:!isFriendInitiated]; + } + else { + // mark it as added to the list to skip + _outgoingNotificationAdded = YES; + } + } + return self; +} + +- (instancetype)init { + self = [super init]; + if (self) { + // empty, _hasDataToShow == NO + } + return self; +} + +- (BOOL)isEmpty { + return self.count == 0; +} + +- (NSUInteger)count { + if (self.hasDataToShow == NO) { + return 0; + } + + NSUInteger count = self.frc.sections.firstObject.numberOfObjects; + if (self.incomingNotification) { + count += 1; + } + if (self.outgoingNotification) { + count += 1; + } + return count; +} + +- (id)itemAtIndexPath:(NSIndexPath *)indexPath { + if (self.hasDataToShow == NO) { + NSAssert(NO, @"Invalid data source usage. Check `count` or `isEmpty` first."); + return nil; + } + + if (self.items.count == indexPath.item) { + NSInteger item = indexPath.item; + if (self.incomingNotification && self.incomingNotificationAdded) { + item -= 1; + } + if (self.outgoingNotification && self.outgoingNotificationAdded) { + item -= 1; + } + NSAssert(item >= 0, @"Inconsistent data source state: item index is out of bounds"); + + const NSUInteger count = self.frc.sections.firstObject.numberOfObjects; + if (item < count) { + NSIndexPath *txIndexPath = [NSIndexPath indexPathForItem:item inSection:0]; + DSTxOutputEntity *txOutputEntity = [self.frc objectAtIndexPath:txIndexPath]; + DSTransaction *transaction = [txOutputEntity.transaction transaction]; + NSDate *txDate = [transaction date]; + + DWDPTxObject *txObject = [[DWDPTxObject alloc] initWithTransaction:transaction + dataProvider:self.txDataProvider + blockchainIdentity:self.friendBlockchainIdentity]; + + if (self.incomingNotificationAdded == NO && [self isNotificationNewerThan:self.incomingNotification txDate:txDate]) { + [self.items addObject:self.incomingNotification]; + self.incomingNotificationAdded = YES; + + // optimization: since we already have constructed DSTransaction add it to the list + [self.items addObject:txObject]; + } + if (self.outgoingNotificationAdded == NO && [self isNotificationNewerThan:self.outgoingNotification txDate:txDate]) { + [self.items addObject:self.outgoingNotification]; + self.outgoingNotificationAdded = YES; + + // optimization: since we already have constructed DSTransaction add it to the list + [self.items addObject:txObject]; + } + else { + [self.items addObject:txObject]; + } + } + else { + [self appendAnyNotificationToTheItems]; + } + } + + return self.items[indexPath.item]; +} + +- (void)appendAnyNotificationToTheItems { + if (self.incomingNotification && self.outgoingNotification) { + // incoming.date > outgoing.date + const BOOL isIncomingNewer = ([self.incomingNotification.date compare:self.outgoingNotification.date] == NSOrderedDescending); + // list is sorted by the most recent date, add one which is newer + if (self.incomingNotificationAdded == NO && (isIncomingNewer || self.outgoingNotificationAdded == YES)) { + [self.items addObject:self.incomingNotification]; + self.incomingNotificationAdded = YES; + } + else { + NSAssert(self.outgoingNotificationAdded == NO, @"Outgoing notification has already been handled"); + [self.items addObject:self.outgoingNotification]; + self.outgoingNotificationAdded = YES; + } + } + else if (self.incomingNotificationAdded == NO) { + [self.items addObject:self.incomingNotification]; + self.incomingNotificationAdded = YES; + } + else if (self.outgoingNotificationAdded == NO) { + [self.items addObject:self.outgoingNotification]; + self.outgoingNotificationAdded = YES; + } + else { + NSAssert(NO, @"Inconsistent data source state. We should be able to add any of notifications."); + } +} + +- (BOOL)isNotificationNewerThan:(id)notification txDate:(NSDate *)txDate { + NSParameterAssert(notification); + NSParameterAssert(txDate); + + if (notification == nil) { + return NO; + } + + return ([notification.date compare:txDate] == NSOrderedDescending); +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.h b/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.h new file mode 100644 index 000000000..263af2079 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.h @@ -0,0 +1,26 @@ +// +// 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 "DWListCollectionLayout.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWStretchyHeaderListCollectionLayout : DWListCollectionLayout + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.m new file mode 100644 index 000000000..78ae2166e --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWStretchyHeaderListCollectionLayout.m @@ -0,0 +1,70 @@ +// +// 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 "DWStretchyHeaderListCollectionLayout.h" + +@implementation DWStretchyHeaderListCollectionLayout + +// disabled due to performance issues and more smart stretching + +//- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { +// NSArray *layoutAttributes = [[super layoutAttributesForElementsInRect:rect] copy]; +// +// for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) { +// if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader] && +// attributes.indexPath.section == 0) { +// UICollectionView *collectionView = self.collectionView; +// const CGFloat contentOffsetY = collectionView.contentOffset.y; +// if (collectionView != nil && contentOffsetY < 0) { +// const CGFloat width = CGRectGetWidth(collectionView.bounds); +// const CGFloat height = CGRectGetHeight(attributes.frame) - contentOffsetY; +// attributes.frame = CGRectMake(0, contentOffsetY, width, height); +// } +// } +// } +// +// return layoutAttributes; +//} +// +//- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { +// UICollectionViewLayoutAttributes *superAttributes = [super layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath]; +// if ([superAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader] && +// superAttributes.indexPath.section == 0) { +// UICollectionViewLayoutAttributes *attributes = [superAttributes copy]; +// UICollectionView *collectionView = self.collectionView; +// const CGFloat contentOffsetY = collectionView.contentOffset.y; +// if (collectionView != nil && contentOffsetY < 0) { +// const CGFloat width = CGRectGetWidth(collectionView.bounds); +// const CGFloat height = CGRectGetHeight(attributes.frame) - contentOffsetY; +// attributes.frame = CGRectMake(0, contentOffsetY, width, height); +// } +// return attributes; +// } +// else { +// return superAttributes; +// } +//} +// +//- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { +// const CGRect oldBounds = self.collectionView.bounds; +// if (newBounds.origin.y <= 0 || CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) { +// return YES; +// } +// return NO; +//} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.h b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.h new file mode 100644 index 000000000..39e0f0dd7 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.h @@ -0,0 +1,41 @@ +// +// 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 + +@class DWUserProfileModel; +@class DWUserProfileContactActionsCell; + +@protocol DWUserProfileContactActionsCellDelegate + +- (void)userProfileContactActionsCell:(DWUserProfileContactActionsCell *)cell mainButtonAction:(UIButton *)sender; +- (void)userProfileContactActionsCell:(DWUserProfileContactActionsCell *)cell secondaryButtonAction:(UIButton *)sender; + +@end + +@interface DWUserProfileContactActionsCell : UICollectionViewCell + +@property (nonatomic, assign) CGFloat contentWidth; + +@property (nullable, nonatomic, strong) DWUserProfileModel *model; +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.m new file mode 100644 index 000000000..805b60d80 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileContactActionsCell.m @@ -0,0 +1,226 @@ +// +// 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 "DWUserProfileContactActionsCell.h" + +#import "DWActionButton.h" +#import "DWUIKit.h" +#import "DWUserProfileModel.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const BUTTON_HEIGHT = 38.0; + +@interface DWUserProfileContactActionsCell () + +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) UIButton *mainButton; +@property (readonly, nonatomic, strong) UIButton *secondaryButton; +@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; + +@property (nullable, nonatomic, strong) NSLayoutConstraint *contentWidthConstraint; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileContactActionsCell + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.backgroundColor = self.backgroundColor; + [self.contentView addSubview:contentView]; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.backgroundColor = self.backgroundColor; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleBody]; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.numberOfLines = 0; + [contentView addSubview:titleLabel]; + _titleLabel = titleLabel; + + DWActionButton *mainButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + mainButton.translatesAutoresizingMaskIntoConstraints = NO; + mainButton.accentColor = [UIColor dw_greenColor]; + mainButton.small = YES; + [mainButton addTarget:self action:@selector(mainButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + _mainButton = mainButton; + + DWActionButton *secondaryButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + secondaryButton.translatesAutoresizingMaskIntoConstraints = NO; + secondaryButton.accentColor = [UIColor dw_quaternaryTextColor]; + secondaryButton.small = YES; + [mainButton addTarget:self action:@selector(secondaryButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + _secondaryButton = secondaryButton; + + UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; + activityIndicatorView.color = [UIColor dw_dashBlueColor]; + activityIndicatorView.hidesWhenStopped = NO; + [activityIndicatorView startAnimating]; + _activityIndicatorView = activityIndicatorView; + + UIStackView *actionsStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ mainButton, secondaryButton, activityIndicatorView ]]; + actionsStackView.translatesAutoresizingMaskIntoConstraints = NO; + actionsStackView.axis = UILayoutConstraintAxisHorizontal; + actionsStackView.spacing = 10.0; + actionsStackView.alignment = UIStackViewAlignmentCenter; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ actionsStackView ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.alignment = UIStackViewAlignmentCenter; + [contentView addSubview:stackView]; + + UIView *separatorView = [[UIView alloc] init]; + separatorView.translatesAutoresizingMaskIntoConstraints = NO; + separatorView.backgroundColor = [UIColor dw_separatorLineColor]; + [contentView addSubview:separatorView]; + + UILayoutGuide *guide = self.contentView.layoutMarginsGuide; + + [titleLabel setContentHuggingPriority:UILayoutPriorityRequired - 1 + forAxis:UILayoutConstraintAxisVertical]; + [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired - 2 + forAxis:UILayoutConstraintAxisVertical]; + + [NSLayoutConstraint activateConstraints:@[ + [contentView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor], + [contentView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], + [self.contentView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor], + [self.contentView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + + [titleLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor + constant:16.0], + [titleLabel.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + + [stackView.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor + constant:12.0], + [stackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor], + + [actionsStackView.heightAnchor constraintEqualToConstant:BUTTON_HEIGHT], + [mainButton.heightAnchor constraintEqualToConstant:BUTTON_HEIGHT], + [secondaryButton.heightAnchor constraintEqualToConstant:BUTTON_HEIGHT], + + [separatorView.topAnchor constraintEqualToAnchor:stackView.bottomAnchor + constant:20.0], + [separatorView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:separatorView.trailingAnchor], + [separatorView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor], + [separatorView.heightAnchor constraintEqualToConstant:1.0], + + (_contentWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:200]), + ]]; + } + return self; +} + +- (CGFloat)contentWidth { + return self.contentWidthConstraint.constant; +} + +- (void)setContentWidth:(CGFloat)contentWidth { + self.contentWidthConstraint.constant = contentWidth; + [self invalidateIntrinsicContentSize]; +} + +- (void)setModel:(DWUserProfileModel *)model { + _model = model; + + [self configureForIncomingStatus]; + + [self updateState:self.model.acceptRequestState]; + + [self invalidateIntrinsicContentSize]; +} + +- (void)prepareForReuse { + [super prepareForReuse]; + + [self.activityIndicatorView startAnimating]; +} + +#pragma mark - Private + +- (void)configureForIncomingStatus { + NSMutableAttributedString *mutableTitle = [[NSMutableAttributedString alloc] init]; + + NSAttributedString *username = [[NSAttributedString alloc] initWithString:self.model.username ? self.model.username : @"" + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline], + }]; + NSAttributedString *description = [[NSAttributedString alloc] + initWithString:NSLocalizedString(@"has requested to be your friend", @"Username has requested to be your friend") + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody], + }]; + + [mutableTitle beginEditing]; + [mutableTitle appendAttributedString:username]; + [mutableTitle appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; + [mutableTitle appendAttributedString:description]; + [mutableTitle endEditing]; + + self.titleLabel.attributedText = mutableTitle; + [self.mainButton setTitle:NSLocalizedString(@"Accept", nil) forState:UIControlStateNormal]; + [self.secondaryButton setTitle:NSLocalizedString(@"Ignore", nil) forState:UIControlStateNormal]; +} + +- (void)mainButtonAction:(UIButton *)sender { + [self.delegate userProfileContactActionsCell:self mainButtonAction:sender]; +} + +- (void)secondaryButtonAction:(UIButton *)sender { + [self.delegate userProfileContactActionsCell:self secondaryButtonAction:sender]; +} + +// request state is used +- (void)updateState:(DWUserProfileModelState)state { + switch (state) { + case DWUserProfileModelState_None: + case DWUserProfileModelState_Error: + self.mainButton.hidden = NO; + self.secondaryButton.hidden = NO; + self.activityIndicatorView.hidden = YES; + + break; + case DWUserProfileModelState_Loading: + self.mainButton.hidden = YES; + self.secondaryButton.hidden = YES; + self.activityIndicatorView.hidden = NO; + + break; + case DWUserProfileModelState_Done: + self.mainButton.hidden = YES; + self.secondaryButton.hidden = YES; + self.activityIndicatorView.hidden = YES; + + break; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.h b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.h new file mode 100644 index 000000000..0d08f32cd --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.h @@ -0,0 +1,41 @@ +// +// 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 +#import + +NS_ASSUME_NONNULL_BEGIN + +@class DWUserProfileModel; +@class DWUserProfileHeaderView; + +@protocol DWUserProfileHeaderViewDelegate + +- (void)userProfileHeaderView:(DWUserProfileHeaderView *)view actionButtonAction:(UIButton *)sender; + +@end + +@interface DWUserProfileHeaderView : KVOUICollectionReusableView + +@property (nullable, nonatomic, strong) DWUserProfileModel *model; +@property (nullable, nonatomic, weak) id delegate; + +- (void)setScrollingPercent:(float)percent; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.m new file mode 100644 index 000000000..9626fb2ec --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileHeaderView.m @@ -0,0 +1,362 @@ +// +// 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 "DWUserProfileHeaderView.h" + +#import "DWActionButton.h" +#import "DWDPAvatarView.h" +#import "DWPendingContactInfoView.h" +#import "DWUIKit.h" +#import "DWUserProfileModel.h" + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const AVATAR_SIZE = 128.0; +static CGFloat const BUTTON_HEIGHT = 40.0; + +@interface DWUserProfileHeaderView () + +@property (readonly, nonatomic, strong) UIView *centerContentView; +@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; +@property (readonly, nonatomic, strong) UILabel *detailsLabel; +@property (readonly, nonatomic, strong) UIView *bottomContentView; +@property (readonly, nonatomic, strong) DWPendingContactInfoView *pendingView; +@property (readonly, nonatomic, strong) DWActionButton *actionButton; +@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileHeaderView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_backgroundColor]; + + UIView *topContainerView = [[UIView alloc] init]; + topContainerView.translatesAutoresizingMaskIntoConstraints = NO; + topContainerView.backgroundColor = self.backgroundColor; +#ifdef DEBUG + topContainerView.accessibilityIdentifier = @"topContainerView"; +#endif /* DEBUG */ + [self addSubview:topContainerView]; + + UIView *centerContentView = [[UIView alloc] init]; + centerContentView.translatesAutoresizingMaskIntoConstraints = NO; + centerContentView.backgroundColor = self.backgroundColor; +#ifdef DEBUG + centerContentView.accessibilityIdentifier = @"centerContentView"; +#endif /* DEBUG */ + [topContainerView addSubview:centerContentView]; + _centerContentView = centerContentView; + + DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:CGRectZero]; + avatarView.translatesAutoresizingMaskIntoConstraints = NO; + avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; +#ifdef DEBUG + avatarView.accessibilityIdentifier = @"avatarView"; +#endif /* DEBUG */ + [centerContentView addSubview:avatarView]; + _avatarView = avatarView; + + UILabel *detailsLabel = [[UILabel alloc] init]; + detailsLabel.translatesAutoresizingMaskIntoConstraints = NO; + detailsLabel.backgroundColor = self.backgroundColor; + detailsLabel.textColor = [UIColor dw_darkTitleColor]; + detailsLabel.textAlignment = NSTextAlignmentCenter; + detailsLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]; + detailsLabel.adjustsFontForContentSizeCategory = YES; + detailsLabel.numberOfLines = 0; + [centerContentView addSubview:detailsLabel]; + _detailsLabel = detailsLabel; + + UIView *bottomContentView = [[UIView alloc] init]; + bottomContentView.translatesAutoresizingMaskIntoConstraints = NO; + bottomContentView.backgroundColor = self.backgroundColor; +#ifdef DEBUG + bottomContentView.accessibilityIdentifier = @"bottomContentView"; +#endif /* DEBUG */ + [self addSubview:bottomContentView]; + _bottomContentView = bottomContentView; + + UIView *bottomGrayView = [[UIView alloc] init]; + bottomGrayView.translatesAutoresizingMaskIntoConstraints = NO; + bottomGrayView.backgroundColor = [UIColor dw_secondaryBackgroundColor]; +#ifdef DEBUG + bottomGrayView.accessibilityIdentifier = @"bottomGrayView"; +#endif /* DEBUG */ + [bottomContentView addSubview:bottomGrayView]; + + DWPendingContactInfoView *pendingView = [[DWPendingContactInfoView alloc] init]; + pendingView.translatesAutoresizingMaskIntoConstraints = NO; + _pendingView = pendingView; + + UIStackView *pendingStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ pendingView ]]; + pendingStackView.translatesAutoresizingMaskIntoConstraints = NO; + pendingStackView.axis = UILayoutConstraintAxisVertical; + [bottomContentView addSubview:pendingStackView]; + + DWActionButton *actionButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + actionButton.translatesAutoresizingMaskIntoConstraints = NO; + [actionButton addTarget:self action:@selector(actionButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + [actionButton setTitle:NSLocalizedString(@"Pay", nil) forState:UIControlStateNormal]; + [bottomContentView addSubview:actionButton]; + _actionButton = actionButton; + + UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; + activityIndicatorView.color = [UIColor dw_dashBlueColor]; + [bottomContentView addSubview:activityIndicatorView]; + _activityIndicatorView = activityIndicatorView; + + UILayoutGuide *guide = self.layoutMarginsGuide; + + const CGFloat buttonPadding = 16.0; + const CGFloat spacing = 20.0; + + [detailsLabel setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisVertical]; + + [NSLayoutConstraint activateConstraints:@[ + [topContainerView.topAnchor constraintEqualToAnchor:self.topAnchor], + [topContainerView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:topContainerView.trailingAnchor], + + [bottomContentView.topAnchor constraintEqualToAnchor:topContainerView.bottomAnchor + constant:spacing], + [bottomContentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:bottomContentView.trailingAnchor], + [self.bottomAnchor constraintEqualToAnchor:bottomContentView.bottomAnchor], + + [centerContentView.topAnchor constraintGreaterThanOrEqualToAnchor:topContainerView.topAnchor], + [centerContentView.leadingAnchor constraintEqualToAnchor:topContainerView.leadingAnchor], + [topContainerView.trailingAnchor constraintEqualToAnchor:centerContentView.trailingAnchor], + [topContainerView.bottomAnchor constraintGreaterThanOrEqualToAnchor:centerContentView.bottomAnchor], + [centerContentView.centerYAnchor constraintEqualToAnchor:topContainerView.centerYAnchor], + + [avatarView.topAnchor constraintEqualToAnchor:centerContentView.topAnchor], + [avatarView.centerXAnchor constraintEqualToAnchor:centerContentView.centerXAnchor], + [avatarView.widthAnchor constraintEqualToConstant:AVATAR_SIZE], + [avatarView.heightAnchor constraintEqualToConstant:AVATAR_SIZE], + + [detailsLabel.topAnchor constraintEqualToAnchor:avatarView.bottomAnchor + constant:spacing], + [detailsLabel.leadingAnchor constraintEqualToAnchor:centerContentView.leadingAnchor], + [centerContentView.trailingAnchor constraintEqualToAnchor:detailsLabel.trailingAnchor], + [centerContentView.bottomAnchor constraintEqualToAnchor:detailsLabel.bottomAnchor], + + [bottomGrayView.topAnchor constraintEqualToAnchor:actionButton.centerYAnchor], + [bottomGrayView.leadingAnchor constraintEqualToAnchor:bottomContentView.leadingAnchor], + [bottomContentView.trailingAnchor constraintEqualToAnchor:bottomGrayView.trailingAnchor], + [bottomContentView.bottomAnchor constraintEqualToAnchor:bottomGrayView.bottomAnchor], + + [pendingStackView.topAnchor constraintEqualToAnchor:actionButton.topAnchor], + [pendingStackView.leadingAnchor constraintEqualToAnchor:actionButton.leadingAnchor], + [pendingStackView.trailingAnchor constraintEqualToAnchor:actionButton.trailingAnchor], + [pendingStackView.bottomAnchor constraintEqualToAnchor:actionButton.bottomAnchor], + + [actionButton.topAnchor constraintEqualToAnchor:bottomContentView.topAnchor], + [actionButton.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor + constant:buttonPadding], + [guide.trailingAnchor constraintEqualToAnchor:actionButton.trailingAnchor + constant:buttonPadding], + [bottomContentView.bottomAnchor constraintEqualToAnchor:actionButton.bottomAnchor + constant:spacing], + [actionButton.heightAnchor constraintEqualToConstant:BUTTON_HEIGHT], + + [activityIndicatorView.centerYAnchor constraintEqualToAnchor:actionButton.centerYAnchor], + [activityIndicatorView.centerXAnchor constraintEqualToAnchor:bottomContentView.centerXAnchor], + ]]; + + [self mvvm_observe:DW_KEYPATH(self, model.state) + with:^(typeof(self) self, id value) { + [self updateState:self.model.state]; + }]; + + [self mvvm_observe:DW_KEYPATH(self, model.sendRequestState) + with:^(typeof(self) self, id value) { + if (self.model.state == DWUserProfileModelState_Done) { + [self updateSendRequestState:self.model.sendRequestState]; + } + }]; + + [self mvvm_observe:DW_KEYPATH(self, model.acceptRequestState) + with:^(typeof(self) self, id value) { + if (self.model.acceptRequestState == DWUserProfileModelState_Done) { + [self updateActions]; + } + }]; + } + return self; +} + +- (void)setModel:(DWUserProfileModel *)model { + _model = model; + + [self updateBlockchainIdentity:model.item.blockchainIdentity]; +} + +- (void)setScrollingPercent:(float)percent { + if (percent < 0) { // stretching + const CGFloat scale = MIN(1.4, 1.0 + ABS(percent)); + const CGFloat translation = MIN(ABS(percent) * 70, 25); + self.centerContentView.transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(scale, scale), 0, translation); + self.centerContentView.alpha = 1.0; + } + else { + self.centerContentView.transform = CGAffineTransformIdentity; + self.centerContentView.alpha = MAX(0.0, 1.0 - percent * 2.5); // x2.5 speed + + const CGFloat threshold = 0.5; + if (percent > threshold) { + const float translatedPercent = (percent - threshold) * (1.0 / (1.0 - threshold)); + const CGFloat clampPercent = MIN(1.0, translatedPercent); + self.actionButton.alpha = 1.0 - clampPercent * 2; // 2x speed + } + else { + self.actionButton.alpha = 1.0; + } + } +} + +#pragma mark - Private + +- (void)updateState:(DWUserProfileModelState)state { + switch (state) { + case DWUserProfileModelState_None: + self.actionButton.hidden = YES; + self.pendingView.hidden = YES; + [self.activityIndicatorView stopAnimating]; + + break; + case DWUserProfileModelState_Error: + [self.activityIndicatorView stopAnimating]; + [self updateActions]; + + break; + case DWUserProfileModelState_Loading: + self.actionButton.hidden = YES; + self.pendingView.hidden = YES; + [self.activityIndicatorView startAnimating]; + + break; + case DWUserProfileModelState_Done: + [self.activityIndicatorView stopAnimating]; + [self updateActions]; + break; + } +} + +- (void)updateSendRequestState:(DWUserProfileModelState)state { + switch (state) { + case DWUserProfileModelState_None: + [self updateActions]; + + break; + case DWUserProfileModelState_Error: + [self updateActions]; + + break; + case DWUserProfileModelState_Loading: + [self.activityIndicatorView stopAnimating]; + + self.actionButton.hidden = YES; + self.pendingView.hidden = NO; + [self.pendingView setAsSendingRequest]; + + break; + case DWUserProfileModelState_Done: + [self updateActions]; + + break; + } +} + +- (void)updateBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; + [result beginEditing]; + + BOOL hasDisplayName = blockchainIdentity.displayName.length > 0; + NSString *title = hasDisplayName ? blockchainIdentity.displayName : blockchainIdentity.currentDashpayUsername; + + NSAttributedString *titleString = [[NSAttributedString alloc] + initWithString:title ? title : @"" + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline], + NSForegroundColorAttributeName : [UIColor dw_darkTitleColor], + }]; + [result appendAttributedString:titleString]; + + if (hasDisplayName) { + NSString *subtitle = [NSString stringWithFormat:@"\n%@", blockchainIdentity.currentDashpayUsername]; + NSAttributedString *subtitleString = [[NSAttributedString alloc] + initWithString:subtitle + attributes:@{ + NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote], + NSForegroundColorAttributeName : [UIColor dw_tertiaryTextColor], + }]; + [result appendAttributedString:subtitleString]; + } + + [result endEditing]; + + self.detailsLabel.attributedText = result; + self.avatarView.blockchainIdentity = blockchainIdentity; + + [self setScrollingPercent:0.0]; +} + +- (void)updateActions { + const DSBlockchainIdentityFriendshipStatus friendshipStatus = self.model.friendshipStatus; + switch (friendshipStatus) { + case DSBlockchainIdentityFriendshipStatus_Unknown: + self.actionButton.hidden = YES; + self.pendingView.hidden = YES; + + break; + case DSBlockchainIdentityFriendshipStatus_None: + self.actionButton.hidden = NO; + self.pendingView.hidden = YES; + + [self.actionButton setImage:[UIImage imageNamed:@"dp_send_request"] forState:UIControlStateNormal]; + [self.actionButton setTitle:NSLocalizedString(@"Send Contact Request", nil) forState:UIControlStateNormal]; + + break; + case DSBlockchainIdentityFriendshipStatus_Outgoing: + self.actionButton.hidden = YES; + self.pendingView.hidden = NO; + [self.pendingView setAsPendingRequest]; + + break; + case DSBlockchainIdentityFriendshipStatus_Incoming: + case DSBlockchainIdentityFriendshipStatus_Friends: + self.actionButton.hidden = NO; + self.pendingView.hidden = YES; + + [self.actionButton setTitle:NSLocalizedString(@"Pay", nil) forState:UIControlStateNormal]; + + break; + } +} + +- (void)actionButtonAction:(UIButton *)sender { + [self.delegate userProfileHeaderView:self actionButtonAction:sender]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.h b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.h new file mode 100644 index 000000000..cedb5c8f1 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.h @@ -0,0 +1,35 @@ +// +// 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 + +@class DSBlockchainIdentity; + +@interface DWUserProfileNavigationTitleView : UIView + +- (void)updateWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; +- (void)setScrollingPercent:(float)percent; + +- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.m new file mode 100644 index 000000000..144fd53cb --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileNavigationTitleView.m @@ -0,0 +1,106 @@ +// +// 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 "DWUserProfileNavigationTitleView.h" + +#import "DWDPAvatarView.h" +#import "DWUIKit.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +static CGFloat const AVATAR_SIZE = 28.0; +static CGFloat const VIEW_HEIGHT = 44.0; + +@interface DWUserProfileNavigationTitleView () + +@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; +@property (readonly, nonatomic, strong) NSLayoutConstraint *titleTopConstraint; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileNavigationTitleView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + self.clipsToBounds = YES; + + DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:CGRectZero]; + avatarView.translatesAutoresizingMaskIntoConstraints = NO; + avatarView.small = YES; + avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; + [self addSubview:avatarView]; + _avatarView = avatarView; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.backgroundColor = self.backgroundColor; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.font = [UIFont dw_navigationBarTitleFont]; + [self addSubview:titleLabel]; + _titleLabel = titleLabel; + + [titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired + forAxis:UILayoutConstraintAxisHorizontal]; + + [NSLayoutConstraint activateConstraints:@[ + [avatarView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [avatarView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + [avatarView.widthAnchor constraintEqualToConstant:AVATAR_SIZE], + [avatarView.heightAnchor constraintEqualToConstant:AVATAR_SIZE], + + (_titleTopConstraint = [titleLabel.topAnchor constraintEqualToAnchor:self.topAnchor]), + [titleLabel.leadingAnchor constraintEqualToAnchor:avatarView.trailingAnchor + constant:10.0], + [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + [titleLabel.heightAnchor constraintEqualToAnchor:self.heightAnchor], + ]]; + } + return self; +} + +- (CGSize)intrinsicContentSize { + return CGSizeMake(UIViewNoIntrinsicMetric, VIEW_HEIGHT); +} + +- (void)updateWithBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { + self.titleLabel.text = blockchainIdentity.currentDashpayUsername; + self.avatarView.blockchainIdentity = blockchainIdentity; + + [self setScrollingPercent:0.0]; +} + +- (void)setScrollingPercent:(float)percent { + const CGFloat threshold = 0.4; + if (percent > threshold) { + const float translatedPercent = (percent - threshold) * (1.0 / (1.0 - threshold)); + self.avatarView.alpha = MIN(1.0, translatedPercent); + self.titleTopConstraint.constant = MAX(0.0, VIEW_HEIGHT * (1.0 - translatedPercent)); + } + else { + self.avatarView.alpha = 0.0; + self.titleTopConstraint.constant = VIEW_HEIGHT; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.h b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.h new file mode 100644 index 000000000..c083281c1 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.h @@ -0,0 +1,41 @@ +// +// 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 + +@class DWUserProfileModel; +@class DWUserProfileSendRequestCell; + +@protocol DWUserProfileSendRequestCellDelegate + +- (void)userProfileSendRequestCell:(DWUserProfileSendRequestCell *)cell sendRequestButtonAction:(UIButton *)sender; + +@end + + +@interface DWUserProfileSendRequestCell : UICollectionViewCell + +@property (nonatomic, assign) CGFloat contentWidth; + +@property (nullable, nonatomic, strong) DWUserProfileModel *model; +@property (nullable, nonatomic, weak) id delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.m b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.m new file mode 100644 index 000000000..954e5c8df --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Profile/Views/DWUserProfileSendRequestCell.m @@ -0,0 +1,342 @@ +// +// 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 "DWUserProfileSendRequestCell.h" + +#import "DWActionButton.h" +#import "DWShadowView.h" +#import "DWUIKit.h" +#import "DWUserProfileModel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWUserProfileSendRequestCell () + +@property (readonly, nonatomic, strong) UILabel *textLabel; +@property (readonly, nonatomic, strong) DWActionButton *sendRequestButton; +@property (readonly, nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; + +@property (nullable, nonatomic, strong) NSLayoutConstraint *contentWidthConstraint; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWUserProfileSendRequestCell + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor dw_secondaryBackgroundColor]; + self.contentView.backgroundColor = self.backgroundColor; + + DWShadowView *shadowView = [[DWShadowView alloc] initWithFrame:CGRectZero]; + shadowView.translatesAutoresizingMaskIntoConstraints = NO; + [self.contentView addSubview:shadowView]; + + UIView *roundedContentView = [[UIView alloc] initWithFrame:CGRectZero]; + roundedContentView.translatesAutoresizingMaskIntoConstraints = NO; + roundedContentView.backgroundColor = [UIColor dw_backgroundColor]; + roundedContentView.layer.cornerRadius = 8.0; + roundedContentView.layer.masksToBounds = YES; + [shadowView addSubview:roundedContentView]; + + UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dp_friendship"]]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + + UILabel *textLabel = [[UILabel alloc] init]; + textLabel.translatesAutoresizingMaskIntoConstraints = NO; + textLabel.numberOfLines = 0; + textLabel.textColor = [UIColor dw_darkTitleColor]; + textLabel.textAlignment = NSTextAlignmentCenter; + _textLabel = textLabel; + + DWActionButton *sendRequestButton = [[DWActionButton alloc] initWithFrame:CGRectZero]; + sendRequestButton.translatesAutoresizingMaskIntoConstraints = NO; + [sendRequestButton setImage:[UIImage imageNamed:@"dp_send_request"] forState:UIControlStateNormal]; + [sendRequestButton setTitle:NSLocalizedString(@"Send Contact Request", nil) forState:UIControlStateNormal]; + sendRequestButton.inverted = YES; + [sendRequestButton addTarget:self + action:@selector(sendRequestButtonAction:) + forControlEvents:UIControlEventTouchUpInside]; + _sendRequestButton = sendRequestButton; + + // fire up activity indicator in advance to fix reuse issue + UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; + activityIndicatorView.color = [UIColor dw_dashBlueColor]; + [activityIndicatorView startAnimating]; + activityIndicatorView.hidesWhenStopped = NO; + _activityIndicatorView = activityIndicatorView; + + UIView *separatorView = [[UIView alloc] init]; + separatorView.translatesAutoresizingMaskIntoConstraints = NO; + separatorView.backgroundColor = [UIColor dw_separatorLineColor]; + [self.contentView addSubview:separatorView]; + + UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ imageView, textLabel, sendRequestButton, activityIndicatorView ]]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.spacing = 20.0; + stackView.alignment = UIStackViewAlignmentCenter; + [self.contentView addSubview:stackView]; + + const CGFloat verticalPadding = 5.0; + const CGFloat itemVerticalPadding = 18.0; + const CGFloat itemHorizontalPadding = verticalPadding + 10.0; + const CGFloat separatorTopPadding = itemVerticalPadding * 2; + const CGFloat stackBottomPadding = itemVerticalPadding + separatorTopPadding; + + UILayoutGuide *guide = self.contentView.layoutMarginsGuide; + [NSLayoutConstraint activateConstraints:@[ + [shadowView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor + constant:verticalPadding], + [shadowView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor + constant:8.0], + [guide.trailingAnchor constraintEqualToAnchor:shadowView.trailingAnchor + constant:8.0], + + [separatorView.topAnchor constraintEqualToAnchor:shadowView.bottomAnchor + constant:separatorTopPadding], + [separatorView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], + [guide.trailingAnchor constraintEqualToAnchor:separatorView.trailingAnchor], + [self.contentView.bottomAnchor constraintEqualToAnchor:separatorView.bottomAnchor + constant:verticalPadding], + [separatorView.heightAnchor constraintEqualToConstant:1.0], + + [roundedContentView.topAnchor constraintEqualToAnchor:shadowView.topAnchor], + [roundedContentView.leadingAnchor constraintEqualToAnchor:shadowView.leadingAnchor], + [shadowView.trailingAnchor constraintEqualToAnchor:roundedContentView.trailingAnchor], + [shadowView.bottomAnchor constraintEqualToAnchor:roundedContentView.bottomAnchor], + + [stackView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor + constant:itemVerticalPadding], + [stackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor + constant:itemHorizontalPadding], + [guide.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor + constant:itemHorizontalPadding], + [self.contentView.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor + constant:stackBottomPadding], + (_contentWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:200]), + + [imageView.heightAnchor constraintEqualToConstant:60.0], + [sendRequestButton.heightAnchor constraintEqualToConstant:40.0], + ]]; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(contentSizeCategoryDidChangeNotification) + name:UIContentSizeCategoryDidChangeNotification + object:nil]; + } + return self; +} + +- (CGFloat)contentWidth { + return self.contentWidthConstraint.constant; +} + +- (void)setContentWidth:(CGFloat)contentWidth { + self.contentWidthConstraint.constant = contentWidth; +} + +- (void)setHighlighted:(BOOL)highlighted { + [super setHighlighted:highlighted]; + + [self dw_pressedAnimation:DWPressedAnimationStrength_Light pressed:highlighted]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + [self reloadAttributedData]; +} + +- (void)prepareForReuse { + [super prepareForReuse]; + + [self.activityIndicatorView startAnimating]; +} + +- (void)setModel:(DWUserProfileModel *)model { + _model = model; + + [self reloadAttributedData]; +} + +#pragma mark - Notifications + +- (void)contentSizeCategoryDidChangeNotification { + [self reloadAttributedData]; +} + +#pragma mark - Private + +- (void)reloadAttributedData { + [self updateState:self.model.sendRequestState]; +} + +- (void)sendRequestButtonAction:(UIButton *)sender { + [self.delegate userProfileSendRequestCell:self sendRequestButtonAction:sender]; +} + +// request state is used +- (void)updateState:(DWUserProfileModelState)state { + [self updateActions]; + + // Redesigned. Action button is not used. + self.activityIndicatorView.hidden = YES; + self.sendRequestButton.hidden = YES; + + // switch (state) { + // case DWUserProfileModelState_None: + // self.activityIndicatorView.hidden = YES; + // + // break; + // case DWUserProfileModelState_Error: + // self.activityIndicatorView.hidden = YES; + // + // break; + // case DWUserProfileModelState_Loading: + // self.sendRequestButton.hidden = YES; + // self.activityIndicatorView.hidden = NO; + // + // break; + // case DWUserProfileModelState_Done: + // self.activityIndicatorView.hidden = YES; + // + // break; + // } +} + +- (void)updateActions { + const DSBlockchainIdentityFriendshipStatus friendshipStatus = self.model.friendshipStatus; + switch (friendshipStatus) { + case DSBlockchainIdentityFriendshipStatus_Unknown: + self.sendRequestButton.hidden = YES; + self.textLabel.hidden = YES; + self.textLabel.attributedText = nil; + + break; + case DSBlockchainIdentityFriendshipStatus_None: // not a friend nor incoming request + self.sendRequestButton.hidden = NO; + self.textLabel.hidden = NO; + self.textLabel.attributedText = [self notAContactText]; + + break; + case DSBlockchainIdentityFriendshipStatus_Outgoing: // request was sent, pending + self.sendRequestButton.hidden = YES; + self.textLabel.hidden = NO; + self.textLabel.attributedText = [self pendingContactText]; + + break; + case DSBlockchainIdentityFriendshipStatus_Incoming: + case DSBlockchainIdentityFriendshipStatus_Friends: + self.sendRequestButton.hidden = YES; + self.textLabel.hidden = YES; + self.textLabel.attributedText = nil; + + // cell should be not visible + + break; + } +} + +- (NSAttributedString *)notAContactText { + if (self.model.username == nil) { + return nil; + } + + NSString *full = + [NSString stringWithFormat: + NSLocalizedString(@"Add %@ as your contact to Pay Directly to Username and Retain Mutual Transaction History", + @"Add as your contact..."), + self.model.username]; + + NSString *emphasized1 = + NSLocalizedString(@"Pay Directly to Username", + @"emphasized text in: Add as your contact to Pay Directly to Username and Retain Mutual Transaction History"); + NSString *emphasized2 = + NSLocalizedString(@"Retain Mutual Transaction History", + @"emphasized text in: Add as your contact to Pay Directly to Username and Retain Mutual Transaction History"); + + NSRange range1 = [full rangeOfString:emphasized1]; + NSRange range2 = [full rangeOfString:emphasized2]; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:full]; + + [result beginEditing]; + + [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody]} + range:NSMakeRange(0, full.length)]; + + if (range1.location != NSNotFound) { + [result removeAttribute:NSFontAttributeName range:range1]; + + [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]} + range:range1]; + } + + if (range2.location != NSNotFound) { + [result removeAttribute:NSFontAttributeName range:range2]; + + [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]} + range:range2]; + } + + [result endEditing]; + + return [result copy]; +} + +- (NSAttributedString *)pendingContactText { + if (self.model.username == nil) { + return nil; + } + + NSString *full = + [NSString stringWithFormat: + NSLocalizedString(@"Once %@ accepts your request you can Pay Directly to Username", + @"Once accepts your request..."), + self.model.username]; + + NSString *emphasized1 = + NSLocalizedString(@"Pay Directly to Username", + @"emphasized text in: Add as your contact to Pay Directly to Username and Retain Mutual Transaction History"); + + NSRange range1 = [full rangeOfString:emphasized1]; + + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:full]; + + [result beginEditing]; + + [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleBody]} + range:NSMakeRange(0, full.length)]; + + if (range1.location != NSNotFound) { + [result removeAttribute:NSFontAttributeName range:range1]; + + [result setAttributes:@{NSFontAttributeName : [UIFont dw_fontForTextStyle:UIFontTextStyleHeadline]} + range:range1]; + } + + [result endEditing]; + + return [result copy]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m deleted file mode 100644 index 9406fcb87..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWFirstUsernameSymbolValidationRule.m +++ /dev/null @@ -1,44 +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 "DWFirstUsernameSymbolValidationRule.h" - -#import "DWUsernameValidationRule+Protected.h" - -@implementation DWFirstUsernameSymbolValidationRule - -- (NSString *)title { - return NSLocalizedString(@"Must start and end with a letter or number", @"Validation rule"); -} - -- (void)validateText:(NSString *)text { - if (text.length == 0 || [text rangeOfString:@"-"].location == NSNotFound) { - self.validationResult = DWUsernameValidationRuleResultHidden; - return; - } - - // The user should be able use a hyphen anywhere in the username except the first or last characters - if ([text hasPrefix:@"-"] || [text hasSuffix:@"-"]) { - self.validationResult = DWUsernameValidationRuleResultInvalid; - return; - } - - self.validationResult = DWUsernameValidationRuleResultValid; -} - - -@end diff --git a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m b/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m deleted file mode 100644 index b631b6bba..000000000 --- a/DashWallet/Sources/UI/DashPay/Setup/CreateUsername/Models/DWLengthUsernameValidationRule.m +++ /dev/null @@ -1,41 +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 "DWLengthUsernameValidationRule.h" - -#import "DWDashPayConstants.h" -#import "DWUsernameValidationRule+Protected.h" - -@implementation DWLengthUsernameValidationRule - -- (NSString *)title { - return [NSString stringWithFormat:NSLocalizedString(@"Between %ld and %ld characters", @"Validation rule: Between 3 and 24 characters"), DW_MIN_USERNAME_LENGTH, DW_MAX_USERNAME_LENGTH]; -} - -- (void)validateText:(NSString *)text { - const NSUInteger length = text.length; - if (length == 0) { - self.validationResult = DWUsernameValidationRuleResultEmpty; - return; - } - - BOOL isValid = length >= DW_MIN_USERNAME_LENGTH && length <= DW_MAX_USERNAME_LENGTH; - - self.validationResult = isValid ? DWUsernameValidationRuleResultValid : DWUsernameValidationRuleResultInvalid; -} - -@end diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.h b/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.h new file mode 100644 index 000000000..40d398f62 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.h @@ -0,0 +1,30 @@ +// +// 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 + +#import "DWDPBasicUserItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPSmallContactView : UIView + +@property (nullable, nonatomic, strong) id item; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.m b/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.m new file mode 100644 index 000000000..0c2c79aaa --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Views/DWDPSmallContactView.m @@ -0,0 +1,98 @@ +// +// 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 "DWDPSmallContactView.h" + +#import "DWDPAvatarView.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DWDPSmallContactView () + +@property (readonly, nonatomic, strong) DWDPAvatarView *avatarView; +@property (readonly, nonatomic, strong) UILabel *titleLabel; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDPSmallContactView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setup_smallContactView]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self setup_smallContactView]; + } + return self; +} + +- (void)setup_smallContactView { + DWDPAvatarView *avatarView = [[DWDPAvatarView alloc] initWithFrame:CGRectZero]; + avatarView.translatesAutoresizingMaskIntoConstraints = NO; + avatarView.small = YES; + avatarView.backgroundMode = DWDPAvatarBackgroundMode_Random; + [self addSubview:avatarView]; + _avatarView = avatarView; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.font = [UIFont dw_fontForTextStyle:UIFontTextStyleFootnote]; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.textColor = [UIColor dw_darkTitleColor]; + titleLabel.numberOfLines = 0; + titleLabel.adjustsFontSizeToFitWidth = YES; + titleLabel.minimumScaleFactor = 0.5; + [self addSubview:titleLabel]; + _titleLabel = titleLabel; + + const CGFloat avatarSize = 30.0; + + [titleLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; + + [NSLayoutConstraint activateConstraints:@[ + [avatarView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [avatarView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], + [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:avatarView.bottomAnchor], + [avatarView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], + [avatarView.widthAnchor constraintEqualToConstant:avatarSize], + [avatarView.heightAnchor constraintEqualToConstant:avatarSize], + + [titleLabel.topAnchor constraintEqualToAnchor:self.topAnchor], + [titleLabel.leadingAnchor constraintEqualToAnchor:avatarView.trailingAnchor + constant:12.0], + [self.bottomAnchor constraintEqualToAnchor:titleLabel.bottomAnchor], + [self.trailingAnchor constraintEqualToAnchor:titleLabel.trailingAnchor], + ]]; +} + +- (void)setItem:(id)item { + _item = item; + + self.avatarView.blockchainIdentity = item.blockchainIdentity; + self.titleLabel.text = item.displayName ?: item.username; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.h b/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.h new file mode 100644 index 000000000..c88dc8b42 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.h @@ -0,0 +1,29 @@ +// +// 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 DWDashPayAnimationView : UIView + +- (void)startAnimating; +- (void)stopAnimating; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.m b/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.m new file mode 100644 index 000000000..725ea42f2 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Views/DWDashPayAnimationView.m @@ -0,0 +1,368 @@ +// +// 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 "DWDashPayAnimationView.h" + +#import "CALayer+MBAnimationPersistence.h" +#import "DWAnimatedShapeLayer.h" +#import "DWUIKit.h" + +NS_ASSUME_NONNULL_BEGIN + +static UIBezierPath *LeftSidePath(void) { + UIBezierPath *path = [UIBezierPath bezierPath]; + [path moveToPoint:CGPointMake(0, 12)]; + [path addLineToPoint:CGPointMake(22, 23)]; + [path addLineToPoint:CGPointMake(22, 41)]; + [path addLineToPoint:CGPointMake(22, 45)]; + [path addLineToPoint:CGPointMake(0, 34)]; + [path addLineToPoint:CGPointMake(0, 12)]; + [path closePath]; + return path; +} + +static UIBezierPath *TopSidePath(void) { + UIBezierPath *bezierPath = [UIBezierPath bezierPath]; + [bezierPath moveToPoint:CGPointMake(0.5, 11)]; + [bezierPath addLineToPoint:CGPointMake(22.5, 0)]; + [bezierPath addLineToPoint:CGPointMake(44.5, 11)]; + [bezierPath addLineToPoint:CGPointMake(22.5, 22)]; + [bezierPath addLineToPoint:CGPointMake(0.5, 11)]; + [bezierPath closePath]; + return bezierPath; +} + +static UIBezierPath *RightSidePath(void) { + UIBezierPath *path = [UIBezierPath bezierPath]; + [path moveToPoint:CGPointMake(45, 12)]; + [path addLineToPoint:CGPointMake(23, 23)]; + [path addLineToPoint:CGPointMake(23, 41)]; + [path addLineToPoint:CGPointMake(23, 45)]; + [path addLineToPoint:CGPointMake(45, 34)]; + [path addLineToPoint:CGPointMake(45, 12)]; + [path closePath]; + return path; +} + +static CGFloat const SHIFT = 20.0; +static CGFloat const SCALE = 0.1; + +static CATransform3D LeftSideTransform(void) { + return CATransform3DScale(CATransform3DMakeTranslation(-SHIFT, SHIFT, 0), SCALE, SCALE, SCALE); +} + +static CATransform3D RightSideTransform(void) { + return CATransform3DScale(CATransform3DMakeTranslation(SHIFT, SHIFT, 0), SCALE, SCALE, SCALE); +} + +static CATransform3D TopSideTransform(void) { + return CATransform3DScale(CATransform3DMakeTranslation(0, -SHIFT, 0), SCALE, SCALE, SCALE); +} + +@interface DWDashPayAnimationView () + +@property (readonly, strong, nonatomic) DWAnimatedShapeLayer *leftSideLayer; +@property (readonly, strong, nonatomic) DWAnimatedShapeLayer *topSideLayer; +@property (readonly, strong, nonatomic) DWAnimatedShapeLayer *rightSideLayer; + +@property (readonly, nonatomic, strong) CALayer *leftHeadLayer; +@property (readonly, nonatomic, strong) CALayer *leftBodyLayer; +@property (readonly, nonatomic, strong) CALayer *rightHeadLayer; +@property (readonly, nonatomic, strong) CALayer *rightBodyLayer; + +@end + +NS_ASSUME_NONNULL_END + +@implementation DWDashPayAnimationView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setupView]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self setupView]; + } + return self; +} + +- (void)layoutSublayersOfLayer:(CALayer *)layer { + [super layoutSublayersOfLayer:layer]; + + if (self.layer == layer) { + const CGRect frame = self.layer.bounds; + self.leftSideLayer.frame = frame; + self.rightSideLayer.frame = frame; + self.topSideLayer.frame = frame; + + const CGSize size = frame.size; + const CGSize headSize = CGSizeMake(6, 6); + const CGSize bodySize = CGSizeMake(12, 4); + const CGFloat spacing = 1.0; + + CGFloat y = (size.height - headSize.height - spacing - bodySize.height) / 2.0; + const CGRect headFrame = CGRectMake((size.width - headSize.width) / 2.0, y, headSize.width, headSize.height); + y += spacing + headSize.height; + const CGRect bodyFrame = CGRectMake((size.width - bodySize.width) / 2.0, y, bodySize.width, bodySize.height); + + self.leftHeadLayer.frame = headFrame; + self.leftBodyLayer.frame = bodyFrame; + self.rightHeadLayer.frame = headFrame; + self.rightBodyLayer.frame = bodyFrame; + + const CGFloat userpicShift = 12; + CATransform3D leftTransform = CATransform3DMakeTranslation(-userpicShift, 0, 0); + leftTransform = CATransform3DRotate(leftTransform, -M_PI_4, 0, 1, 0); + leftTransform = CATransform3DRotate(leftTransform, 20 * M_PI / 180.0, 0, 0, 1); + self.leftHeadLayer.transform = leftTransform; + self.leftBodyLayer.transform = leftTransform; + + CATransform3D rightTransform = CATransform3DMakeTranslation(userpicShift, 0, 0); + rightTransform = CATransform3DRotate(rightTransform, M_PI_4, 0, 1, 0); + rightTransform = CATransform3DRotate(rightTransform, -20 * M_PI / 180.0, 0, 0, 1); + self.rightHeadLayer.transform = rightTransform; + self.rightBodyLayer.transform = rightTransform; + } +} + +- (CGSize)intrinsicContentSize { + return CGSizeMake(45, 56); +} + +- (void)startAnimating { + [self stopAllAnimations]; + [self startAllAnimations]; +} + +- (void)stopAnimating { + [self stopAllAnimations]; +} + +#pragma mark - Private + +- (void)setupView { + DWAnimatedShapeLayer *leftSideLayer = [DWAnimatedShapeLayer layer]; + leftSideLayer.fillColor = [UIColor dw_darkBlueColor].CGColor; + leftSideLayer.path = LeftSidePath().CGPath; + leftSideLayer.opacity = 0; + leftSideLayer.transform = LeftSideTransform(); + [self.layer addSublayer:leftSideLayer]; + _leftSideLayer = leftSideLayer; + + DWAnimatedShapeLayer *rightSideLayer = [DWAnimatedShapeLayer layer]; + rightSideLayer.fillColor = [UIColor dw_darkBlueColor].CGColor; + rightSideLayer.path = RightSidePath().CGPath; + rightSideLayer.opacity = 0; + rightSideLayer.transform = RightSideTransform(); + [self.layer addSublayer:rightSideLayer]; + _rightSideLayer = rightSideLayer; + + DWAnimatedShapeLayer *topSideLayer = [DWAnimatedShapeLayer layer]; + topSideLayer.fillColor = [UIColor colorWithRed:166.0 / 255.0 green:215.0 / 255.0 blue:245.0 / 255.0 alpha:1.0].CGColor; + topSideLayer.path = TopSidePath().CGPath; + topSideLayer.opacity = 0; + topSideLayer.transform = TopSideTransform(); + [self.layer addSublayer:topSideLayer]; + _topSideLayer = topSideLayer; + + UIImage *headImage = [UIImage imageNamed:@"dp_animation_head"]; + CALayer *leftHeadLayer = [CALayer layer]; + leftHeadLayer.contents = (id)headImage.CGImage; + leftHeadLayer.zPosition = 10; + leftHeadLayer.opacity = 0; + [self.layer addSublayer:leftHeadLayer]; + _leftHeadLayer = leftHeadLayer; + + UIImage *bodyImage = [UIImage imageNamed:@"dp_animation_body"]; + CALayer *leftBodyLayer = [CALayer layer]; + leftBodyLayer.contents = (id)bodyImage.CGImage; + leftBodyLayer.zPosition = 10; + leftBodyLayer.opacity = 0; + [self.layer addSublayer:leftBodyLayer]; + _leftBodyLayer = leftBodyLayer; + + CALayer *rightHeadLayer = [CALayer layer]; + rightHeadLayer.contents = (id)headImage.CGImage; + rightHeadLayer.zPosition = 10; + rightHeadLayer.opacity = 0; + [self.layer addSublayer:rightHeadLayer]; + _rightHeadLayer = rightHeadLayer; + + CALayer *rightBodyLayer = [CALayer layer]; + rightBodyLayer.contents = (id)bodyImage.CGImage; + rightBodyLayer.zPosition = 10; + rightBodyLayer.opacity = 0; + [self.layer addSublayer:rightBodyLayer]; + _rightBodyLayer = rightBodyLayer; +} + +- (void)stopAllAnimations { + NSArray *layers = @[ self.leftSideLayer, self.rightSideLayer, self.topSideLayer ]; + [layers makeObjectsPerformSelector:@selector(removeAllAnimations)]; + + NSArray *usersLayers = @[ self.leftHeadLayer, self.leftBodyLayer, self.rightHeadLayer, self.rightBodyLayer ]; + [usersLayers makeObjectsPerformSelector:@selector(removeAllAnimations)]; +} + +- (void)startAllAnimations { + NSArray *layers = @[ self.leftSideLayer, self.rightSideLayer, self.topSideLayer ]; + NSArray *transforms = @[ @(LeftSideTransform()), @(RightSideTransform()), @(TopSideTransform()) ]; + NSAssert(layers.count == transforms.count, @"Internal inconsistency"); + const NSUInteger count = layers.count; + + [layers makeObjectsPerformSelector:@selector(removeAllAnimations)]; + + NSValue *identityTransform = @(CATransform3DIdentity); + + const CFTimeInterval step = 0.4; + + const CFTimeInterval wait = step * count; + + for (NSInteger i = 0; i < count; i++) { + CALayer *layer = layers[i]; + NSValue *transform = transforms[i]; + [self animateCubeSide:i identityTransform:identityTransform layer:layer step:step transform:transform wait:wait]; + } + + NSArray *usersLayers = @[ self.leftHeadLayer, self.leftBodyLayer, self.rightHeadLayer, self.rightBodyLayer ]; + [usersLayers makeObjectsPerformSelector:@selector(removeAllAnimations)]; + + const CFTimeInterval halfStep = step / 2.0; + const CFTimeInterval totalDuration = step * 8; // 1 (show) + 3 (wait) + 1 (hide) + 3 (wait) + + [self animateUserpic:self.leftHeadLayer step:step beginTime:step totalDuration:totalDuration]; + [self animateUserpic:self.leftBodyLayer step:step beginTime:step + halfStep totalDuration:totalDuration]; + [self animateUserpic:self.rightHeadLayer step:step beginTime:step * 2 totalDuration:totalDuration]; + [self animateUserpic:self.rightBodyLayer step:step beginTime:step * 2 + halfStep totalDuration:totalDuration]; +} + +- (void)animateCubeSide:(NSInteger)i + identityTransform:(NSValue *)identityTransform + layer:(CALayer *)layer + step:(CFTimeInterval)step + transform:(NSValue *)transform + wait:(CFTimeInterval)wait { + CABasicAnimation *opacity1 = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacity1.fromValue = @0; + opacity1.toValue = @1; + opacity1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + opacity1.duration = step; + + CABasicAnimation *transform1 = [CABasicAnimation animationWithKeyPath:@"transform"]; + transform1.toValue = identityTransform; + transform1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; + transform1.duration = step; + + CABasicAnimation *opacity2 = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacity2.fromValue = @1; + opacity2.toValue = @1; + opacity2.duration = wait; + opacity2.beginTime = opacity1.duration; + + CABasicAnimation *transform2 = [CABasicAnimation animationWithKeyPath:@"transform"]; + transform2.fromValue = identityTransform; + transform2.toValue = identityTransform; + transform2.duration = wait; + transform2.beginTime = transform1.duration; + + CABasicAnimation *opacity3 = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacity3.fromValue = @1; + opacity3.toValue = @0; + opacity3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + opacity3.duration = step; + opacity3.beginTime = opacity2.beginTime + opacity2.duration; + + CABasicAnimation *transform3 = [CABasicAnimation animationWithKeyPath:@"transform"]; + transform3.fromValue = identityTransform; + transform3.toValue = transform; + transform3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + transform3.duration = step; + transform3.beginTime = transform2.beginTime + transform2.duration; + + CABasicAnimation *opacity4 = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacity4.fromValue = @0; + opacity4.toValue = @0; + opacity4.duration = wait; + opacity4.beginTime = opacity3.beginTime + opacity3.duration; + + CABasicAnimation *transform4 = [CABasicAnimation animationWithKeyPath:@"transform"]; + transform4.fromValue = transform; + transform4.toValue = transform; + transform4.duration = wait; + transform4.beginTime = transform3.beginTime + transform3.duration; + + NSAssert((opacity4.beginTime + opacity4.duration) == (transform4.beginTime + transform4.duration), + @"Invalid animation"); + + CAAnimationGroup *group = [CAAnimationGroup animation]; + group.animations = @[ opacity1, transform1, opacity2, transform2, opacity3, transform3, opacity4, transform4 ]; + group.beginTime = CACurrentMediaTime() + i * step; + group.duration = opacity4.beginTime + opacity4.duration; + group.repeatCount = HUGE_VALF; + + [layer addAnimation:group forKey:@"dw_dp_layer_group"]; + [layer MB_setCurrentAnimationsPersistent]; +} + +- (void)animateUserpic:(CALayer *)layer + step:(CFTimeInterval)step + beginTime:(CFTimeInterval)beginTime + totalDuration:(CFTimeInterval)totalDuration { + const CFTimeInterval halfStep = step / 2.0; + + CABasicAnimation *opacity_s = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacity_s.fromValue = @0; + opacity_s.toValue = @1; + opacity_s.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + opacity_s.duration = halfStep; + + CABasicAnimation *opacity_sw = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacity_sw.fromValue = @1; + opacity_sw.toValue = @1; + opacity_sw.duration = step * 2; + opacity_sw.beginTime = opacity_s.beginTime + opacity_s.duration; + + CABasicAnimation *opacity_h = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacity_h.fromValue = @1; + opacity_h.toValue = @0; + opacity_h.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + opacity_h.duration = halfStep; + opacity_h.beginTime = opacity_sw.beginTime + opacity_sw.duration; + + CABasicAnimation *opacity_hw = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacity_hw.fromValue = @0; + opacity_hw.toValue = @0; + opacity_hw.duration = totalDuration - (opacity_h.beginTime + opacity_h.duration); + opacity_hw.beginTime = opacity_h.beginTime + opacity_h.duration; + + CAAnimationGroup *group = [CAAnimationGroup animation]; + group.animations = @[ opacity_s, opacity_sw, opacity_h, opacity_hw ]; + group.beginTime = CACurrentMediaTime() + beginTime; + group.duration = totalDuration; + group.repeatCount = HUGE_VALF; + + [layer addAnimation:group forKey:@"dw_dp_user_animation"]; + [layer MB_setCurrentAnimationsPersistent]; +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.h b/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.h new file mode 100644 index 000000000..6683ad72f --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.h @@ -0,0 +1,28 @@ +// +// 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 UIColor (DWDashPay) + ++ (UIColor *)dw_colorWithUsername:(NSString *)username; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.m b/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.m new file mode 100644 index 000000000..6b36956c9 --- /dev/null +++ b/DashWallet/Sources/UI/DashPay/Views/UIColor+DWDashPay.m @@ -0,0 +1,40 @@ +// +// 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 "UIColor+DWDashPay.h" + +@implementation UIColor (DWDashPay) + ++ (UIColor *)dw_colorWithUsername:(NSString *)username { + if (username.length > 0) { + NSString *letter = [username substringToIndex:1]; + unichar charCode = [letter characterAtIndex:0]; + CGFloat hue; + if (charCode <= 57) { // is digit + hue = (charCode - 48) / 36.0; // 48 == '0', 36 == total count of supported characters + } + else { + hue = (charCode - 65 + 10) / 36.0; // 65 == 'A', 10 == count of digits + } + return [UIColor colorWithHue:hue saturation:0.3 brightness:0.6 alpha:1.0]; + } + else { + return [UIColor blackColor]; + } +} + +@end diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.h index e6e396659..417335e98 100644 --- a/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.h +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWDPWelcomeViewController.h @@ -17,7 +17,7 @@ #import "DWBaseActionButtonViewController.h" -#import "DWNavigationFullscreenable.h" +#import "dashwallet-Swift.h" NS_ASSUME_NONNULL_BEGIN diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.h index 9891a141c..3be168517 100644 --- a/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.h +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.h @@ -17,7 +17,7 @@ #import -#import "DWNavigationFullscreenable.h" +#import "dashwallet-Swift.h" NS_ASSUME_NONNULL_BEGIN diff --git a/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.m b/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.m index 1520318e6..d0d37feb0 100644 --- a/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.m +++ b/DashWallet/Sources/UI/DashPay/Welcome/DWInvitationFlowViewController.m @@ -19,8 +19,8 @@ #import "DWDPWelcomeViewController.h" #import "DWGetStartedViewController.h" -#import "DWNavigationController.h" #import "DWUIKit.h" +#import "dashwallet-Swift.h" NS_ASSUME_NONNULL_BEGIN diff --git a/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.h b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.h index 7e56b9cc2..de60e7e17 100644 --- a/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.h +++ b/DashWallet/Sources/UI/DashPay/Welcome/GetStarted/DWGetStartedViewController.h @@ -16,9 +16,8 @@ // #import "DWBaseActionButtonViewController.h" - #import "DWGetStarted.h" -#import "DWNavigationFullscreenable.h" +#import "dashwallet-Swift.h" NS_ASSUME_NONNULL_BEGIN From 492241864a5611ca53f4acadcf6c735d3b7e560f Mon Sep 17 00:00:00 2001 From: tikhop Date: Fri, 21 Jul 2023 19:03:56 +0400 Subject: [PATCH 010/123] chore: Update localizations --- DashWallet/ar.lproj/Localizable.strings | Bin 123958 -> 79098 bytes DashWallet/bg.lproj/Localizable.strings | Bin 127440 -> 81360 bytes DashWallet/ca.lproj/Localizable.strings | Bin 125188 -> 74312 bytes DashWallet/cs.lproj/Localizable.strings | Bin 126358 -> 76049 bytes DashWallet/da.lproj/Localizable.strings | Bin 125226 -> 74340 bytes DashWallet/de.lproj/Localizable.strings | Bin 132440 -> 78099 bytes DashWallet/el.lproj/Localizable.strings | Bin 133578 -> 94894 bytes DashWallet/en.lproj/Localizable.strings | 328 +++++++++++++++++- DashWallet/eo.lproj/Localizable.strings | Bin 125120 -> 74276 bytes DashWallet/es.lproj/Localizable.strings | Bin 131944 -> 77923 bytes DashWallet/et.lproj/Localizable.strings | Bin 125120 -> 74295 bytes DashWallet/fa.lproj/Localizable.strings | Bin 125536 -> 80358 bytes DashWallet/fi.lproj/Localizable.strings | Bin 125182 -> 74311 bytes DashWallet/fil.lproj/Localizable.strings | Bin 134192 -> 78731 bytes DashWallet/fr.lproj/Localizable.strings | Bin 134208 -> 79324 bytes DashWallet/hr.lproj/Localizable.strings | Bin 125130 -> 74282 bytes DashWallet/hu.lproj/Localizable.strings | Bin 125570 -> 74625 bytes DashWallet/id.lproj/Localizable.strings | Bin 128958 -> 76143 bytes DashWallet/it.lproj/Localizable.strings | Bin 132432 -> 77927 bytes DashWallet/ja.lproj/Localizable.strings | Bin 110318 -> 82984 bytes DashWallet/ko.lproj/Localizable.strings | Bin 109918 -> 80506 bytes DashWallet/mk.lproj/Localizable.strings | Bin 125172 -> 74382 bytes DashWallet/ms.lproj/Localizable.strings | Bin 125156 -> 74293 bytes DashWallet/nb.lproj/Localizable.strings | Bin 125220 -> 74326 bytes DashWallet/nl.lproj/Localizable.strings | Bin 131106 -> 77204 bytes DashWallet/pl.lproj/Localizable.strings | Bin 130034 -> 77724 bytes DashWallet/pt.lproj/Localizable.strings | Bin 130548 -> 77533 bytes DashWallet/ro.lproj/Localizable.strings | Bin 126050 -> 74916 bytes DashWallet/ru.lproj/Localizable.strings | Bin 130080 -> 92025 bytes DashWallet/sk.lproj/Localizable.strings | Bin 128664 -> 77741 bytes DashWallet/sl.lproj/Localizable.strings | Bin 125316 -> 74390 bytes DashWallet/sl_SI.lproj/Localizable.strings | Bin 125210 -> 74321 bytes DashWallet/sq.lproj/Localizable.strings | Bin 125230 -> 74352 bytes DashWallet/sr.lproj/Localizable.strings | Bin 125398 -> 74465 bytes DashWallet/sv.lproj/Localizable.strings | Bin 125334 -> 74440 bytes DashWallet/th.lproj/Localizable.strings | Bin 124588 -> 102190 bytes DashWallet/tr.lproj/Localizable.strings | Bin 127304 -> 77014 bytes DashWallet/uk.lproj/Localizable.strings | Bin 129712 -> 91690 bytes DashWallet/vi.lproj/Localizable.strings | Bin 127240 -> 78320 bytes DashWallet/zh-Hans.lproj/Localizable.strings | 328 +++++++++++++++++- .../zh-Hant-TW.lproj/Localizable.strings | 328 +++++++++++++++++- DashWallet/zh.lproj/Localizable.strings | Bin 103468 -> 72543 bytes DashWallet/zh_TW.lproj/Localizable.strings | Bin 103858 -> 73630 bytes 43 files changed, 942 insertions(+), 42 deletions(-) diff --git a/DashWallet/ar.lproj/Localizable.strings b/DashWallet/ar.lproj/Localizable.strings index 3d6998339f1ee526692b3ed533e7a59b16eab7e6..45407b4640756e2b74bc9af97390f9861b6ed38b 100644 GIT binary patch literal 79098 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 G-~S&y>pQOi diff --git a/DashWallet/bg.lproj/Localizable.strings b/DashWallet/bg.lproj/Localizable.strings index 41ab3e43c6a082562e657a1de9a4b4708e69429d..6162033391f7d9b48897636869fb27ba48d0d1ca 100644 GIT binary patch literal 81360 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 z zxBJh^z4=BfzVUWaH267C#$3fFEJ^pXfv+sLpT)BnUpO5Nf?P`lI<|+OBbObdwZ5oC ze6b=KBN4aFf_MdSGKs*nx5N>4T*L}4YrPC@Zu;V~e5T7j7NDI62qNSIy$0r>6pBy> z1W4vQv(j1}A~7kCoTL9;UEXn%`zq)CNB7r-PDYDS5kUCE246+-S{POR-Lsb z<=vT@qgHNV$|g7>y>Q?v5OR`;1IyCu1v*Hp=Pdo~@SYUr6HLcgm6sz0-;!x9$4tx1}$ zE0hmQ4(-A!Yjrj~l$Os~*F(a6i5981L#+^)-M%X}DjJYI_ghmATS1E|tsMeeRP@fk zp9jwEgtKKu-H+Yaj;cUs7+v=CPK%?9=%F>KEE1J0*gO=;rZu`7pXs_)ID&) zel|4PlSfMI6GODB(oYb`Nv)xo^8FlsbGQ&PY! zv@fXZwE_4*8zpAu6O`V>V`GPcCs}?Me<_%A24I?2B71=5l+RDr$$|-_{`KiJWMIPG z?fFa3I2^lr+fLti7Jexirvc#5Y-L7g18vV2W|0E&CzB&>?Os-L#B7`qk1IV-f}1p9 zr@y&H>ti0b*o#D4Keg-MRf=*H6uVLjtt**L?GfvZhzG5IINIzdY8$t~AOww#k(e*` z1(1%ZRl5i+YR$Q_1VmL(-CP}p;?$l~R=8#Txnz0OfwTgiwv30fI@|Z~zjnP;+L7^6 zW!;x_v8;2gEjU*hd#F?cZz^;|OyZ30scIb7R$l>pVdj13J^uO2RE~cKQ#N#EBY&&& z$IT-?riT|Ulu03J$ti|UrPvX69eh87H*LZ}9RG1MU z6={plSKlGy1}^2b>hP$0S6(M#B>Mi)yR0A9^R}JKjVdUy zWH?OfKhA6kVS3Gq#XqRTG}N!S7*rCP0i8Gq=%&71rn|r7Pl$;&@{MbnN==5wfI| z8&mUWJLnhX{B@I>R38cHYoZk^B1odU+gK1NV8$n=zpP`VOOiVp+607?1Yf`b18 zn-E7jxkq!vg6jOLmDUkzlzH)s~TkHUQLBXQG%zxPlfT~lRKtu{e zU>kQ(r$C(5q07$mS2DEA#_QHAAmL153=WPIZilC&wti5RUev#-LQftQj7=y2zA2ut zz5ca@lmL(30G8?X=@+R}YB3+)Us|h=eSM{dZEI+KG=W1DWsYh2`mikUMf3yPz(C*O zU*>CbVEJq%Y#FigMIsD$D?!g>tHH=*M2Ex**}?V6_!sD(jj+hp%;%apC7dske>uUf znMoH4(X%K4J=Gp=F237>T)!5Kfs;;S5`qr<-$f_B&qj|}WqHqkAT*i&5!8W>qMN2` zz;pt0mD6kU#U&%$$H{AkSMc*`Fj-w_=-*hcqt^pi>uLGnuyGoB*IJTv0^dh^7b z4wOT7QHqwv9Q7Mm*3L=()q8Ya&KL{Dwu#?MHW{keFioBJ^ zR&S(eE~!hMcx9gxslihs`FX4)M!aT@g{--S*ZD?l7nQ9xN{;{&Nw=HV zq+#b7tS&kBT&jO1+VAZH~nx7&hV;T%-(S$kb{AZa*j7RWH=TvL2@TFCd;@8_$(LHJcx*QQE;k791P-7`;bJHmMzg^zHa zP4Xap>8OYAG7jMuFXj@LZLt{_*wgXhmo`XH-ca4dhLwgR2OlkmS)qqIVs0Io#*2_r z#gGhQVRYYXD2GgP0uK!B`ANvFrhcB1S_SUMmuha_$gw6lx^*qH)&(F@!iV_emC^e_0@3 zDV-}ALC8Y3!oE~r3gE)cYZJq%OBPGT9r%56t%G!m=ct&}@ERX05OH|qckEADvzE6r zi_RH)TmA#KKeTN;>#ZL?qSYOuFU`!WqjpDs-JA~!Um&YdI5&mc$<_6Xn?nuqOPa(u z@=quVSFTxv*5EaUPTH>WUMG;UD=8{f0chqlZ*JY~9Fgtq z_Z4{cBIXNFrk=f+Gw6mdX2kbtjoz*_|N`JNqQ98sq^h1e<-- zP0yAzShi_=;n3HaB7qdsT@124HeT>2kapAG1-DzH{q(AZo!b*X^a%@etc1ymdct~W z51#6B@lUoFa$yMXe4Ob&XasoO&7#c^+mz6TpJZ~4uqQ5LyF4v~<$>v)4*Uc@=)|cuo_n0ZW$r6td$R`k$a@5S_lQ2XjshWBloA3D=+KGxHcO! zGh}k#gJ2k;J|rxp+0SQG3+Qsy?!t+CWs&a6SbVqBS5_)f+eNEW)~a;Eu1@hGWm}c`#tC=!NFq|R%Lq5A zT{M~{ZVhxzScStk^<$?GD_}d2QOkZSiK@<;60s%8#OpW6u?Wgt?7z@w=kMgUDa~CS z>?l^-gs8u^1ZbuUG>JmW>Y6~9%^g!&spM+Y?(61}Glos(pfi~AL#oy7K9Uyeql>+EB&W?ApGihT(fB_VLuVJ8@2sPc^J9a~u3>WgN+ zB7|&AHR8h>vu5)23(H{@*EF8co)z|6mey88tktPWrFNl49A2~zqUW2QR zWo2MK(p8xW*W4*7R4oOtNApe7U`mwj)S63?l=*Hn_p|k5oKwCWXICwq;MD`7x`c4a zHhK~sQXSl1no(R11Jo+eD#`g=xvUqD8g1I4%0!2=8+`!-ibb}w((Ky>BX3nK z?btGb;N)W0yEX=U0=DwVc^v1M`D(Ww$%Q(H=afT4nXOFZ@Z?A|Va#l`6y$5}~m2+9Ahc z2ZA2)QxC-)B~HlqL-?`C3k3_nT-GJ23~(I4T_bq}7!xmjy*Uv}@efOn@1Oyot~`Jm zeFVA%go?(gK#mMQLM*^yYdVS<5ka5dr2P+o|B1Ys%me2$nkYo_Q?~}lsp&H;KiH5< zRs5rnQED1%{B@E>dy&%NolI)63QVic=E#Tx$p85T;!>d;(`d@XT8kwbYS>*4|MBtVO8y&J2NseGJZ$q|NFlm&i-w-KD#{o-u{2v z{{P7SyE}V4duD$>nLV{9f1N#;-LYqP?8&X!PxgP_-?yia?YFzu<^y~7Z?g|)|1djf zzwz(X_RoW}6ZZ5U>>Z<=EB!sUo*0il^LJP6?E@R_roq7|SFO!Md!oN`uRFC|e9Y?w z{ztPd8}IwsJ^TL+`yIIQc=KB2vBtyVJG0LWhHniQKn6x`8&pqhRkRL28MHqbzO-hn z7HwFwr`F=X?cH7b@>!b2cs=GB3tLZ(5^ff{37Em6s7SCfi=ChiYN(;zO<%EoWAv5h zJoYL-C~N%KR+RO-X*B=Pet*8b($DPopn$E?_VcpZQ0b-F`OX@jT06S7KI7kKYlqg3 zt$k*HU$lRx?8%X}(`%b+XV?C)cE;Z6@AX;R->Ws-XSNTR>+NkCdul)1)r~oyu^Ap) z+pzzi=$W(6_*0wxNqY~5&f344PrdvVzFzkCZJYU6YuxQ|npJ*pcG$T6N3-`0a`^Q3 zg>yl-$cy{s=}qJBE@fgRm;ZA58QdtG?UwCGC{TM*QW1Wv-T1`lai{t0rk|;1xA9_q zkjHlo`e!fJ=Dhv2wM})%XE^u+`}b4YaqrD`+r#uT>cjKx7qesym!09>dYo#;AEhj|0Ay##=e*l?-^cW``r4q*GjZP0>Kpy@S^Vp1W^&jt|83#m zPt(5P=@%wzHXCVkVfLeGcF25oV^HX)_WS3ynh&>E6HU;yZ2Eds-ra3Rh^KjO*9tsO zOHYsO+rJj7fG2-%)Z@A@-##{)fqJ@QJ#I!ixpv%iidu5+W-c2x)^|3WSj%{l=dioJ z4_WVyldWou_DvpZJPlxDcEliXYN#RLjVPkkKz1|NkBl$3n9R@e{C3m-(E&`h&+|CT z(_N-=?>SQ*&&Hy>qqCL<+;Z){$+OxG_KtVfCZ6oQZ{|_&q2pz4dY1bikFx)4y9wKh zpX(w?k)44wXb(bJC^?_8hh{~Kn(94nU0 zA>%=(P2ay_|M>lA!;xOS`Qk14#^(NmadvzvZXe_8_^xnX>6yE>`ZJzthoC=e6>pk8 zbF;`-unz9oJ0bJ_Y|l=cRerqKqn{Y1pSIsb+o$cX4g04rrh#?SAVTBE+Xo%6TiK~w zh3e$1a|*!g#}lYm)VXDC@7a_4M%6cM#zEI;q0j9ZUX8of8d~_ms6W2ssaqiOc0Jzi z84nkj>Ovx|MPsuoWT&9J+_ez`tNV=~oL)P;cESFC#Bh7E@DO|>_&+`?UIMtvsq*|> z;V3-)r2K{#;jI00uEYA^QE^V=-{Euk%vzqe(LOQ113m7u(U|L7_WZbwebjz3F6+o> zcwscsr}ln*#=niVPwnf=YhS!*ecVQ)_Y-MUK_rOtEdJd_6L&mPAUIR{KVqEn#M;L; z2IGU9Oh5BHvQWfVg$86zz}rO!^84N?oa3;`8U72s6>k;m$hxrV@E~juXn=QGUF=Tz zH~PmAt=+a26?bEW9~OQHuY;nXGST2oqYzo;KN|HuvUiNA*6bN5NcPZA_LDC&jr_gO zGq3k}(s0va0m!iIV}{n@SexblHKmO?a(KgIr*9fHxsSXac|MKDSL?Iug-R3+xRRm) z{iDolpK-qE_-^BBx8#_FtoYxp_rtNscNv~(R)QUWezrNgHoIuQ-LZY>Itlx*AM%~P znGR~?4U(?*Y(Ycyo2ekUV;BmY{FCjO!^X2Wj7!(FyJ_u^r6+Bt)t;Qw_Eda!C>HdA zaXBQA`wYATf`~=6 z{;x6l(H+~>P!^JaeRIuzN{49o`nv76V-^=XUSN_owP}BSw06#(NQ#|pcH@|kq8SBl z_8C@=o8E^mSaX1}$CNf5@d|kGbK7^oLwq02Um7*uK{PB@5?qR7fZg?(JRrmeWXro& zCp}*l4!y{uNYn>AsJrL;M!!_nk8jcC;TE!ViHV|lVo@koj}0t6Fi?!;;4g&-KPzydjRL#4n{06;9^YeYgB9=u z2mH}~0t=Aff%Or$Y&?1DA2s@@)}oFp^?!o#pCJ=(gWlPfVX3$KSJsZ`oL$83r48RhliE-0b!8gVwi~k6x%awh!46yh=(Q#F zbD43bfw4I>d=5UMj2gHmyjOOLBF^GE$hPb`327qPro0dQ3jJ9hZ4QKd6Z<>9iD~7L zW?N^VOf%nh3_LL}XSUaExBpNig|a)QnQwQLMP_`?V1vFS{q9T#+LXucUg1b+iVy5Jbe+2;a*a%TQ05@NF}w_*b1<#WXsYqHgz zyU&&1@Z#3koF~nNg;*p1J5xw^@5p}kTqpdg@N6_hc~4^};lWgvk-mwMHtkLOE{{5< z{Lx560F)IZyZoKvLzO1X9*O)?*JY7WwZ1A3LCoT9TeIWli9T)XCYr`0&I+q{Jl)4_ zg+49of`1-A`X&3PIN+z|$3EGt*Q+se_6_zy)|$3#h1pH;MQ}+Q;m6~BAJ~({;(#}8 zKZ<79Xa8gPmk!5z@#+l{88;nux7yizO7=NVX6qj`s_s6ZM0*D z0pZJ_R^p%@VQnL;HJ|qtfurSE1o?|U6swW_us-__!~9RhB9%?5{ot!B%UNDJ^lfSQ z#B;Iy_!~{`wppBRy#`0j)+3bGE#mS^H}IP2#-QwaM{dBOZ& zk*9O!mr!)-Q1O>sv3<-x8md{fa0^;6yBW?dr$h)r_uZ4mYk5wdCxhvn< zQ+Xn>ZsqR_@5PtK>!51%Q)wsY&re@?L--aAmdpWpK4_J>;HG&LS2^}{@{f6B4O%nf z$yD?tO~muU>vfPk#LmM9kDYLH+vgfu;BSnFs{#e>fclHb+mbdY9*b0f&q9q~6ic9< z(@s=?=O5oIlmJBp-)MEI+8XS8J0&jB5}C_=`^VUUsn-lYV1$en>>*)6vJ$M2=>e{4 zyo|{^faiO&RTKqSH$MQjoXe`6Aw`!!=WnxRh;*XcpE9Z5T42|0-!j5h8HwHRTC}1M z%4EZfj?T@>;#J4ebpQSVdq#!A5&MaU{gKfic__5S1&fGqeR@CZW?fQ;KpEdqMm&VF!G0AM%Y4{~n?*d(6v-Vm1Py~?`d8}gIs~a(| zrH_i%j~$V1g`sr|Y%pKu(w~F7^W1j&K0`Y0+Cr^6jczi+S{a5W3PSX*|;UC+=b9Hre=XPsLcPc)}5Qx9CJ5cXDoWJ%kfAbRnaW8A}m#W2dqK#8PFpY@w<{Qq1+y1 z3a4;<-$R+ShR5u^=RDW(SIuV{R1|a^?=lSjbT{~pZv(IQjApRS0|KYx`K>cd)8QrO z%G@Xx8TKP9J0u&s_#e1BPM25ccF?SjI2Nd|k{we1&;t@F~AmSUeRz?|oc-?-7XW!rE#;l8?UfP#_9*69;d&PcN zlwE$}{?YJ$T(>or_p+{(hISTNDwRXY>tvOPfxfz>Q|-z|3XG2=}556OiH@0@#Iv61;5uU%ix1kp4+x74WN zqrFl*lpfPlg`Hwz?&;;MAwPF#vp{g9WMXIp+3fR2WQ`8KfC;#-SiOY4S1eJHJo?I! zYy6AE{D`T-efwb_hBkZ!oDt-4!FjdE@O|__{t-0TXP%{x+2t1f zaWtxqzBh_T(!np_@aUx851t%lzUq5+2Nt16LZPz%vcG?@pTT+LgYw)l^hB?55?6_x z*Ur4v7rYJ|eG{&dcV@=%-}8Jkh2o`_{(Y+tWIB?ZTnjPQ(`$i5<@*8RF??97~ykNLZxaanXHZJ)wfrn08;*gL^I1e@mv>NGRw zRCNy3bkW>;29iL-kc#7r?@ftQJm2_!Vu#8yBQ6|OXDytHN=CKzXXT^`I9>OgIOhGm z{YMl#kK;PEXX?cHOEtxHzYS>m`<@P8>>}*y$eH-4r?q{QXOz8RR^A)|4yf6sSzmAqdFl z>d(QfVGHWTcaF4z$7af{GsI)ORsz=jwr|n9$@uW|C*k!_!Diw7InTWN<;LuDYZqGX zFbt-xcG_ks)b`!gSB7T}jqWY-nB!ShK>ye*0%9il1g*D?>d0{ZgRMB{Ueqfw z)%fy*D(Y0@Bw(!DWUTDG5@?QjzIC=-=#F)p_ezHG`^_q4h@!=+;JLF9Oo~M)2JxBY zEv#x4yAr&nCbwEv1CR9Kpr=gR9~XbS%YkX(alIk?(d1K**PF}(xFvoLDkE+uT{LHD z9oXKN`5YN#w_|w|$(FT=+*N)>*q)pd(65iWN6I57*|@l zcXM4nZ*D<}oG5IMam(l(YH$1);tyckSSaV1^Xe3AE(HMyF^8cXl zfZiz^{{Zz5==sb-oIU6QIw6NT*G$yApFi9#!^JyyoxfMfrKeJ;_Gu#x%8ZL#?Kx zXJP}jjCsQ-ff&0a;ps^=*(ZjHzRJm5@yH3U>vowi(k*|PYSYg!n0KY$DB z&F-5PgD3U>*uM=s=WWBzcF)mrC-d`70QaZi;bm21>&IwtiFQQRMHM3&vG%Zc*Wzyh{;5~f&yLq= zIN-U~QDa}fU1^sdFC@Y>udWWcg$6z)uhJg#nQR%QdDZ1Dqn^l_jk)YEk+~M`s7Rz% z9M7FhThksJ5G?`irR*+&)-39dN*(OEpFSk{o_X8!)wv@JY?&1*S>2f z?*zrX*?W!`rxDyVN|Ns$+7J(^^^-347WsZB_!h^~|9Urc$^O0Ehp*wUGGt@&pKG%} z8vW|jJG7YEo6+|+LlSf7qu_kPF?;u_Wg)1t<(=w;@N>p-;wyo7BZz@);nHnydbx`eRT1;*jE{{aq)H`I zZznm=-M9ElqVAoy$Sa<5z5;g0vspeHeqY(A=eGAZR*(DB6(u_S!kc>3GGoaJIl8^8 z&zRjrt`C*6^v^EGdeV9RI#0ab@BQ*DS1tCR!@!&$8*i_Dz4g<)zFn$W-eRKnCx$Q1 z3ds6f`+WYmbzC*X6Q-T;IK<}`!|LwoU(&`Gw~8IYN#ErA>ZTair1%?8iR(~g4)b-c`O_Eg79ESz+E;Gf zozT?_ktZjt?v;BB(GC5^B7AK0kJs%~@%U05LWTlS+;gT~Dhs2{KxCBnO1*?CIgmM= z8^h^@e9stUL2y#PPM&isrO8#dvu@0DZVe3k*k=XU{051 zN71uWwlu7kvgwUn2B`PrLd(ph-&PEzgqyXlED6xmhSda6<)p3Ix}%oNXn*B+*Z>5}!z7>W;cPt(=8ptXLO?~U=w zj*vTXM^NkM>3&!D+uHFv=RMG)?y`#*V%Hxq>^tA>_nAMr9eT}ngz6fzRuLNQ@?MYb z+*-|gs6~cvcfK5YMh2?RDr*hxn>^N-+5(w%kBiiXn<4ArJK{vQi~L8UkquSrHOqcE z-3*A_1n#IEP>$cl?HyOs|6R*3{h-)@=WX8Hi-IQ!bDui{l(M4#J77{kfH`SEy^f<*~_k@qPhAPY-ehWKApE)A?HzoWxtym zLwH{~-y*Ns9|{#woIa(QQ< z1YduXq-xRM$O$0)1aN>&9FBULN^7rRC!T4H$RdL&ocTuTj`X z{iFS45Z&GpCU2F;t@lgN>0%=)ez+4ZG_JSJqmL))YP{4wZt4>i!^tw6>~Q>>XY41Z zO5-8r{3vcG)13r(SXGgPhgS55l*{?W(@C5*m*b#8^{K%-^@!N7SoP$UVB@;};9itA zcPdstAXL^7SOZ_^|2j!b8AYqfv={#JDNlaB8+#qwfSJK5V@pfo9!yST$mgm8!%&+%S{EbAX~K_$)iZqV zIr*rHueW*NZvPCo$PkRltB^Rpuddtr;8WVN|Hp|%d7XZ1eCUMnC2GQ*4-pT-(uSr! zEi%u~!l@%()_5r28q6)ld{-S$2;CWdJa>r|G5BEfO4q|;%}Lduf6&JQJu0_OajV{*iaboS%! z<-(J(J zt5w7cJO*xznB5 z_j#csNos22a-F|w)$f=dLpD(z0blmJ>F`MfDptx6^qh>eJNWA_r=I~kTplOlOUj+x z{j7H1Tb^mye)D+MyjG!8&2QB|YG`V8zP0n1=(E3N-{?avGX&A*nsJ|;V=;UK(dX6< z&8WTeKsr=#!RR~BDInczKY1RlT^N=`U@jo*ww9&>=dH7$clULMY&@>|MUfV)lDsiG zc}_dYy}j7^#F}-BwQ`v2o5T0k%PMB(UV?L$>rEvt(KmK^U5Q)IWBxZ8>UQn@oXP(4 zkAjXE4;|${4a4GJLCJmZ@|fe&08m^rSX5<#&Jd8iUAzOz&L-B*dHhuI)t2&*{XMU4 z_Y0$``UayIweQ_j^T)0br|IL=oCb2sq&s?7_VRvVYqrdH(?kEw0Hkk~CsC(QCu_CB{` zZ`u=fyKa15B<4iiK#?eN{ki+^l*JJ^CzoFLb@={x*v7x8YGd}&yc-w0E&bL-zn`)1 z*2Rm~KfV&UwtFRgN8sOcn$GR8-2OdrufBIv(H_upV=u7cVySSS6 zCmDCKv)(oeP!$%rbI$)+OVvxU)@0UTI};K2dJ)=sT?!{Oann!SwO-49R_!k~s$vYl z2R>Vmg{nn)%YF%L>x{sF7#f3a$^NOw8IRU37XHsJC88B$ziuNlZ*XyC!qKybSlk&7 z(s!q0z;@A~4ZreEUrj}oSFLjLvO#)fo93_^PzGP!;8fCI8@aYFWyKkn0p!Qlvv;i211jvUAZ(`Trks+>tjKK=g{wG(S z_K)ppds~=1oO~OndC(8?onF{=P&)cgUzQ=K#2z>PK)m>5sohspU6E%ZfghO^IBa`D zHJ#!m9v9@!Dt3I|9a)Oh(>uI}litX$fzRwUm$6S*cdi1SZf@rpfCuR`-7jitSnP}G zsN{L!?^qcgQ=$%o>YRXczhRH_Y{}NRU|3ZB-PE~}m&{IcZBVo)It?jAmZ2*6V2eUKK z2u~DlPL-hQdiAzD@+$B*4`*brNav**jq}a5^O0tfZ`1%O`f|_ktB4mdGBAzo^w?&c zhsy4EorD(7DeCB;G;4VYrJuR~u)ilpf(q(eGwYrG_c+t3D1`VW*w6ON>!yj2Q3*Yh zr!?Kk>X%FlBLm}(t*N}@)b7PjB+C=ch@8WX0#j|RMD`W*nWegXFQPR((9jlC=vv%^ z?b@yb_Ut{Yz&&C=w`}Z3_8fhhw(Nx;tNKRZb)PnVH~ck!eKE51?0_C$g6w=UoZDPf zPUpZ9QY6*u$dFf4t#yVSJ2%4)2UWydUS{8s+Ku__su~B&_mgLL&TtJ!b}!AmOvy{u zj#a!z%G2KuKsw(8EuKvihp2?%5kz(DFtC167XNrm9%inWQiAEh`O7=zh@t_a;q0C^2c`(r65xk6LH7ARE{r zoEJ}w1;`y9*`(}9@oMJldT8irk}+uCe9PQoHSfOIY4k}ij3?hPH%4kbSf^NvRtm`m zM#6xBu#jt=@E5B3g{p`+j6qd3Z^M(g$T{JcY@dmafkXax0A0jS8Y2InllWD4(%zU}=Y^JBT4qlHJUVo4BqS0+-hO#VYU?}Gi=f2Q|Ioe(7}QHZzR$3RokY!! z^d8TI-?R!aqA4}*#--{ErQ``&iP$~t46Mg~NVUp3h4Z9A=~^2ee%-fFL(Tdi`LRsZ zb6<@>_~+~P0Rt3=Cy&UID&^{N&XzG;!x-uh$s2cS5B08ib$>m}xT}yc6S+px>(EuO zG6TvQH<2ywnqYj9cR`<@9Vli>w84BkBEU#!&%zr^*_?Wc{sX#8u!R zq^{2&-CuTU>+jk9ymua@o@;HPE`B4nuT6L=l?m^%7i~%wN@OegyK5E!yk;xgW-Q?O z=b7wIdA%OnlV`=El=dRI?LEyK#jB(BU41eX{FPIoYFkNI|J$F@{1tBZbIdpPG<(vi z^st@XrVMZ2;n|XP+3y-v$h=thR!8)nqixQ`b-G}U74dx1)eq&|OBb=Un=2e+kDXJopbxPi)$0C3SJ5+7)_5ENy z%AUpZ#ws~}SdF|py8JBT;TMYVsM^nqB7WgG?C6|Vuy>Ag*m?{eK!16sjX__o<3y%$ z&3SU|nzXSD!Ov;0zZz>X%`W?c$qjM*ZZ2HtLi1Zq1DWE1SpK=zqkcN&$m9zV{|PK; z?0B9>SKN;Xi?o^0JzXFE()x>vU)MJkH^pv^advK*N1pFN-u;=|0mXNw5x5VtwzT_T zp4U3Vy$r_SHq`0KI?EP)9|ZGSX9&jAj=wa>(8180zS^dN3|ZgX49O+?k{l3foNgBi zLO%;nS6}~rTao+O=C#RVjXP_s0<_6>ivWpAkW^%w0Y4E7oo`K^AeMKHmF2(AGx=lD zr|}X?zm~oK)bv?0Tx#se4x#2Ai$c0D_9ht%P!-SJ0%&2B=XMt2ScyY01sx5y&1<|jJjEE@-LnjYJAXX|%=J1g1MmDVGprF^#9uCd zI$W9+gu{)U^Yx|iEBQU(XmLX;_x=T+eRDf5s5qLUcDmoM-bG__fPf2)Eg~}V`Q)e{ z8K>mA2E6}j`F-s46SmVii<2xnYL9s4aT<>X#@BfrhP0pM&L(s0!kdv^0b%z|9(`;) zlEaj>bj~{QoIIWQSFdBA1i?pj%WB`d5|LV6_e;sM*8RSo`6BJ7!v~{ZFjOM!4eavpnrEI7*z3Ysx0@1cNENh4_|doa8|Rvm z(W6SNfN@`m>9gk)7e&UkZC45B^@3f_dRzq3ZtDyy-fh`Aw@Orseax9cTD|^^-z=G< zb>K^DN|wDaR?}KN!cC(P>AUN*qXvs2ul*w+GraZiXw>UM`9~TW$6n#<@PXI1C6pkW z3c7%XLq}GgCGskK?(P%g8)(L(4nG+^>HE+QLd(dJp_l_WA(M0b5$d(xeN`$D<`e7p zTb2v;71LFuer2mcj?(>tT|X<}s{PfTRuFhx=9`R}@%Ud20oo7wnov0UKI7Ec48O6Q zb8^i$3e9r+6PXv1^5m~ki9!Yz=Xa5#(NZ94H)@dp)O= zZpbm=MC*(fnU$Abr?r(vfXC4NiCJGoCg;6_H zxHv0kN~o|i(1P41%n%`ua<(=nt-$M70gr5aD!{ncvdwAcB#16-8wMXYYV&S$LMVy! zyPOcet@Wz@_tQ}FW#do8USi$iNgi>?*Mvmj++aW59{%J~!+WBQ(+B=>&#n7Yd- zP6N-MUcj@tBRqB649{i5A)KCb{WAvQX~uAVntSQ`j2FGTj`6hn0@c_ z=;QG#py@j`|IM|^k1IaSK2URt#Irl~UWvJD>kv^PxTrn_Tk4;8R z+=-xk0zAO@FS2wM3_G=+5*B!p;_o@yu?SrCml?((>AYMdNHE}ipe7TEjx>*x+-us< z`NBgjGj!_rw$0kJhS^iQsf?v3Rcu_cvmXMU{ig47j-#IDvG-z0GTP%xdf*`?K1a>r z#)L8qPA;w!^sbsHuvmK=_nWqAes=rUw$;s|^&TE~ExJ(NunMm)tCi_O=WX&jO#>J5 z8s8umN?>P*X9p%dMV@JkPf)fedlOztbj$DY*)(s#MSI?!IzjI5E!)%d z>oE?z48+QKj?RjV!|SNCs(Gurh=`?iV;|Y?zzY5?!JVlO3kH8BScpp9tjGJ@S`9Gs zAg6RZ{ZtIAI=$Y1JNb9~gmQd5wJjwG&f97$wx*~WI(@+4YtY{+&v^X(##t|5P;x&* z#l9kP$VogW>?ga6*oSKQ@mWu44}iBmt+>Tnf18Z6Vg2X(*=E^YL~rjI4e894jDz78 zd4%y+<%*p-8Z*>id88}0_Z1gExxHd>a`x%64}!Bmhq3qZrbMQTGDu^mM8EG?woA>S z=Jn$vL@n)A!y|m@ykV0u;{+Y~c5c}Iz|)CcR(mjBCJZf(kv`}UHHQ3YUZJRsuQ^6H zPla=@kO9v2e5*BtEjQ@eD1Ji*7CG^4GJ4{qr@&r3yK3)M zM~YRSF*n>Muh^Ja<3v{W7c|oEMSbmsuhgU4oM_E$3uleT6VH>jT;pvT7`yT5H%!*V z+PxeI#lvD9-!_<_vG!h(@o?f?)AQ5Gv_L9j@k$SdAE8Am7O6_o(DCq0atfdOXeG_{ zeYUUf84n?QB-_?LJ3oP)9i&@)i2~?U_A>lfOM>78CDrl8TmFG zcfJ;jN-(6QstT~d>5a95Q=+-+M9_S;k9$%Wd)@SYWemXMvJZM$D1us-|1$HUo%v(P zL?6lNunR-H$z!zNWh>A5>u{UlQKFxkddA1#R0-R5?3T?$%UQ2(M@v%G?!4^>V1;0Tvc+A?>A8M7y9>=Sy z8OC_mjXR@}4nq_zGBjOGv^{^7=8Jr)IUucb#(=)|r+K6?If>4Q@V#ad+Tc1kBb%7h zcR$_s1++O|eKwEJM%ujRE%^x*Sq~X*k!QKc7~XrVmG>QQmhCg5OIdz*M;L0C#~X8{ zE|nWk&>_%69-3~4{mox)@yiaEzC^qSo=epR?OP2o=JWR2>udW>H`7V(XmQFi_ITHE z)AG1^1O1I?nExaqh7BPtkyvfk>$*4Bboa+_%;>7kPnxcv$G+*CMIA?iC!%S!BgI_D z?zdiRxS8jW?HrM=VIiLi9|{$o;mg6o%YEK1Gvq1WmNx5jydLX$jdgBn4WFcdEZD0S z8MQPdHo7*8HT{ zKDE5xeFZ~3+sC$YSTT6|-NTP=sThLZ`e}BNy&!zJ4oarfO~Z!wfbE~pYKt5mc0hfu zu6jb+ii2dF;uA(91C94Bo$hwwEm)kaL(pYg#i8EiQS>-YtRJU%0FeEA8G4i`-pT(Re5=^VAabJTVbd5v~YeD6{cF|1uqCqgPkp&K(3Aauy|e- zmqQ<=MnJdvz;E$T`pqY@r?SNCw-z|TFM9=J^=dkEOunj6uY$t4mkw$I*UI|q-b?4) zRXh^D!YS&KSgX0CE+RzuXSTNK#$)dAlxH*i*LkHqrnJ)+Q(;->2Ll}0A&JdP|@G(WV#kF6@{{bm;Vww&d9S{Ik!v@XtIsrPAj27T@GD!2ZW zZX`YXGO2x2br^giC>%)ZH+hWlbddkFao}5cQi#*B5NKq$k}ijUVA$(UAK0 z-MFw}6;MrzZV~y9;=Xr^YRprX{R18!d>gB^as$yK@m!*+o6 z{EnTU{M+q*`tBhg$9Mj?I@*as>I3u@w8Qo98NPS>B?y z_gJ?Y;$?fuSZv1_cLqU~SYKTaMPHt?hjfVvMT!5BlDA3edPhwJ7Fs717>*% z-zz_}WNIOD9>$)fwRS9DWSmS1DI6-OvJ2mI6$agvcF)v2c+ecD#g2Qy0t!JI&!=1) zAEmxyJ7D4NtlA@{SXJ%eUk!$&VSB{fNcyuocQ*)MRL{J*>7L?7#Hy`t_Fud zTl-qE__3e75>QzsXq!O=F_Xo;rHW#18vSgTHxjLVIvb*84x&wO7s48Hxyp7zKYI}Hyn)&MQ2 zIdr_i<&mVsUzOd?`Iwwg$h{gh7GE9|8MDOAIh(}e80e$edr$^z?Pn9oj()Gu6Tf>V zK6S}iL&)vx`POeT4CBh(JWjropq&lxRs)4<@w-xY-E4|D!^!Wgp7z{~77K!nbAsR# zTG_Vha0t%+uQE=r7wJF_@@h_xWp?g2$3MzWy=0?Pxtje4|77+GK03HFb~@1_B3wlG zi9NO_Es=Lfe~~FsQ@zJ~g5uHeHcW>?UmylWP7K~8VrTf~kP=hUpF?EvRKq>K!td-K zxzO;h<>2)E25n@sWJ@-l}?R_=L&k%}|bYX@vCMxbT)MsdQ;A=6|J4`OT@i2JSBE~Gjs5qNXv zOifR_t)tYMVb|irgS%y`wf}8i%b#EU065uOEkaP`7=7i{7?)0=itJr`%XszFxdLE> zsDw^&`JHha_dJHqsCys-i^$#$t8H4J(f%!~eZj|~bHjb_z=`3^AD4HWK#JU{_t3c5 z!&6Btc(2k3Q=fn}3nv9ghKwm;(dzIlE2u{M!|w}^j?@upk9 z7Bl!V;O+P?kX2jukL(Tp)+&<=fPVu&k))Nr(LMyPo&maLJ`dwD_C(di9;Ks#fG0cS zS~eV>exlLXcAl>z{|j(Ft2`?QiiLI3c&|q`B_)sA6Il#UpQ4=`*5-))BMR!bETAu+ zt11+-WF_m7w%wDy4y`fQe=Kw$+lbMWA;)g?eYxD)YcypWv2XAv9xL)4N+T~*xsJ?n zy3AMhH8g?!sB@{b_ISAP8+k2kaHhq-n2NF≧D4umE#}X3-gRdsy|1ZWP;<6>%S) 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 diff --git a/DashWallet/ca.lproj/Localizable.strings b/DashWallet/ca.lproj/Localizable.strings index ccb3b828462e333d4d94f40f649a2532a9841185..e6b1f861d2b3a8beff73782778fa95c1dd120f45 100644 GIT binary patch literal 74312 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 literal 125188 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 literal 126358 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 P1Ml!eCwIF5rK|ow;LAj* diff --git a/DashWallet/da.lproj/Localizable.strings b/DashWallet/da.lproj/Localizable.strings index 93d9984128af6998e11c812ac0fef20208bbd49e..c816e0d5cd258f2ddbb0f557d4a42f314b70dabb 100644 GIT binary patch literal 74340 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 literal 125226 zcmeI5+mar~ap%8ly@BpVwj`1c3nW2mEo*H{LjWXUkpP4P8B*v5XMzBSFa{t9y^9}U zA8Zw|Z=kE)&0qhK%&h9_{tnai4M-vo0A{-Dkd>9^%&Pu>|M%_HKdlZ{msVffzqjq* zuk6p=)zRvy{r-6M#GZV!da$}<&+gchTdNoLFYoW$(!1y4D52eDtk}OL#>#76 zdY2yzdwgU&%KqInntxY>)B;Me*D(LZAil}aL{bpPpm_?yczAQ z;oaS8j(D2qxHdraWT1>A>-)_>7f9u=jegu7rtgta4fBoXI%>?svpml(YdLFkJ+#%t zZpM?7kNu8~?l?Iv=BVG~xt6p0&aU1!2%H{D2%aztsdu=BA<4h9?k$VR}5zh#n2 z_CD6w-%N5H4KnP$jrnS?nK;Vlw*F@ZKhYNVoQZ2e+liO zw|F~_S+$S{gk=}=fjRaJjAG-lcW5DE3wKw)o7|J#clp&{aW9I&j@B;TH0r(i9oE4; z{1ck*FSZ}++V+9+&|tylCjtlsKn?Ke@j!2i-}&AX3FHZME4nz|j~ISuL6yevqev?Q+*<2z*```Xe{s zlRGv(F#`5LvpuxFTAO6e(dzH^JMsz`9$3o*TNk|LaRh9phxR)dq6P3_FWtAF?wUOM z+J4d+|M&2Q$b|Te**#LB)oRWd|Kafdo7Mk$IbR#nXb-3BH~1In2PCXN)5u&TmR zFH{31iOy~s70CDg*0BDSy<a|q>EMUH&b;5t>G#8dp795Qqb?7Y zdawB+_8l`G8Vz|wd>hJ+UI(k|18pewe>uhe+q2BI&nutjte9WiE;;eU@CWB810fIn zmj`V)HtCn)S#swCdZ0bOI$T{_eQZA=^W4sW``VDN>(dWvBrH0tj9NiM<(pm*+%XIV zPOcjykBr{_zWUOh<~a|o9a`?+O!}2kn*Xq;lCD#kpED-g&@mnj@UbKZ=limnZ2<3FGe~4T zjQJfaZoX|VD6xk!ZuEQ4*<>L{76R$}e2_d?WAgN5C1br}QL$TKP_hC%9jp{T!Sa=N z&QrBv1+rloMNvX5V>}gFmE7K%{i3x}U+h!#tNd1SpIDmMZPJK%w6Y7c*TC$ID=K?D za0YAf+>G1A*o6=6xpGawhKyroqz|}Y#TpAw;LAb!KD9NmZh66yYx1nob@az`1}oqR zj{MgC2Nob9s_HWy+kCR6zpCs{;jqjWwJ`O1pH5?+GI!2fb=t?Ee7xQ#w&$)loonvd zQ+TCa2@`LF-r>vezH9!KcOyE7TH#FE(4V=*Pl|`ZRi&?nc5w-f&*-qVXD>acAIpj} z4J@hI!Jl>{S2ZAeWn(LkEUAM=%h8ZpkL*59|J9Y1DkoXrZX@+y;dR@`XM;9SUB*e) zxa(MXmFEl|=tf%Yu4RQkF!*HEqvwze;Uj!CK#!J_E)NNjscf!Qo_k3S$#Wv5@b>ZI zkaAP7joa-5I`|U|PC*s#dqIN*!k*)EA^n9XC{fnhJ^M-d&Ik4r*2~=?zl@f8FhH;9 zF46}rAnBv{tY;zUm9z@_`8KZMLT9}9(^54t;HRs45b~})$D3TbvPchKoBjS*% z2vVPz>2QcqmhbYcOY)4MAXZN#)bsfAqr+B?>QJ{(@fu4jRF=zl6-$o&>R|yY#skQ9 z(Xs6|+=YY$ue1@aoHNeYlg;vWH;q3yt-j1HSyIVUo-cz^tk4}9Ca zPWNCVYL`9Oe;(SpZ|kEU*=pqPKp*ui=tZ<(UWr$*l{WgEG*;Na z!660Em_$G>+g>Yrf)vo7&^DIa#gz1Zt4=B^nY9_}Jlz5mC@ArJN{7TWb8w z!|UzIIgBiiiSV)&1C^HRgO%yIGKQAa;O+g~WOcOZ1=V$vl~Av8sLI}|n&hE9RWwm~ zX5?x_AU-i!1x1G~f-U^yyNHG&B+BS3dO>u%lw$jxyDtt)o0*mIq}Q&=){t*fTN2V| z&kWl{#K}idH-(j&ZN6Pc$SYb$14QhJp5nVe4T5t2bOL8T1VFj|5`}UXL zYdnqL_k{+l0gZ5A{ym!dMU(=9WE*Nrb3<9Ha0ItsUXl@Cy*7;$ZEWOj-;H=d$L z+<#`zI0^8+{ZGvME2AhXL1;@1{@dXMi0>~@Vkw}OUZX_|A9xe;zVSDwpefsAD0<;4tXMpt8f$I` z6GPA$5@-nDvgMhx`{Xsi)btvA&X_UgG=aU11)r_-sXYqVSTAd7*R)RU-{$=@q|5Ft zl(`M`o3zi+D)vRMYlb0aLLYI8K@zq0wlg=hWE%UFpXa$+RMsAk`O962G4-w-xAqB; z8If6?jtc?p(clFh&QN>1#nKRbZc+#Qm+RKt@osKbvcd4Gs#ZCjt(arx59AdmSJ~x= zC71Fn;*zy*kuARuY>bnRl~?0FvML0^2E~ID-;!rjl|@j^b}$4-*DGVqQ}V9g9C`iA z$qnp8?0TXb|7f+!aJTaG{2cV9b`OmbbVI-|tY-A*Lu=}Ssd7F${NBp~{$N8m>vj&M zuc-y4q5?m$Rq*GQX$>+gK{xy!m{99-o%^@91miCaPNXs!$Qr_fRr$6L{dhzr&lR#_ z9;VzbL+4{+ZG2>We9vSDc8AxUmpdb?WZZ5%cYC~YCzs=_yb4ZjY880Us-6$e+ND`3 zF}1_Mym{}8MIUuL8R@es*MuE}SBam`9>kadJz9|%C-n(L46qDdnB9d<(AtBgM$T(p ze%E}Z;}yi441qu01>4a#uzJrZ20waw{v4Z$))}ULSjltW7`!cfLv+WqW)x+$);d2z z-xZB{3lBH;A*=w0e7qyOjPa_i%Fmf#%d66etTMZ)7(ve9OP2;_9-GE|Vl8#UO?f`Y zrQr5BS{kB^aY6O=ce=CI_VK3P?ffQ>xFoM(eL#Va?XT(_BX^Lq_K}OAwW_#~#Rv8? za+|wQkjU(dvP|Mmzu7`w+r7bSR}P$Bq5#Dd^K8U7uG?68Q=T=XpHAv1n|5cIUr{Hl zL(z(&DceDpsjL;HK>Hwf@*0U!fd$pl5#v)lo3+JwSM0u>OZEgE$2v>x`+5(MgQAeY zSvyj+>>gyU!sZiQyUpiMf-mDq?ztR4GQMXBW7b1^#%V;L<1}AsMb;kUxNd8cJX@Z5 zED75AEb4l`Hfm<~kOfHZ+i3+M-_>7H=g3IH<$tn%&+Pw@DvBz3%@%f`pB9qD2nwlJ zURwl2lc~AEo zgHN6zh-bZLMILi+oz9eC$so0n%DGP9+`!+e_MB_q4_fVYi6ej3d)9-MYUo@j+bPWr9U^Z{;)YB;b-}dQ7d&^qo6+x6s#gX~ioPp1Fov%e4UyZ&x0~KU zf1k0{3mRGOQ?U5ko{+fq&v7k96cmZZt3yWO^Ah#Bo$SY4<)j@x>1cVS9v$)=20It> z+A}gWU#-twmXMaGOj_?UOiR;&<4ZMhobtm`KOXYtWsYqO8CLDM{XU=#e-rHnh2$JL zb{|<+{;et7*SpOKc}4YZPgB7*x|dwHM>Fdgh}lyqLJbsrP_Jm4o_QrpQ&PZj)i56? zKj0d2^t!Exo6$;{ym`OVfPO4trr_;YUn$Ouc{inpy4^nTH;u3U_<^Unfon`*@h0P7 z%xUYYPP7w|aik7toK2i>l_6M`2Z_j!wMjhfIdiT-_=8C*W*tW!mtHS6fhuKdx_dm> z3-PY++Bsu&-|Q0Nng7fFp0)eR&e}cXIVTNOlvPnY0nY7u_gjgdYdSKxlvh(VvTi$5 zT{jVBu&$V^*CFbTTUF>PCnQf3zPmlFTkRQD)%;|1N`QNy_L`+__&Nt`Q( z-b=hYH%)QT-1<&Fb}uZz$#6GxMwQhi`-xjs%1ZpOq&I7yp`EK?=3UHB)&~ht*U!(l znx8BDCT;;Jztfbx>>L}+FkLnY#`#<17ZJ@(Ae`@0!du2!-yY6xWFK?te7Tn--#oX$> z8j6$jH;s}MONTZjFG@ROjJ-|zuY+%MEdBQzVG~aCa*eO)UKuiWP1^sYUsgM3F^A7= zEZxaAjyts8W=KM|v?w^=w+ruHv7ElBg*b8Mm31xKa|ZPW-Lywiwa{j>=C*lW;fR_h zI6HQC?)w?%OqRx-!>T!=4#H2bPUC=i%9fkJ^7e3_h*!a3`yJbDJyT!zsG-A)%5$oU zw0%$1E`9R~mZael$GY9%$;-&*9G#6@W(arLZ?{ciU*kz0`5o@Y!Q z+xtI^{@C!sDfpZPs-Mr#TjnssyDCS~cR}2U-hUmmA<@e5 z75P#n6!7_s&Y>>rqSh?llzgbj82;^7hF?Yuue;?NWDa_DU|NUADS5Wp3WTFw=wND} z){nSuMN^c4-GqfZy-(31&#|PeyVyq>GW z{}+cA=j|(2N2^2oi$3gf&F(eBMnx-WV(< zZVh{3?TH&>WupJtAL2`I+P^xhz%DZ*6=`Gcd8??2K5uLLVXyQxh3rBh#3?x;gg+eC z=JSE7Bw@MnMNtZ3Va(xQ({Vb4?x98ZtL4u?tMIR^3C6!;zqt*Y^B6yL+B{7hXRbJJ z`^R2I1$VY8>UUWxkKg5SYtMCkT@{e%dts6N8hgOg(c)eEXQ!|L1K z&mXU?wmzp?WcYTAUNACxLMM&NJuKhkxt7$ZL^h8G&4xrlAHw^R2Db8=!GkpJ!;7lPk=EKdqV;Nis>6@D|-3xjU>7tC={ZYF%`08CzOA!5ZCXlyu_z9OP}8 z#r;d{cQD0isAJpZa@DT_JQ>>;ZFQWX$H#Vl^q3{QhQxY0&cO?=NbKJr+npp)QKf^*Ju%o2}`Q= zrHEY51caOQ=!3)3%AtGxA|qg<3n$~ufq&~nkwIDJ_|_#1yN!?` zoR95<{n>{1g+X+CEll1j&pSnjo6V?9*g9NjUT>RcUry(jcqya7oN!Y%BJ0}Cd-Q^s zh)dw^Bm3=^Wkt|gJ!d33j;Bqf1AeF58>;!o2c4?nRz1Y_{rj#4pIFgQzkv&0#%5<_ zNV@nYBrA^7gL<29g~|T1uXgG2Gd60uMBN-z0gkx=fcRoaD&DYjyu;6r^I^mds7O=( z4ZWHrxyIb4XNGYT2|-?)xn=9%mkRozZeW;dlV=-aMAbHNi_cVbG!=(X^K=Y;xOuAFiXQV?!5HAgU<)^wzS_=5LAE)=~YTP58 zq=DdIuRY){-B>O4ThC3(uxnsJ*4izTe0bD472y$8qJzXn)h6Pr?CNcM$NV{-O7Hgm zzp9X)#twmfbW(eNc+Nf&!;bSTn`3p?(R*WJn&$8>^ z=ihp#Hko{58+WG*Psa9Kq#TXGZ69zjK4;Vyf-&gGPlC#B8|T?cpzb_Fn^@pvY509}a%!$s$e#ID?ODT0E31NCzQmaAmaW;RT4o5M%{7xex#nT| z8!;bRJFFtbq&>Il_ESi}7(1^iAU)|gc^$Pc7+yqRE+8AXmX#u2&ynxldS}R%lc+>} z&=2^A@Q>~x6DN737T=!SFIoMAp*rb>$+|3ePd3UvR_2ibKmB>t)(V%Gw@#JonD3LJ zuJ_(=8*JaE6LiFU*e8E93`>3mCAZ$?IhUmYptxqR=qv>G!WfeJeTHT#4ls98^dqCF z@(VM0&hfiE-;%4xqmZB0lGEHRam(l(i>r*6Zr86r^q+44Y27A6HKyWCV_zQ`_3}+m z)rY88-pkh8JoA#fsVEop{E_9DuG#-&0jb4v3yu5<-j2Ri!2ZUm19=8`?rv9;tIH>Z z>50UIQ@mBSsXrQIDe;_rLy$3g!&b+tqne4zHeheXpgocSCPJg3r|=AA;kwmuvT=ec zPC8Cr?Q-<=XUry|D3d6 z>s=pgXeMqk0#+i5&>z$$iVT{%Yq@brHSs4kZ^2n79Vf4j-TBf+*9i#V0B?01g6uda zRls(}s&0dp{t)U^|Tjq;A%~m%$#w$Epdn}7ZW=Y?;7lLun;fw;uLDASa zt$IGjeN|!1yVA0|T{deCsup1FvQ@@CN(#97vc6!xM}}cJDWu&;Zp&^^W{49GoOua2 z|I>EwzOCrkd}?;|8~Yg^!YWYAwpS&_Mf1Dd@(|65K10KhLxJWpT}6Ii>fEDSL_zV< zl9l;cMrO}#+I$S>;=5Wa4!t^;@Qj*So72aV9gN8_e>EnFrA^16oM+S!#w+2-e?M~=yVBD zkiQt`V_Win$bgbg`p>%$?9E|}>JwSBoLS*cYw!MxkHXb8KkZ z!U^9NCkHPMo}bg|6~h`QntWzirqfv9Pz7{G>PwZ0@Niws=FTG(k|!peiv zzc#&jX2>OzPsTRzZwr)_DChzBfjh#9{;G|#;0c@$PDxeebFeTRqhdAI2ndA?;=dTes%-e5o8 zHhA#pu{pz^EHSXYb%wOoJDcC%4JNQ%$dKjJI`#8BTUb7>1C+HG`@=}Z>5bgCZdrBM~(YXo%E5vOWQWMk%qU6-~Fzt66rJ^UTsD)DYs`pk2{9(~wTJ&(MMi zbUk#f5nC1fCFY`iId^`Z=lEb4-|dO1vCy;e?c%AC>sEQ5WicPCiRx@-$BM%;!GD~F zGHQtG9vR|R{Nl3h2)jXaZW>qlJ_fHYtM@Xu$+On&4inyWu0;`r>Dd`UZKg4h+vM5% z?NVJyaMdsvC!wYxUDt0v(EriyGnB=T^*0S|-7Z7dqNj*)qaSo5G?F9V0Yy%kIElPY zkA6gKvpg*)xg*;i>o-mw-YKfNFWPwfnQ@TE}m8U-$Z1%C(kWD0+-| zk>8(!en2CRzgX;h6LCjXsJk~x=0{#gR1*5TYnI2mW~0`%1;p`rCf`y~tRs8!bnqHw zp-4mfNOQCJsbuvlZ&5-HbAwXp4+-nv{}s*eaLv!L-r#9?(y8=ayW>o?6%F=7OYooH`mqeuc^NrW`d7BrkxMt}I7+wlaAz9Fng%v3HID;&-zBBgAx z&z4V&AQ@zlh9r;`D+*=p%%(fcGbVi1fd{Qn5`u~-#cR-QK}4Efd$PS&-3h4XFnoF z@rjK`Z9}P1l?f#R9P_MinP;Al67PN^bwKgZECP>KmY#MC=DF4x?rkuJwBZb$ytC}l zw;-5nogrAxI{w5U!v@20`qXfRrIjITz0HtZw4OvYIDd3|=#34UX=gmb8MExNJ=TK3 z+vK^HT{ZNqdmMb<(Nxrq0Y6y@-33kMAD(xKmF;`ym3%hX)5L{kU(4TrV)iT*D<$^i zhj4Nok3x1b{w6gDP!-SJ1E^tCjC-@58NTp8uMKm!k4c5GXqhLvKiIwIPX=jB#%%1> z9vW>Bk-*N+)xO*G$?(lZ<6=zsR2Aj!zqR_Uhi$iUGQ3MtE^7hHHwL>6y@xlneiifW zGqfk${c;}iPV+lI-#)`NuP-v!IF7-jgwEZ5*3fyJ zoAt^g#m>h4%QbJ(KEtytCYb%Q(RrzbwF-aUcXq4*_&K8BSvrhywXS_nXwf5Bg-9Uk zeZq>9I(nGg9(&7pD$FRX?7hkgvJBIkR`74(@QV}N>!T~CX$XZbdF z|2>btoFx0yuu9F){Q zh7zBS=!oh&T`Ev3MnICa7u14M$}us5D_DS7|fM8oP+^8S1z3csS9 z>EPul7~K^euqqCfrA|PHZ=xtgt5@(?J=1)zjJuO)u1^Q~`(VIhhDG)a*ex-(BnP9f zgcI#9@n7~^cAIuob_KCMj|66m6g``{g*Ysudc+Q!pwjx@-EKP zVy0^>D5w2{)qA$ab(FPfj||U}J6%WCBi7z`Mnz*KSBMp63KvY{7X2W?hd{%+?^i-I ze%l`s`Rs=VRjc-xU2>?}Eqg&oUfwkVJ|}0XGPc_9`#^KaWD@>e%pXtkj8hS#xm)CX zB-%Jt5Y6^VxIZ^$oom|%oycS4RA`lRw%(U(AlG->_`++kuu3>@2d9wH(g<23xir;hF|#VV+(rPJr!TFR zhZPk0`95Zc_JFX&#Zz0+-wbLrFrH@n`zm=AWM%M6vD!GF7++m%uX?I|4ToC-)9o{s z>TjmLWjh46xfkzEgO9r52e#s}^7ptn!>slt9QgNnp5^r8C5x<`Gw5=~UY@)4c7Kon z7nFTub~e;58=V_~h(+;4@v%7T6Y-q-jEZ|U)x z=d|1od~VjW_X>2+k>WyL8B6p}-W~jjJSA)8-@`ey7{SN(yuJy7THRa5*X)LJ6LH3j z=UA<1IxITf{7mFQ=NKYKHqL6~XhYEB_XF;{dwi3GxFlGJ^R(HLciO1!zfwAAgmIfZ z*Sffl-}g;yhds`^j`7diek*IHd>K~x7>ahQ49D_viyK&gP};u*)wo4Zh+39#jj48< z3{`E9#EyKuiZg=ujB<4QNnZDKi#)?}J8{`=1&tM6wrC)?yOcWdvS~GCy8rFvPQ`81 z|1oet$Q0-=xc-UlZ`7)&jx(?k<5I6u%BHz-e1?!nR}7a(r1OSR<|Ia@ukwv2NF1BYB51E?v&OBsb_TW+XdU%eUXUw>EdLZpKJ01;$>9e_I0Yn_M;3 zIC0lfU@V?pvG+RVi3gvtHQgq|v?Qju0hL{WdfE}w%Le0AoBmLZGtC*Ec-sdONu=1J z4-8l0!CtiMGSKV7iBh3k@e*axB3-Zsl|Phx3|oyDBlp>Pv{gEJ>}B3FIYNC$_LEOz z?!0fRr_px^bZ5skz!Hm!D>p6m0AZ6ifoBu5RMmL>!!K}y{1ZiJYYs+ zACPHS*Sa+`-;CoC59U5F+g$m3q*0C(Zwp0G;`Ti=rq@fg&|W^1)7Th>c$4R-`({}24dW}5xz;TEXuI^ytKih3*9OWj zeczsl0~`v?!80o94()&Pxl#2|QKq;Jg#B(0Cy&zByFb+`Q^PWGPpvTKyKZtBt8p5lXpx~AW1=4U)3jbR zQz`psopbo=?N9SeOG*;;r}npa#t&$G9D5?_g|ZLcHpyB@M-G2LR@Ieheh+Q*JJ z+m0F8pRBdVGfcJ1^DVhc7l$u^U^!rYJThwy|Crz0BbT2nD~VhJ@>aFVMD-pI?VZMB zJy$GZe#zc1H*&_nm?Nh1I9F^{vRLt#6`|$-8ul``gA~N7s%Oe8Tz1^NzDol?ugl+N zKF_dF)kI{2^L`Ow!M}P|9Zxvr(^0LopJ+?S8(l&5&kGow(x zBf`G3|IjxvWNEB(Y~*zH_Exw0dpL%b8B>>gdmW>g$pZ~f-Cs2kAQ-L|Ag z>87pRN5B)$_nJio4b)!VGpmu%rQ+m_KccbdTcGi=WwG5Jqz7IiI1d`E&qio;#q>Oj zQraq+u78}k$W*nYkoDs|I0u&z2Z@9~i*f<^+VKzWW5eL_=evf%!(72J|V zZ=!plM`EDLcVQh zx8rZEl^prpq{vi{UJ!c@hKS1XORDhMF$bm?K8A>1Z13@&T$CNjOkPrk|9gB`c0l=IqLbNNh>PUiG5r(2;d@B4hJlA zZTdll#m~7we2J(5L<7(vqPbZH97KD_`WoX86K>eqv9syohxmYpJ#=@B7)VSH{$G^gl)O_47P? z&Ee!Cb@mS{m$jlL&>k5-!CB*)P?I`(vp-ehc77dG zW98M9<0T ztuMoukd52z3%W8!ir#;aT;7-aUhTLuH8tb z|9><5f7YH5dB%g$*J2~?yg3e*o3S6phe=mP$X`aI!I$A zZ`r2CK)X%vm3tlbd4rKNR62XyhE2X|-LgM~(;lT`wrr6h+V!*_JO~vK@rJjMDK%E= zw|U+lUkyDNJ6Sz`iQkism3L!V$;Q6dd6B=H{YpfKb0SE$ezOzH*Nr2;v>8337j~V# zGFakr(?0#6CMrlZM+sLv?LTuFj|GE*i06G~zh;sHEgx%+Cw*rmB3b6@l#?7oGTpK_ zg!MOJ-R=y#ffm0qtHJHP4AGmzZG1Wj#O*;?`i|YGK`HNU2(9cb4_0N17Jlj$8Hz)j z6-z;034YeS;fXvf&gy!e<%#iZjs~+zo*^w-aN(t?nowF-_2@15rdnn=my>^iIl*;p zSV89N=@$JUa!l$Q2H0BouV`)8-Dm6>6~k0`YA-zcR7XmFTk-4CWA6_O*rCWc;wor> zWBW^$0>5kb$a+JoL{+3W)JJ>;4(X>XI~e@Po=^=AKN(U_ch&^@e-3P&@8?r{@z#8-OlJb5_3*;Lh4 z+80HDu%qCDEIT&sak>!4`3l+M+pl0e!oF8@W1t&o(qmZC4UywQD#e+DP1d;mnEPHe zaa}mn<8Yg-YTPG7jh+cAhqLoNSx)tTF)HV~gkKCic;0@xyW&~au$zuE1@qk8``~e9 zOG4Jwc=N2{N$73qIr2^}X^WuWshPn7%N`ljQoh*_1n3fdPdc9^tf47e^@9wJC@N@w z2L`yN+GMEe>V%S;uqV(jp>xXk9R+vss8cgeHmtaZyg z_oge1+==Qvrsf;B%d^D_%XQT>Lk)E)Q5;88n8{eHVJqKdc$Vcztfq2qJ?T56OFgfp z$Y)W)LwY>O3M$KFy{axTSK>s@jrf3{g7fdK!!3b(?84C8%E9T(89E|+dsxZ32iJYX zvhoA#(EVSi1B^}8e7yQAd+%jk8GG^m@bg=PU7snSRapiX^37AfzbA8tV}yD&&sF70 zUF|G+ah)lNUD9b*X>Gh4s6m-f?KP)7(IK>;f=lNXq*a+qIp?5@7UWpiSljhiIP+e9 zKOoP0w7guXZ(dgxH|{iRae=79y_j*23~?>T<4ecXOP8lM$JBMzD8Tb`ElA4udEOqF z!b5LCq38RUUFV-48$T+eT62;(qmGY_<(Og(@W-}e)%;`6!fO=frM=9aSU7#@vL+78Rps6*PO)bee{MoWU$+?abKHOBnAPUdbu!pUF^ zjq`c39NYpA8ze{7rgqNbbGwb1*D-G~qE$1V%GjsukotWd{jBNJbvNOZv^swyQQ5X- z6$zR;n)Y62IOZU!VR^qhEc4LR@Cc{)XXGu3!A@03d&`zI8u5F}H3HDR(MA?>Rf!Hf~GG+1!iv zggQsn?5HM^_&xUq^n`_bQ|g-Pa-Ui)GDKLE*-xCx3gWTgpFkz;+d;1lUQN4Ao^eUK zCF=nO!uE+9@Z@26j!pHv?+ho-xChD4zu|A?on#eDjhR<{!6*ctA`X98U{G0O@P@X_ zku4x0tA*XF-u$arfxJleDgtJ3&Hh46>S@TQ6DfIau0g-Ur0)foScM@K+C!6)?2Qjq59U^6> zM>OQmd(U1*qe|W7kc``8sHUoj->&L|vo6xeio)poZ}@FkJlOMC5Sey1X}vr|tz7RI zl4!LI1fpIluvLr2-Ff-ThR`i^Gu3WsDE@5oOTOgq8IGYS+hr)0lq=lHdE3^0Z>VWG zG!AaXsOMVsge*rN#4{fI$9k-9TfAlxvW!Sf_vr;S{Dx$%Wn=lg*X(GyR(!gA-fMRB z1K&CNSs`ok(>(M3qqXuvIAP)(h>go$DNmOr+e`1K{XRppe=2d9z(fA#$2F6XXi{SQdXwQC+rzr^p=G^l9O>%tjLRHj&LZb% zai-Cejgz5Ye$U4eP|*7Geay~TF*5Wm3n)V~ZkM5(vc|IYl}D>jvOC>q<^DF$P(nF1 z?k^9Slga+kH@ld9egsQb>6F|Zsx2h7-w z_(Ei7@Fm)mwQ2i)(0*&soipf&X5Alb+w2ViU%N#wh*ZrQ=Q^PZsx188$Dm>NZLb@i z6*(iS$@}R(8EULOTKkQ9eC35Z0HlAYK*Q3<58%u=R)EKEVn#*NeLhCJJ@g{}#|USv zS9v2ME8`t^Wg%(&rWxW-al>i0iS{$w9A?J1k8SpCEQdU@j?_SDH=h{}bPnLyyotEq z6Mn%aN%PtqtrMR!*6)A__nOtJ=+{R(mHn?W#20c-h+>0JPrt^kwW0K8QX8G85 zS@*l%wVO=KecY~RUI92#??bbPKQ&zIH{G}*KLMLeJm#_~Q|h+8Taa%)cj?QHd1xD( zUXXKxs`Lle1T=)NOP-K84iX!i@TUj-1W&4}ANuq9Z_%Ukouc>1c1{Ptf7#m18n3n$ zUj=wFZStDf zvW@pw1+u&Yt+*jH-#XzUbFDIDREtO|-x?(Sts!a*1&jAtF+R%~Yv=TR-DEgXDm{$VG~Z;E%g>py*0KSocjtobYhCAA(;eR)h*srX zHAPL&)Xv(E=PKLf*?Q7L=GDNkcnwEW7 z(&L=a+1s*|ukfXgO}aVOXlavkMO6OUeQBCT;fz zC8vhSAA0O{>TGih>UDklt>U4h4c>%?p$-v`tldl{e6FE90#TEUEda(;hXQ#4`R39%bkp;dFH9Ayy-I%H%w1M zOMJ0_Z_2B}pgfN^*Cx-tB;5d2_D}@_Px{VCguVW9s?VL@^@Jks|HDU9KBd%AU}vgT zhOFg7wNH9`!rXwo_6ZptLt~Sp=G#ZTsahXAdP*1&jya#tWPgI2Gp|8u9*easvgYLym_DA zQ0I!Jz$Q=g%t(|k4cDrFB9e&pggndlP7*)$`yj*4(WrQ=AKUKUH?OY7MENeyyQJ*# z?+6?klsV4)nL(x66MvH-h_^`EN4AQnHPD$Ya+B^`Z_+xi-e;a3yWEE!_&{sm`$XQl zv`2D{N~}-4#w|1MdO)j+{A05~<9l|{B6ZT#EkCn2WGIDE{M%B7ImUMzr}yfv4jh#; zs8`ZDTa>e|M=yv~RzA18j4p`2^A56JW#h-sMOeIIB(AM60^^Jjxcj!mFk zE7tq?96_!%_uRc_=c%ZIvei@d#AMvR`A&XC27wwMHodI|Xrl&ZO8U&V$`CB4r^-218Sb&+>=&Ocb8(Mf_x`3)kDv9_~6M2EDyU4^#yNs}(6+!9f zG(1rG*-`Bll_I5Fy*jjc&$za!DC_loNUNw>?!4 zDc=wq?a|9UhzboXEV3``jBF)wC_AD`OZ0P@)18Jzwce6=Iw{DvEZJFU8pctTGQ0o8 z-Z3Y8Pi4F+exPV7K#7gY9*LShk6rpbQ?mG=j7I|%$?_zU4K{)EPcMIl2eEVY73LUG z&_J7tin&G?&ZnjWlbzf@7g>CzuN&GeL7 zIsXLLg{>ngJAR*`p-uL%Q5rwJl3)r=Fwm&AxX;F^YdP_BOQ)R3b>qTdo)=} z#Hj=JfEbbYB`Tp)iDjfHOLEC9Dom)P`NCwM%ORJTB~Kk$OKi_LAMz`ch02E^7u`p` zX+NXMkkYbJ(HT@(mXhqpvEH840(RMRq9$>hR;FChK)cocA^TmyRnxH1E<+bKE;Ojh zt&kaC43tJjjT&dg>{+o#oW%*yDY$AyjgD6}WYF%(N6c$jQch{bT5HyN&Y@x7aMmry zVZ_l-o9jL4c)eD8(e9)2`V#Cb#eMXppG@oX?Rr6{b3+*pf6{lxse7tFH!D&nnX;!) z^3!s(hy@IP<$CXy2NC$$yhp}gNC03*0(cGm_|4Q!USI}T-JcGa#&725j%--nQ~H%z z@Ey>Waoz$8KUOav1HDKKY5|Z~Ia1@EOlyVDt$kQTrCu#p8FTI%qFr%_+>WYPY!dmy z)B82uBg3#9)&I$`pEEB0d#~BpIj0WY*(gs}noVcr;(mo1cR6lUR{F`SU2^2!7?8z- zckR?H>*sjL8tJ5X_R!-D0sNM&;&wzOb5dYuAG4ko=8#Cz7f{pqyiKN_Xn_PKD@W{t z_{fJTD#9sTEPn2(^}5uMLh?~s^Y{%yl8F)H0sGi(GlJneI8?qc!-`538xp4 z6nczL>Tb8sKVBU5rAps%KMSFnZogFtjTripQ%j1Y=^Uf`o8?(~bS(ZUG0c1$sPl6F z8DAZYT{j$*r#k!NY}Yw6*3KE+SVVCzp!<5hi&Vcu3&?^}6FMaki+Hg5>mYwr!;~$t zu&m0NT2i6sTzSLMl>)!Hs^3FY*0s>;Qd7TX!ae+^nk|%jkFgR#n;L5prxu zRUcH&Xm^n4;$5UN72UGw`0Y*+74jYaKEb}PU4$pdvD~vi$Vl`dR$rNukUgZ^6`e=I zavtONB%}6SIrj}hC*1nMI?>~<^ZCi|hhYV9G-m_UMmWg5+Sc9}FZ-A?W3A=e)zS{% zP<(_nr3zHImY?BcmuA7ko33`bk+)q_*mwbh)}p=p@}&-lh&FeUl6h>&=#!0B!d_~G z@bsN?WLMrb?!&SiQ&9bWyU``1b3VoAjW>D3<+X<9fwngFF12HA&+=rW<&|#2DP_JQ zbcpVo^*e2~bE>C}+s2)XHszh3bLDmS*x?*A&RA+omzGA!mbueOYlq~0jZZJMxOf_) z`-Q77_VH*`NkP;~6|W%!g=?=p^%zG)F&x+ASJ_2nyUS=GRSnd;s&YBB!Y$K~x_z5oIM?~-gj(N z_AC4i>_d&xmghbSwLG$bbq^CcaNl}|UGKAZTPQGHT3c=p z$DU&uW}hM6*L}e&ymQ&23b=X7`*GqUOSG01QhHR#hE}UO`zEfxZTMHEwKQrzYbAO-`FK6y zN54UtqC>Cj(VBD*sc2J?o&4R4T~@dKZ0)tfVMzU22W^V$=f*p5R_vbSdbW&YOG*C+ zmq7(yElb7H!N^iN9T?hp!aLXwPx%~M+?h(uT+uZg1aw-w--|1v)EGulhyWqHUlPM7u}*?-rhx|!r)bz`uk^Bo70pHq^iwuhG) zGq1WOjr9;$RTQBWy=Em#jGEWdV+XOI$%nbLb2^tMzzUXTkYCRE zld#qUW1d}+rRDx9*7)OryL6L0vKalznAH8LN)Ky0PCGy;oF`*`Y#;xM4CnpuT;Wgk z%wxAt8qtJI=}KEnXA85)!NQwAvc``vG_F-76!CwJ+q_ejGJNE4Fm` z$;RuwTIslKttk71H;?uSdl>D~y6a}_Ta+3wFm}mk@u^WG8On!ET%9H~r-f-*Q{Y*C z^*->q+?>6!R?9VL68WjSh~&%SBR?NJRM|QaeG)xodks4Y&rUuL(d>M?jI6B}My39} j7Fm8}uRLBYA4L1e^;~0#6`W&&e%ol+W1zTfh0(xC{k>K6s=^sNgxk^EF>~9`9TQ0 znu~dcHCwaU-J7yC+ZWJPT`k|FpJac3caJ!65(zRRrJkOt?kXlS&+&-x@bGWOoaylOIk3ITmuxKaqqtoT$eLFiFfB5TcIXPMX`s;RfK5wSS z&6|1aFYi8i5RYy&xzE3CP6zX5aCFpOESUY(bU9;9lf~d@(ahTaylm$So0L7cXof8- zYo6(Wv&CR?JR6*}&0uoUP7Dt*G_#pa2IsB8o(ny|Z3e4=4FxfSo1f|pKbke`W^mGi zuhG%jqH^t{c{7&cu?FWB7T^^l&eBtSlYlGkY`p&(tn}?@^8N}1h z=Dxk-;hoRN!{TdS)!pCehkN{Q`IDcKjVSlayLZ0%s(Cq^9<}rNXmZlL99-!n8hHQl z2XHt$Y)6Z>`EhVM?oLs~0BQr6{I%*B_)y(Ck2I)5TsP#( zNc}~#>zdD2EB?k-JeYhT#&a?P(5zVoBk?8M6A#*nF-GGrJ&gNXvr#+Se*9N4vXGv* zSTVT&tRdQ_oJiH-zuTO`Wew`x=3ud$4BOxxdp}={r+^lY?>CP}<9v+nHs_;p8}B@C z_1VwL!Rd5{xM(1^`Di+kEI1#5A(txAeDdk9ZB}!C9ZyYK91Va6xz9JE>33yl!(jeY-szL zA6ztNh}`BabcXPoqXNvDx1*MSPUE=4Sm(F(`|)&qk_huRo6QbGFA)<`A;zOKs5HzqQtGyde{vjDyrESfpTjQ0-PhUt$MaPBcu{f{@VZvDy}F+0uk55FM)(TAGh z(vBOFvYp&&hUiac8vJbGNV-hOdB1e-91jg9QXJrDkhw8!eg#4^pTaSjSIsU?gn_}y zKr5RxpMUF%nxJ;I%SAW4_^|@=F8lj#gz|GSn9q--r;1csw%gRB`QW%cSq?z4X^NL< zPVHi+c{0^@%-e;e73$=KhnKTS2L#T<15zwuPVJ6OcaNDrX=jKlXpn&}M}R$SCm()U zj7}EK^1RtUh16^al#K=z@5A5Fi6=XOz)5p}A_f$m1CPKnd_#`><=;INV39A-j-k-^ z%j1C_-U)2?)$!ft`2Y~JiQdwp=gr-(zy8|AFWY>?P9Ym-gRw2K9iS@}Z-A+zC4?|p zjIW^0OV9+?1{Y`!Kab9Nk*`sAFQ9;{b};L(P{8QcUyMASYjI=g+DMD$FFJhXz#PR7 ze?{u@^?THv27_T}Bh9AGpIC2N?M7#E_i(y&E$YL6J8Wk)WNEK9=Psx6A2rlxc8F2O z?%8`x5=q)54p#Qc9&KKm1@ygHK>ps72hCukcO~Q5OgEs=H2&qk{6Fg;G&?(W__K-4 zn@76`PdV^nN@LgA@;7jb>r>opbB-3HcS#4_J+nV5^}XGpGx(>Y38&%1-#9F>$NbX# z$E0%orv`#A%|Xy$8oi!^idTNs@kRr09>s$7cT%A2u)Jy);#SX>izUbB^=vSiVV3^lsR&5EOQRt5|S*VVzbQv6NBbOd)Yj6i`mHMMZ08--rRdw-WaqY|1rwu1U~c+ z7@VkpjwNlu`d@3#+Hg3-o^g`5k5!_3cld5FIfB0(v@pFyl6$jD*;}Zj@Mh#AI>M;R z8rj8~4*p@EY8Ese`(ltu^D4GA9w)1AjuhfAbOvPA9EZ+K99$BuL3zV1BX0-a@XTm} z@5gvZN6X#o`}(!rXzgN z=c&Q)FX$06ZRe78=sGw-j!vg|Oq#{uXP{gl*!1H33gLC`Vr5rWVRu~I0%hLwW_LM1 z86alta7ei7gq%nd(TVYc31gY+)aZF6&t!0_b92}(FzwNSkob>S(SvqAl8-8>H6f}w z&L~CD{7j0-V6s3ZTj1V0y(PT0&F)0Hg-dA(vS+Ndo%mu|;IKV!-oX||r{|p7%MX7& zMLR;&FF04T&LKAU)$-`<{c_T3f{9J92nzO!c~9oY9CEQGZQ${2dS3YA>`&XB(0&pX zAgkG#bBN$_v^X_I^RUHP6e@uqA%|AHUTlHk(?-^s#E;-MHbGM|!kaW_&8`ZCub}zx zH)O96%R5>=D6$>$fp`79HiJVjgx{+jFVf;SV=r=eg_kM0yg>ayC~$U+e#EggIzBds zSGb#OcUBcGW1`Sg<%whbaI$SOf%NEaJcI1Zk^>aq&c|?&pWS2*52qthbbIH)5}7FP zja>oPr{wdWVd=9I146Nh(@d}vRWSW)5= zI9J6R>e#sKlErd_g5n(WSkh}Wz6OsrCtk4i2O~ zJ(YuPZzd%k1`?;lxJpM zJ~{)~eDJaP6gwHVAj9oSd$QB4F}?>7#&kA%k0p*gb9-8|X0F}Dd$Z2$Y9e#_oH1m$ z9k%cB$|hf3bN1nv2{I=e+HT#+VrJ*X;79yZWhL+SmwjpOfaF4mbK{if23YfQdB{#R z&)O>%{#wFXxIh^|DK_)>tZgqOOpeYZ&W&hYoZoWUuE!NnC|)_oz2Z;m%!gzs_xIfW z3dlPdu^)apCYE60lD~Xjx0z6r&-9L&wup{T zA;VY6Ke4Ji52e_Ro9{(;js9Z?0ly>*ZsyDQaEWA>IAzUx+Qgrcs#C-RQv-P3)If8S z;5%Bnu7J|dIpi&A{u49un!v++k@h&B%k>l6cv2?(-PZWz)Jpva>3Rs2$VGE)ZktKj z0%bn(!3aJk+IOs^m2L*@^>aKdgBhEMLUlH0D;H?mu9$o`4F+@2&g8y9(ZT-$(N58d z6o1qKJ&?z$naFFgyIf4=k(e~c%ZX`w<~Bb>2)46Z(9J}Om^@YN6Y3ks5a$Z2lSSGF zXk>`7d8N|v`gvFz`cT6_VK|(L#6vlj%gMXJXspPLv*mc;c=VoSh;QbDAk*t`kh~bH zXwv!?_%w}{5?gcmAPmh$bD^bjuG8kb=}GdT1foYcuh~c;g%ORP<*V|E`I}=IiI`l)Zlb~2Jt5ewhPBx!uIhNxT)0;SgFQb3D&Yhsy zHp`1-`xPs*>A+=5C%msSd6Cil$BJF12|MdZ*xQJL9t?)bSDqaGHpb)jbc~5UIEFwr zaOtBO!o9#&JGpO$RHzNUoa?li1WYgGA&B_Crz<2fzMq~=dhn zqB%KvW{Gj*+i!6j+0yl@*;x21WQzub9|q&m&{ehBa@^j>#$8;p-sT%K8#yaS{rGnJ zT`_JMmio7Q#D@wHpDX6=4_Vy1c&tDDX3yOFFTusaPeDL1Ln5qf4VKpHEdIw!<1%hs zB)_>7=k75d$H0W3Xuiire>cL1WxII8Kt`nrjO?|1IBp!&P75}6Y^XWJ(mriOw1?v< zhvwgXjto0)J`ejQzPtOoq4@|%AV^yLMlc`L!9?-+JJg>;Y&gTt(yf4JSna=_#RBvn zu(-o%Wia{T2Rs}CAN07{sxqj~u3UxZzxe)3pBgkzM#qchp+Y&aw(>h8Tn?wi)@X7z zkVDVrRmXR1pc4&rq=C$b(y75dN8_q1L&-$&=RQfX8Uh-}T?ZliTRzSJMH~kt-2Lhz zY%3p7M*^;)$EA7NKr+B>BEZsj&+H{RpNizGjW{r9m@o3=E z$G#Fi9`j9_P#5RnM#V|Cf!yunzutCnw%xYpO_xg)&zW%$4Pkikjp%PW zT6XMuE$!bpmU&en0-O%YXB)VOBUx@2OLS>`G|oMJN}7UkuHd3ilvvgxWpn~8*823r zF9006RXUKX-<>WBjH4fzzf>rj@5C}VZFh9c(=oF@IxI}?q$6KJiyJ!tXwp-8^En< zQ#y)1;uvQFPJojsM#{vu|KI0nV+QpR1BWG8D7~4G#At?!e5PT|SP}z1O-vXflmwRF^H8D8 zVfx@=CG5-!Wce`J+ZH`$2w-7QH>zLKkaAy&DeW ztHPEH!(GOfd^BLpj1AI4<>$ieI)xj%87530l+QCaxBurH6sC1DXa6)G(#QN5dGr*f z{ERRHo+h62A67LAB6p`UN3-*PS2S_BgPv#;Ib1FlqIQ`S#t!h%3{ZL(%@+?(WzJ^* zn{$Uhe&L&HzuCro_^%QPRflsuWP=tCygA0Z{JtOlYQU=;0RbHXHHbP@0j7p=K0L)x z^xZ~wPKZ*oZw3&4bcmlqJ8RUggWXNvpqJ!gQL^&sQvJNXH|7W{Ahb|of-`ORo+b>j&1u( zJP8EyjSq#6?>J3=I8qZEYhGVY74U%#$ob^)ko@Dl$GavOdJr0YD;9hcN9+yWJdU_= z2KuBu6c?)6pXuayH1iZlqMHj+E3_duP2di3e?1?rQpbgV^X2E?er2mEz}RK+w=B=55LSLlsPzOlMjEp=s1+j8&kNPL}kHcnpm~0*+2~+{6L%! z`JXp@Q#3dT61#{n-%qw`28Pcl|4_G6#+9Ywk$vu2=Xii;tT^LD!SW-UeKWjQIR(anPGuVsjfKQJIvsHILaNPiu^I<-J-%?obD2e_<2% zEM1bUi0crUsoc^Fyp=YWXH!1bxy&Cv$TS^9no2OMFQC<)aZ z%UIT4BZKf&+ESM(oheXp^<%k%+5F~Cb4=zBhHvv0E~_AuEAk0AKf$oe6!2QCr0It0 zDGnz)-872{PDlb=x5(FSG0$jCzZj6&=fO{rhLx+kN@suj}nShjnT|aah?0cN_ zf_nP})+Jd`XF9W5tgi?M5hA9nS=nhU?bVgu<`9w7-fE_Y3SGK$fVAWo%s8#d3!bYC zhCNg;TD2}DePtWOCZ9t5y?G90ET6PtR@K)N<>BUdvoFXjyY8*;BgQm;nJBV*tIL=2 za6p*Mg|{TX*spSvZ(cEW+7Pq9zjrn(z_DQ|Z=i~mr$EmHe|N_;{3PD+Tg@pfL5wm_ zx(>ehZ5!~)I4vS#N;pmK{;l#@t;`G?JC|FLsFyNKk3Ij?&(%f``ilBFc=2KxyOBVi zZkKl(&J!2~1r zXZE-GOp`_;yj#Yj8A*8;N@Qn}amRD{<_i^M^mZ&&QI}d-29Ook6$)^JF%K?$4`+i* z%-Hb=u9DE1U=yuZhLU2h@%t*gwNv_+|LgxWwpAR_Ze}Y#I>60TzJni>gPis8?$2UY z9g4jWrP+P?XAVac2tD%p5KP+c`{HH8vh7dtGLGT(Z!5p&^KW-zn{F;`dl7%o>yV6JsvDk&Abfm=<1dS z4vq*Uqf@A`EEQSVu}5B{h~}-v3UU2OQ}epAtKy(ra$A|T^{6F%(522U$?)Tr4B?n@ zM$C2+Muvt{Et~!3Z8JFa^X5wpcvu<87cs-hM{jKhs%rU;M z%v680_rw0fz2;!=;hR_cum9XU{QmjNSKmM1KiGTp(`1)YoG<=Dsv(J>gR?JG%V{aW z5l>a6jwc!L!xxkRw^zt3@JR~Mp!rg&8#{n{kvH%wSEQfpz54K9U+*34KYY6P>gnG8 z>*iSu`}+0%-mBL?P5y*+-F)$^_5$kmP;co;c$MRX;wyrTz8Y&Nt zl8Nn-fj*74X zp-qs3EQ0wc;-oa#e`Rbto(X7NT+BB^bd#b+mIZ8q(|M9v%X6>7vJkYXEo`BZh2t@8 zJm&?G<|%;unrep37t`R(NIU6en2N+2Z?U4x6yJMA6rHXUDdjCG5!bTL9+6Jw?(wW> z^o*nYqMJtxD31L7Af*~7d^b?xhmQ#432{>3CZ4{TdNG<)>VMU|kRIX5&&3x`>K{?q zP;7IWEeaaNTayl3n@wP9zBpXjV$bs;{?%vA}RRstp_X$?Y86lMoqXI zVDS0NfB7%}2dZN#q*j1J{R5=ldHqB(v%pDnu5&6i^S;e}cX@nb#p=qq$N(FfSY?It zg>`!gcbz3}s3_5nL`uCuSe>?1D&-{7Y~g9T?as+g^A{?I$cgy&9EQ4s=(N$_?>yL1 z&hxp#v9oiSYxjEvL)=qBedmlo9FrM>O085IJd|P8qpL>#5iHo8nR0kW_61pLXx{&M zNhu#Z4a!i;Hoq610MFS8#*H&mE>c^?zv1wXq%XgdDM(fo{^!wgWa-aI1G=CTs~vvT z>xXKMlI4qv8a1`?)^WoCt546-ArAx86{Vz6a`AP#Z?sbAYeB){(w@ z1)=#Iz2ec5f@y4^q;7aDhx)@WR`-Z5>M9e`lPh3d zKo;pEzL*~Qc$S281FXl|m1=}C8DM)T?2cD4ytGzAw%CtV75s_`n?9Y;0k5T+**8f6 z3v|DUaz{LU&&#J-RUH8fEjG%N+29@8{H(D9ky_*V&fM@o%}JTfrcM@Ki%~U6y-QVz z>J>;5u*uMBreic8`Mq|E;kHO-u1^-kkIozU#i*K4rBo&D{T!zE1Jt7~U175m>dhEz z)3I8up+ObyywjQ=$oO1?lcG^u3ByEC-8`_+r6qKwr|yOB8fkDmb6IK)=-|HY+O|pR zo7?-3n(yT74p5o7i040s&wEVOJsZS(k&1W*lj%U_Rusj_DX#7h{M!#4Dx9ih=@g$! z7Ric1V1lg}?`m>nb3EX~>1;YNdJ=F2rGbgDt5{^ry42p+KtfmA1eoZz4x@&iOVyhr zb#?Gu1rJZk8~Y|QkF5(z74*no+ahfZU!uy&X_&%{$S;%|w+!(isL{oYh^Zc&SV(?}ib*WBJpy-cYlOiCDX@ceLS9mRFRE!?-=0 z9+m#2eLrQ#6a6GYy<>}=98Z1UTXKnUxOkCAlT!Nq48@WG%0{o0bj;5_{Ixb)`*FLV z%Pcgxe-17Bk?MctEaWQ?EDq(|GQGiJnK_gxVt(T4Ea5FDN3G|dGqFmMup&1X;u<3_aZs0v>9R{i3SV_r1D>6q_qQnqX6+rHw?40033W^^LYyIY^3Lpnj~R zEmRdd+puXV7Lp;U$9tX0zM-0dqiY8EA+Zr`3mH78bW_13H@l~_cSeyA^P2;$kd-TA zU!HoC<%_7*QZcgAdAVT1Vd+dmN<^7OzN^I`|l}KP89lK$j(fsBcqp=0{n~$?U@~q=Wauc6qpDWSAbn zB2Aq%XDYqDZs|`i>^W35ENSZ2TwodWrQ!`0$AAU`9aacMWY?;7)Bw!5`8i8gf>sUb zSpAU96K|iOQU{#;r~_DcRax53&%=znDl zT8*qSb;fquV|GkK7s~GzU*@;V?;N}K%|W;hdnL(_)u}7W6=88kmgW2M-u%FfQbl7b z6mq8Fvx!M2!D=GMW^B;%*Ra`K*Q`bU>JsKB_`hay<>8lcHUGj@c>+bzIH%L|S9`y7 z@twl;R7LbLyfl`FWQ)AAMsc<+*|&E=<52n4zko= z`hB`{QMFH}S3q6E`MjZSK4qj}9%5}d+MiI>2%9Y(=5P5~eHZJ8(3=Txf`Ym{x8}wx z=Vop0*OTe=1ZOI`sxP_wvj+WY6WBa?d7$RF#?0u8s95DCZd!~Pf+v?If>IKZdJ^N4 zVar@=crrR9Y=$ZG@<4=5*B)XbXbLL-^o~=Q8|V^UzL6ZmNm9L_N)2UVLi1e7=hTTR zmY+xh`KO+Z*1F5BWCXAgjc4-N3Hih`h9fnn)>Jq*+#@k-kr~*xbm5q%8#T<}t-a6U zsVaSQT7XgnXH#!fd_;hQUX|2Q+H}K!Rew4)2=Xtj-LXyYDORAVUGJ!-^~*j0#3xJ3 zaGd3sB~n$sa&sZW!IPvEePJpITAaGVQJ*4 zD$94|lPA53RF8Lb!IH3iN|k^EogST5O%#hhwa@**JK9#g&dC~*6o8N^c&F=IPjC3G z290i+aE7p@`rHxUFfoYeyU_0iAmph9UR~3{r!SwsXugMo3{+)E)3k7dz&btRdC0g~eMW%ICtR!f!A(Pvnn+cs(ZL>bi?QIwN)Ox&07J?SzuZ zmNltQZ!aL6(KIL4N3gGZ<2~%U3h$RQlnq(ALt@hzuKZLD#u$NfX(&vFZcPs$p!2aAM5%IG=x$}UwKfI=U2OUXjO_D*C9CCxRrU+8cm@CpU{ zPYP2an2PaTz`eDOY}P)n>{?)5b}bm5ICAV7T9HhVp}F_eW8cU?tr9rsXnkgMX4^WH zc5yLV5#*wh%-IlHam$ms8{G^fD=C^N^!nIpEMY+TlE$z%Vk2INHC8;D=q$TUk5cy}Sa}SI{I~(F*?O|bMK0p8fX;#3=u`vGQ<5BVj9?X7zfHG8>v?-(=gsqw}U5> zRN%I+j|QRY;LE{^hh14LAWSMCCthu;_{(i5kjhnSWZ>aJ^&?_l*_xMXw%8{0=GAwa zOJq!Ye-(4K_q%?T)7kiy+R4mGDk$%cbLOaNUi+mAbWW`^fCp2!VTu(QMQzbdaR1z> z+2#vjB7F^Z{H;`|l*uSz1RpB!U!W^Uf+sZ2~l&(IW8tp4s^>-SxM_ViQ2O* z+zUGFuQAMx z)-mu^c%LOpYj1xh|5BLoL-6Brj_i4Ft!K?M$U9hNGPLlBWMtj$LNyi^k`y_}*pkqB z$t8PpF=%=kVcn_J6F=GrfiqLjG%1UU7RqhYN23M2tocs>L>71+w$!D_ue7@)RHmqU z`tQ~E_E6H!QG4PHQ0RMvWO>eojAsy;iQaynFy_fdsx_UVk}E4Dg5@^uGEs1ssDCLo z1D)&5qKd3fXTHA9B-Xn&Idd!h^nmD`ubH;q4`-C%SS#rC2U%Yo6$zZysPZ z6AUZTKR1en>{B!xxs-e9I)FHz>*Bim6AlMC0q@x^=S%U0k_1>o)zO~Jm&at4(SUa0 zI+8)m9iUQW@*DzDtFEekfcquzP=d-Xs~`K_xoSG@YkebDC_M1McKYnExo|eYZ)X*H z7f*1FlV;L!UxBjfP<%r-G=1A@kJ(~_KyGT_S8=+Y%&)_-UN=QW?W$E(U4WJ5h<{=# zbxBdYfK1Ywnq~IX6Q7}dZvfs*PCoo?+@+@mZq49L?|Xc>|2T)*WI@uI;!#ic&!>UiP@5?{Gg5T54N1oM880_>vzp_lNN?b(42ZN@? zd;f<3(tXkVyKmBNq!tkJrvIv)yngJFC$3)po#8Rg?~}3NU@W6;Rs86;7`X-QD-KV2cIUFO<&eNJq6x zL{0h&J{RNV#)is7ZJXx?&sNVpoX~bZRMeIiZfP3DlOT=U_uLi4Op#UY9H)VH}RBhUjCJs=uc2e!A*qJ+v88X z=9B6~oyj=s4oSwjRjE)fs9R&@{4&6iKPHmK^!8-8p`&JxN;g%aNn>re6jkpHX@xq& zt3o4`6(pFvG?Rga4EB@Kl7(9~Ji`cCA{H_O`Y=-9Gq9 zH;8H<)VefX^~5oLevo4)G}FZi(co011mc#wO^CJYuPB|8-_kt>)JJsq)a56j5tN#c z#M@@p4sbM(O1~l+{hAurW2_w2Tw^`TEodsRDUFf*?0rjl$lmdWdm1?3k6_h=0uaY3 z<00*Yt=lMruJxe(uCBG!M!PAP)Keo(PZ5CaDWIf>?c30%Wg@TCX{T)ysbCWV+G&XC z1`!zmOEXAN749s-8bZqYZb33@3e~x=j5f1wpi5iuyv6*p3t~6|W)u%-HFM52l$e$* zEEO8s0dZX&6WERS0d-b>`7HKn>8QV@>@31OcCI{!##-zel^`2gzol*9EU-yv=yXX< zYPht%o0GlvM{nNC?KpUK{VdNc*#)1O?wJ;isLaTUSX3CYJR#CqCJ53}rKJ+$r`;BcXfdO=6znh&2Qxe+v`p;MUEC|hJs5eosG|I%cLz466;$U7@Y}cL$zbUhGuixc zl^kIa0<=_>Sl)~rD<0%)qU6#-oj56ou_}YLES64(>VF2>yIV_p;%a5Kkp~L@hajB0 z8xRiRkVH6sS@TE9rbgNL6RgU#7yD451n7C=Z7GGK_eCwBy%g-Ux$JUTU-|+E-e_E3 zXKJ%~5%o3skBVvG?rrwn;wl`yD&qXnJvMkC>oQk>crh5c=c5C8G04~a>=or1X8nC< z48||Eo~KfS@8}4J$W6V*R|*ee?^oPgy&jr-E7x?`C}$HnY|8BWUFA-Y>tq)xDILPo zOk#lbb0uv+5~HXw@Y&AkY^q;q7h=^175ng=Wd}b`ftly(fzIugb{%%iMOo=lnCxiF zuc#S^?DpPUU5Ad1SRE@MF5L)0N@Av)KHWel9af`x1OA|ud&#oW)S~Ddf1W((x{69Z zRFwoEp9rg}whsfa=6GswlXt1>R$pQMiw>c9n<*J#wV~0u5x@`DdrJ=>bV=RCFE^ha zDfRM@i)4^fj-G%0QGCOHkmt>uK-~2WbxCbwY}8Kt%9G_ecZY&6r1>~xW9H@7b#`T$h0v#!*wU~K- z3~6aA$XH=mP8yn>Q9tPj83{L`^AnRYIC)P>fR9kBx-I-Q;MXkNfXDYYfi|D`76D2% z7#cSZt?Sp3yQ;pMo^ee88b$pLFb=It6m$o7?jvT9EedvwsUl0!UEM{$g;FNmP-&w< z6lOEV`F#%2Zbqy`@1oPj6RvPZ<V7N!STubIhCS+z4@G=Zd?{G zkQJ$1ouH$Sr%iqrn&}HHOd2WI(BAXG1XqD&9ly+3$2M7I5%0~$gXJL^tinFFKH=&u zfWMBb9+Y`#pnEqxq>CO_M|eKCFf8-0z4S~4wRNMbqlqw~**`+7SS7Oc&RX5W!mp&WP=%P*b}ejc4K&%ZF+JP6@uBLse_ zo6QxzTM0xrmYxr~zg-{u_~Cj^n=Kjun&NLwm#YSka?FT5XffMdTAL~Xts+5za4Sw_ z)77?8>D8Tykc}+N1@-;RtvVK#mQm%G>DftD%T|XY+t{hMPcf588jB^-SA#mhUx&5| z{l_sD{PN2Ref>H7+{}U>r+BT5Pvg2qtn>fJN?ehFL;m!!Zsa(Q?hMs0RZhqY#*N5+ z=n@U^{??(&(-uhn8RDgNR2AUlKkkS9*O^eysgsDNjxwI)2935sW4~*e;D}pSekL}5 zyap#%Mn1W{^K-c>?fOh2DOFM+_dl}_K7~@X?3XU-q5G~#?TR3Nv^Dy-mR4o4ogZUY zHiGk`;Owrz`O)C)rrlLSl3%a+!amy5`wCsm8mr5R2eZYou2Ig57DxIi6&ru_Yi02y zeE5a56L%LsrA&j1mMZq34BPVAE)Lk@edPf9>H|i~b4#$ijaTz#LXR20&)V313}1fV z0~6iQP#ee!E3UUrow&Yg)FS@NUOz5xx5f7XX_@9=twgQQfC^o>XIkNUXdg`#HO#MJ ze)0Gr8zQ6K-+ug%l_;xxJ0SRT?Ct}EpWhCEifg$fNv^>}81)$@#Oloqgps^^q62?i&f{Rgg@N?NB8nym(D{tm&oqRjH z>wCO#-Pw~h0$eR=uLMNAmK;Odl(pc#pUoy}`0%!LO+Q8PA|f7vH>eaFAv@xxN1~miuuv{)zHjJSVy_;90s%BjiFa$2igPepO6(pyRdCC)hKb%|Eut29 z!aeOJ22H+O)g@uJ?cCq_0_TAYIy@zK5{^zk{8d+pchyk@HqroR1jfmDqX=`EIwmNP zXvUw%KfJ<3C)`R%++2(%2}yUeZYS``(#^f(C|GAux_$wDU3H*pt zLaa;0Fp$G9;!uuJywp?rq>`b4L_DHOaq=mt1jT~%W=o=0)YY{?a$R3VgV^(wla80q zcaL>Vch$e?*qS=oYw^Xpj!GMyr|+K`82)h@|DO9toBt@jI6-;%PeD{x$udiovIGeX z$@VOrsGaco&t#e-1V*UVsZqMan*eRY9!!ami2p&Mr6dTL|xEX zBrZZvBJndw#q%ck0t_1C*2MK?!6S<_Q2*$Qw%b8s;hmeTNVtyob_*A4d}aR1j5E7s zBwcL~xaoaYjF(&2)SUS1C6Mwkwj)m8mZpF7sN)~s_(Wz|&Z$|z%1wxtMz6emx%h2K zKTMTJJXp$bLrPb64EX+1DF>x~j~AZ~WGt$$*>6V51}!^BGN|obBQNC|af51bIf7A=#&Kt)=Ex_0kQ;FE3Q)sx9n+Zx%k^B)c@0 zS+l13NbdE%vA5)hwx82p*RY)oj;6e>XbIeR3i4x$orzL@6M!`((+aP*(wTNDE@OjVxh-3g~vu9bv8zAUj9tG`$CzE3OApJwn)6@)&B1mpL0 zFjjyhDAzc5TDn<)Zk`wlLUyKBTY-uV><4gbuwGN;+JgkYs3a%BnNy@j-euBJbzJfp z!lyVP&}j14BytK&Y)`-m2NWObHaGzg%tYP$MAc^`2 zULj73TBc6j(6Plq*WCNPCHFbA#%|Xap3F809AAG&*?_hodfp$t@maIXg~o_5>Xp~B z*44kc&NWADWj>|22-a_-mw(qFh~8fWV+P~P!IigRfpx1=3Hq=E<_o7gAHyEThe?>p z?!-u{pb~UdE0`liH=GB*c@jdy1PZ|9t}ITc)-WZ#WMe&$%b}rY9|J;Fd6pZQ#!JV? zGI-N#=`5~s^Z02j@4YmOYZ8tkB%|F49J}H}TiR>fSLv~|hik)UYj%1a$gEa6@|H0#TuIZ}A2=n;Xo zMd_}YQPc@jE#&pVUF?>};})Qh3r4huE$W))F*GG_HC;YTR>Wh+Oya7AWMt7jK;{?h znFKA}#6%1h_M{MRx^;;7T_*TVe2sx1Zt%dkOfd@dO>6knIU>t-SUs;uA5p?kh+MY*{}ZWkfv#CAodG(gUpTp5sv#4$x$!@G z-0;3Dq2BEk1}6tD%^L=2i)G994Y6wxsJ@$rAr+R|%FPq4^UtE?$cJCnZ%7s5atqJq zOZ2_L(N%2wn(u42*I!RDg_w93Pe{sRcDcE#sC&Vax&4e2SjScWSvL+LR*qT%Tt-z8 z=|t^>_WVMXT5!|Z@|=t*Pfu=#Mgk4Spj*=+5W&ewS*mH!v<= z4ai7wf{TxiV!QY1m3qJ2ZQdL-zawRZE3fpQ|KJV{8BY3LS(f}5t$&{_XLFl}eRuD7 z-v%Lcq;K!3Ys=eE$?!D2oN}TgO_VD}O{QbB$vcKGes1y_hwdo3zBVHLauX8r(`-@| z+-ku=ywe%S7$M|n#^bi1;ow4*NM9p|z5mH}(9Jy*>aG%lx&h1$qkEJ>R;`Q^lfB+B zBK=_VO-rwmAPIu@_RG(|_45dI=`S|*D=Xx(FhR{h&rk<((_}gl2#>0JBQhXqj{Q|i z{#4FMIhh8e-?*>tq%tMT?JPDjc5StGZj0F=6H>j%f%*BdXSmLCW)~DvNK(NSW1d&d z%gyc-+6s0QEpk8HT)n+;3DV?5wB5(B&i3uMG}v(k+?pG5uYrX?%?3yE{hIN04V~AA z-o1CLx%&tH`%kx;dw4I+-(%IDH%!Y{puTRp5OhNAWgmc$kB3N;M-5F4km@EoRB^0{ z{VpavwV;@pt8f^)noenDtND_vA$?e#^5OMvt35)EwJ32rIkUUr&4|QkBCZ2RI@DQc z4%#!zS5U=@6bWMXKyyRr8CI&fSBS}e_yuHnKg&c=50;CC47Cxx{%#@ElsU@FU#9q^ zXTYTI4U=68Y3=cG;9mV$La9nKN^?O!&8(pVX3A;Z9{H>Q*JYfptdspOe)E%PHo_KV z6W&lyLO&-cT7L9S=fk{$3Q-3>CpvwdymmfvvJwioiVp;r;zbS0`= z3h@=W>B#FGf?qedQ~Ivf_3Tpn-^rp>LyD9tl!t64~8r}=YDfrAX|$r z*X!2<*rWC}@~QXyh72?Wrwh%xeT9jKW6aTH0-g5LsaG~8II`JH`%4Qmsi#!5!bq71 zcB4Vk5Oj^YDGveGMN{c;lPRy5FqU8pglXTk`9|B>r~OE{oaUwo(4>i2tx(h*{?rUw zj)Oxkhv%L=0Du0CdG>y9p1sh~lQ`-;w+|yD)yd9r(1mW970s_>ry`*Umj!+c@X&Bj z(Xf>(2`B4%;&s5wwgfU(QvH*8%Cg;HTaymOwhkU6AIljfg~HaRj85fX^pJf$zfvzs7w9Kg#Y&Q_X2+dNpz<_k)c0a9NTMbp}PY*@RZdqidrwu zA#19?rZ=KE(0Qo;LDDON$#-Zm4I{}22KrBT^j=STO&DIgsV)NYt|rR6p7{E4D63!+ zoa@Q2pMY}>P=dsJxYR6_(c7=bX1eBsf7{K~kF&&n$iwK5>ovLb0^ zi)c5YE)O=FUb~4&>Nf##7|*O#5Er_N)!-^mPR4Y-|M38#HPt+U>U~jlS{Lf@{ zRX3oKm9par<+hwo zFCwvDwRUJQ7TLS|hp*4@!uyrj$z5j!9I6TMe0g1qZjmdZO%^WCDa4+;YBek`(m4mxo3 zhZqst=fGQEHO44~UCfLI&^Y#_-*O;rX&gxNikQg51$J0=O07CuXCrrmfB0|eZijA- z7dVmWW+X(DcBCj1A_JV@;fxEM3^#j=ODqrR{vOONn~YRVk??A2b$`HTcekmhVKtP{ zV*K2O+Kp#60S$O%Eb2}Kp4`);K(E#Db1@NBU#V_iE*Fk8*M`XLpWr1g#s=Wzc`}@t z_vQtERRIC{RDCI;<=rJRmh`PU^R8 zCgtcag>WzJl-@vzt!#{=OxeBZR-F&t z)X|H3hMa>)gSHZV-x_@fVSqb~TG3JD8Thni?`dkUeB?uFGz zNBaJ2D~2Fn)s%&#{Gvx*R~JpCAml9<^ZTV5-hk2?qOj>rNwKGlu@*%MKngWqEj*BfFeiJJ=qR)WIdD+w7`7S&q)x+^yz+lYwi97~bq` zM}-!cB(_L}&?%vs9;zrD7G_j`L_9uZ6BCdBE53gnX!mOSGviDNx; zjk4jwGwvg?sb?v!1;Ukeb_g@{tFBdWZ*K^{s|fv{V8S&Up%lMU&@B{;_rrS=6C|k9 zkgCCNeWB2C!yH&z*U?J>2<0&dG9_(O{P9PG?4x`KW0D;ZY3GV2S2!3IhrQA$R1U*# zkCCd}6lv8$XPB#M!smLqV%w{=Pf1ZpQ_IQrYx-Mgh9>)DJP=*hO^aUjGi8-w)`-B}Dr?mb6rj-~-;q zlr(OZ7b{iPs@;*;G$%{i+JG3pH+!eqg$L?dO3Wfz3*d!qqwK;m`Q#+fwV0-lFf%J< zm5EnQjKAq=5dOv%KH^mLXsU!-Ta)D%ezAXkvYdTkLR0wjicL_2U1@Cpd2jW?TUFr;~js~8`J4x?z`5jfz+8nv%I zq_2v^waSdaS?>ZyBf!tUO{myNh(HnN&6kf`v|SN^`tf@*``RDsPO2PE{C6_7d=m;z zS9%KV(bj8pn0l-!H^jQ+Bp<~V)(R-?1~nq^igLR{C~nL(#e<<+YuGMbCz`zJP8D|( ziKvbEy!O0OESGaI8OO0hCRmcUOG9u=-(xJ<*xx=Pa&CvbW;zEldWk`%Qjv}JwTFpM z&O&0{*oORTpe$URz5TgyT6#HZ|B~Mz=`pMHo|q3edCA4Zr3{!TAwWfb%1esSXxb(* zRWXOhx)w)ObmVL79?n#E#He(~G_pzh9Qqy7kldwCWBbG$ZgD}8RWE10UbL@eV^+p5 z2M8ulc->Nc#LwmznAw_@-2p$EoDarT$MLmpt|I+XLTHLUh_8cstJWhXE^2n|*F{6m zrOEAs_}LGpy}kSTx!xvC85JsNxJIJ`>ofLor<>cZNFO2r(Kd{B4&)c;{gyL7YxJ2BcU z-L*wqoe?M6t{Kr%6~-S$3LThL9HXJ$g1ZrsZhxUT#Y_Qph6qB|zN*R}qi0UT4e=%D zKCYu$DLm*5>2Udkd}9{~+U6@jb(jfLn5mR{O2jPzX7DBPB2jALUQ=U`$+XpzBb{j{ z4-#~biO7MI7tf?mrJyl{URtt;$W!eHNY-9lDbOr^4R5G_>u%q6=)DTgF&Fgg<%m-D zuqM=7M=TTtt<1gbh&ZKrh$P&>DyNWFc4$#Ob6E?zlp^DY)}Qa){1WY_^yW>uo6VW7 z(__n&>d1p^ehf_6hyoGyT)+rVmm{m&p=CcsBLI^ofk{r1QDOYKPFs75@;Ow|X3&tp zNnsUX-3E>b6ILe3hY|ZKKfcdObQ)Fl;*z9bxLdzzlo@J;g0U{VxBG) zy8hg4Gu1=6e8i?B?g z3e2oqS{?BHiQ#b%F+>H#R-5*Hx@u)PFJL74>Z7SMX?uiOT9Pq`-ClXuYlLstW8``j z%r5(b#IX7U(}R{X>5g_I6h6YaS^d%ADAL#MD{23f##}!ZKJ80d*rO{Wzqr!x)H1=P zAlTf0L{qu_7ywh62*f3<`7TsjkYuTJml~|nwL5bBiG&jJuy8n2u(2@?ZRk%vRKaUH zSQrNC17#z~g^4w4n!vg^!bU{afj*Y;nU_L}5pfG=)Sr50tCje0<_E~$9aJ9lgVcB` zcXg6(;*K8^vVjA-k-4tAI=x>D^H(g#=JzBTX5Fx?8~SOju3}42TTNb`OWAy7rr|T{ zjNBsUo?$cYQi@?q_9{|438_jl!{4Q*9yreW*IRV!U-!gT%QE*(n9?_#&9#qgHQ4e( zjw@FKFDQ?s2NA!shqfj6{{4<>=>O^dH(#50$v=dVR{rg?vTtNg zza;O)Z)EI$WlP@eE2&N$L#T08v$C__X{K+S z8Tc3VfQ+5YTRmk;ZwiH06l*;DtoQm4 zHQU4c=DqG;gCMXgUvC4H93v#Wn00bE8&Kj!6u;Tkc-9cJaxb`?wsXS6;HjOtJjGjq zL%OTHk)LU;BV0?{8h|6A^Vk*1jz4(UD$#HFO;Ri~`Lk2+s_LAM0`xL%OmQ!R{fucN z9bP4*C9d%ctxXTwi0amio7R-ZZ2O}X;n{3=mpCFac|7u~M~RfX6V~VhGlXB>$12!7 zozmQ9ceB|V2(*NQBARNz_loEx3?K%oI>t6XFW>B@nzh%DUqHhPSM-!W@vcAzx;gul zkU|aB+g+-=Q8LKC)a~?2H0^iXd>$#lsPUfC*S|J=7yxSmqC`((qxA{Z2AT6^EmN9c z$L@pL$L;MmsAGloX1Uoms(*zndC!u@HQD_+@*j{08X&+#4${t+0XTio0!NmqS=a zul(1Z2cq7kE6E^==P7_>vDM|Z5IW7H)D{&8hb=h^grbQgM(bfxD5!NuF)$CgF?3bE zaOcF~9NENmT}nj9m7zegcO!D!f{Lj!{XK;O%ynM0z9^6w?tI*t3XQ;@SohKNoJgf` zzzhnc8AaIYuk~*2=fU{WZIUYyvR;aOn%>?`g&1#P8rbbUg7hmUEFkMY*HMoFBt9?q zmg~YX$goXLxGL5QI@B3Bp{BOu%-h{pFZN$N@z?qMhR>^FT#m%I${1RXhn^{?F4%03 z5p^f-qh=w9bTp%k64TaVeo%4uoF~nA&`8#)QeAV@sC(@Cun|d(6d+N`rZeDz3=&B2 z?nWO;&$HpnYtyp$kwsBAo0D?>tebe}uJ4v`BSw5aU0+KNPid0ZN*Rgx646?j(MGa@ zJ+qX?d(DFDBC!6CS8A5tvPY+bLw4w%u9e|mMaiD312TT(D(6!gE47Sq9kh&S9nCNu z<)xt215EyWgvG1t4t7v=tWAx+)qy|EZO1$!O&fF~gBJ1NdOQ*{;fL%yadiUG$sgW> zLQEb~CGATIz8thGSe#wGI-Q*}1J-znmIL!rE;f8qig*j@Qz8hxim6 z&z1mSa7Ads7SqL}rX)25d=vZX4uT`OY>*R?{Sray&wv9S3}Li9Pw1hlhjcPLI5Tq1 z;n+K(V%+NZ3+2A?E<~OmQZ#4-XI!f7;n_L95YIMKV?U_6QF3lTQ0D*~P;7m?@^x6?t0Om5kI z=t>UIdRODrb2@D+isH*hQctb-qD8M0+urmLym3cMaNA7tnrQswXN>C{EqfvupoE=d zo%134>})cGU=`nQ$54e=_q>JV&g@FHEg*>Lg%2hsV}Q%AD@@V1nEpn>g^+a$w(1&X zCWyd2r8y*ZItcj07K8&Kd7Yy;aByD1IU+uXo_wjZ>LPyiazT&We<`!xhz1X9+xIWOzATEJH5ia+w7Ar=}8i7 z3%;CU+vWjjK$B>mV%JPRgS&Z-MuF#ck_OrA1Ti)PJ5SXH8@nX^$qH8ae$gA+IgbW6 zAtwft`gg&ynSzpzs8os#9@S3Hhf%jmY{ln01WGtOKVjaY8Q( zNtt;|rNueFkKEeS3P(yQUQ8jxuOh9UgfgWMEthWE%rDjXuekPy3~%L>EhDf<`APLO=$f9} zvC}-J;sQxLpHGiQmJDkLxO_*+_MB$Zg~=LbUhmkJkyYThod9v`X4zh*VILDijD-=Z zD{?y)Dm0jYNqK5&nj$?@jGoSjB!rlOAqj2IrSv<$O17CWq|4`%mJ8>khNNx(r9{!Y&cv z0F@@7@in`ij$obKe$Go2h)`ji>RT+Cmt!IhXwp8ydVR9_?>KBFQK5HA>&w-V!NuV= zo|}A*VG~IQIs+R%?vv=HOX%(P=cAU4ahDg`PCS(FO$@tcVj4)~=V#vJf&+4W0I#(R zQNIw^<^&-Q??!j%ag9Nuo(XxU+d z_Ccyfp5(W%`3Gace>#dCxGuJ>WP)GCGR%uc0ksN7CG9-eClkfu2wFzuwuhA+!o?2L zA?FU(opwRBRx+GWQ+&>5aX;{fBC;?`l>xG}Erd5X>|GsfkqNC}aAx}4XvLTWN(D$+E@4T%U#hB{`p;wym6hPCc^TWaZ#0d! z+bKEYp4Z7);QkWJ_B89sv2@*56PvYV2;cTnLU7WUK31eJhG-ZMu9#)b8m2D>QsO8z z8$j)l!$XZE$nl3+6|tCmx4)5Ous8=>m%=L6iqQ-CGpaKq&T~lU?8{PjJ(YD;gu1PN zLI1j5JTK~)g)Mu}K76C1bS8piQuxE;K(9zXT=&2A7c)@2sOp}1iTdd=wo$#;)yen=>do0;crbb4S>1>f@l<&DRm zfy6e~yDHXR&_h|Q&{NiOg9j}$yRM;E(*Jr-TL7e{?DaN~an>h8j@xMVGdcljm7vX|9_}09Q_x1f1;$XtpFG z=kuZ=u}z-5{lIR7<>06lfHK~x3s&VjtW2H$VP4KnA^5jH7QnO5MCD}J~ibEFW6(=tD zvq=Rlsfh`ihZP6e0p(=CcxdxU`_Dv)cC7vfnNOWYJ1fkOP$bC`K621e$o;gSOh+N%j#4iji4M=mfV@6Z5$Q_M7E4Ol5jbn^HIFyOD%K0G2g4^fq?%h@7VTJs* z_$zdBI2Ac}GA^Y9$l!?1JYfma?EwnXY_BB2L(}F@T~KkE6tj-5`k{R1wBmcRdL^Gd zuSJ0rn)*cC9%zyh7Xt=L3KoCM>WcUR^kQkG{O@QBdI=jU=~iyz4aREP%8_$91se4I z?c@()c<6ba3?@@I#GDpfzgHJvG?u}!cCm*Up1B;!m0e4#qz1()w@H>oN2dSuBq*Sg zDC;^;UAx^UDMyvIcIP3KG7V$GF@lb_-7IB?oREY?1oO9>U!q0PFD_R2-(-4e*;-~o zfN`hURr?QVY@JJBvhkBYm&k(+NWs8}t7T0l2fU;@x$c5b1(O>Ui=?MzB+gnS#k*Tu l=M%q#M9d=mjD<9v{YbyCh18mOxmAL|D-`;vk@bl7@mt(!0z9 z%zOA5=E@Pi(KF~uF*ECz|N665=HC0OD)@FG$2bU}Y9BIJuJb8#|KI=f=FVU3?CxCK z`6~Xs9sfR#KX-Qycb>-YU++ALD}UN~uyZG_-H9u=cD{{&xqmON9>!01Sf1Ki~PMnC*V74H&?|?U?`Two)+vZNT*`)+Q=JAD)3qPvW`%5%=!Kn@D#CZ4O8Rmw%&^WoFU$t=aB%b)9aZADt&cr){m(fmw zIC&r;1_v)QR#Gd+(&`^H+?^H&tY6#(|AD0*1KXzigGO~Q4zyncA3utpp9d7s{%QQ2 zJaN>XeLtRpLw0wr>|B~q{Z8C{()jqWNznb{ai6tocj6jv_G9dZ6g>;d!%q+5IVky4 zTlLeB5Fo!D>qAe$wIj_>xAqUq2>%gQej|9`S*-j1D^YwL`w2p9eMXhs?}r{_V+Xh2ztv{cgpa z%r6O*y+kS{qhAM9C#|Q4d^dCrx)~kHxK(=p6j$K)x^lDyTH;C5@9W9y8#`~mpdD8H zZou*M1>La7N5`CrodJ3%^KFsMI>LU8fUUaQ^c?H-2p2+|)e*jrr`FOz6{0z6-hQ#p z3L*NoLb$ZXO)J_&WoVhWIY0WoQ|OBa@XNrwd<#51L4^15EPe(?+x{x0ImWw<3(_<5 z)0;I&{0Swkoqt`RP^7^({0skyvWIdUED`vgltj3cfhg>}fyrL=L z<6gSeQ6I*7a`e1XJ^z=%)Z071j?s;)qizoF zB)-O#Y|ne0@H7w>-=<2-87cdyBQdblCy!>XSJyqTZajo%(Lyx1px8Y>3F@{qAAiFvL% zp2OnkE?6`L^aQ>p`2cAx&4>37ZNWaiCN#!!DJuTmOVLhLIvvd_$AF8gJ3qAO3~PnA z1P45ee{I_oyJ7GCYrCgB58l4kO^%`R(enx45S_~pBRepTgwMvDaGN|d>GJfk^)C44 z$)m<;vgcrHElDI65C@Q7`r-w}fB_jzJNt)#3;Ez*to|JCOmV%r4$ONEk9UJ|133tEFt94W7p}QSWKXnqH0(EgMknY5akw@5OBB z5_~l7llcQw((@D{^%hybK)o;$J-e z$MNr#rXk_bT!FF&@Dh26ALD8Au!n7B_z~tkAUufiQerilI8l%#XG<&UUVZMp7ud(E zWK2PZ&cv5QKE4d-(By)m=ju(_XJ~#k-W3$!+LTs~`%a_BTVc)klYTKYf>K}-x+*h` zC3zHAO+TV#Wp3u@kcGM(@Jkm!!$(bPvJ!Yk@jbloMeu;~4BrG69>?#(Gdfl|&#&Vh zc=Ss?dMngsI6IaW6jDNE27xl`D4s$O*%gh@ZAp{MElrM!a3-1tg}b* zddL;+?)*Mxg0`$@Ey=$XGogD@ZvL(1nTVj)nlm%~IqP27P%uLNUy(2z-NUe%pL5jR zof|RIli(FN_2ZTSRxDfRqqYHeZEs%bF?3VTuG)%n2*21p?fknzbN=+}-=0sj7t-l! z3pl#8vlsg6gZK%1YOe%|Oo*q?$bz2+gWH814#8IS`8GHx@2r_e+Y2mJU-^T8yN>vVeKP;v+r(u^~?W=Rd>c%IY zqikNEHI}V3ubL0BkmzClU(PFCzMGk=02Vh>sZG7m&cS^8)h^OerCc4cl+v?Mcbwp zxf4`_chQ32?jZh`KQii-{eXvx1J|J`|H`|=!{G}5a(6xY;%QYJ$)g5uYuRXKkoTSS z9JCaEv+gM##ScU})G-0&#ECo$es_12p@HMje5qOFRV%(>6lFiDiV;6e^~&7rR~iL* z7vCR^r0AMTGJY&xp)4?Qx4h08kvOgq$9?@`Ev|CO( zB~>-zljcQ$QR8m(0XO@=lFXDJ;&QBz?L3@|eA4}b)XaA%<=vDT)}8r*Q41&nleEC| z%)v9fZ=M$ysLP{|C$V0VKp;J7b}%yK|K!YEvuR}hmR(K^X3T5Lo!KOCbGcy;PmxE-rl zGc<|$&qHg_IYZwJ-DAdJ-(`|A1J0j&z7-dbMHnK|J~puMK@SsG>+?5d!ggVZ(tSVX{0PBFJpbAGqLW1)7=c~ z@nMAw?T05yT#NQxf(_gAb;uifTz2bx;6o39!-VT{U={72UIg&SmqAOe=?%;70?)I+ zm}36wS%M!`NFT&cXz9DH%8!mAw!t_qgv=?{Aduzojnh6`KJ}r4?$}`9>N5-;i7|O|)AEO-k zO1TPhCg2ml5!~ZJnR*&ueh*m3XSesvJH1y9$E_JrzdR0*DB_mthfyzs!&wnn z*jL3*-MG|DyZx`&IX*Q-C;@-xc>gjGq;YXSTA&;^g|^k$!Z1 zbSO7J3pNysSyy7q-Ny+_virDHvEg>t(stuIo-LHjNRr=rTwOTE4pKkbW-#V@o}vjV z`Dh6`DU!z&$(l!}9kA7^_Ph$t(7kZty<>Xe|A4Kpnuk>5?UQu==tsR7Sw!R>&xCBy z#{s$FNAmfTb^zWo9jK5}+pAZgHMlf~h<`kGQl89*W~rLt-NqNZtu6zy1*{Ehf(3E` zvg5jM*%ZY-Xmc|v>mUMQ7BJ2GdZuy$PmJwH}X+DgCv-KSQnmgu0^>UbwfzM;@MQ)W}A5s5+{kE_Y0l?2C{u{I~h#pC%%kp zhXI%8loVLYB1M)e`8()5%l$#Tg&f~$YkM01%cDkOU_p-dT-6%|Uo>NE5uVW^^Zh-Q8g|_u?~0o_w(0 z;%!qG2}-;bB!dh#+-_@`@J{38FWQPMkzPUeejzdJz=N-P_%p}JOgv}*TvcH83n~WD z73v7c^MhmF`&qofE|A~E|74#Z$94L6z#bU=rtMX-tzu0;@+|zgTv2~@9oedL;El?* z*AB#lXYMYmOaEZRBfU@fl}IN(+NbffbgQsN9TnY(->16L97g)iu!N`9DlRm>NF1IA zw$i&MT7&u(Z|QwuP0dBDcrJ&aT%a>(xyFNnUAPb#!1D6*JUvv!*Hd*a=pYGlHJYgN zbQN@S3DF0UMvW#?yZI?Slh~0Tv(yuvYrPpOgFDcB@;9+$>hx0{V8rky-rJrIVqL^r zr(MVU0WevsgQSZiN()4NczR_rwLYP&&epjefo7 z@5u?$Uj(#hYO1euaPIE##AopYajT*4Vc#B4Ie${2N*!`~`06`V{yje4&E=4EEFL;Y z-Q^?Qkk`5A5)N4pYbbOfIKtn??@4@()ZRYEfRvrXS`qmJa%MHJVBJ?WZ1Q8RQEXn7 zvyyeZieq0tn~oJXDvzK(D&k~CmPyUJ9-bu=EDwx#uu61`V@DlP*A?1I$liK%#jnsS zA%~I`BZ3QPc<#e8lRA7Hov$qyhA`LXE1S42pFz?yleF3Di6Z%KW~j(Y5GTw*VeVk3 zG|%+-*#zbGWnvEYPh0Fmgo|`6+2efEUJ@}OQIp;z$8VqQeAhIUaU%7kO?n?*LsF^A z!X_Dq_f5wfrONpBX?6B>(HBNGm3RUjfV_UwD5|_;?WP#3Okal;ei9l(XHE6H#447I zh4i+1FO<5xwLiN5VazY>&vViuA4EP}@G%P1QYH#pDj)gX_+Pnu?Rm@Dcv`&$jEv`S z)H1!57P4t?zOdq>70WwwoNyT3GNW~0(^}yj!L466?5MZb6+YJOEO*w8cv@YI=zFSL zNFF$Z_D~Q_ZdnGyU`;Wm^4jW8;4SeA(T*|{p6z1%ej~=xoqm@s?`bERDvsp2mDj-9 zCY)!7E@gE-FJo(1*GOg#EQt5XhSH5EKFaZ~N0sqfJVn$AR;$gw^n&+YBcm$;dHAt; z^JKofi&0ntTX4GfOdhrR8Whl(T6cm5Ji`?-AHaQJ@39Lf^@;LiaH}*(?q`#( zNO>ApZf)b74jhYb0KcOvdU?acRHcO{Tw_+g1qtIm+yjqg9LW9x*W-})9~%#|@>&5R zFUord@$CHQ!rBK7TVMkXB3qI+2mGKHgdOctlI%kvpikV$s(skH?A5oU(dzRj_|ws*Qb}a zm#$zpjjEg2WHi8s?G1ZKk2?6*e z;MHYll4Hs8qe0Q7RVVft=bT}*mHjHmyI4HBVtf*-teCr0YUlp+&9~z`HtYnl3{Nb> zup2pMJeuC?8Lx zk2WO_LcR(uY@f9Ds$C6PWt2f)iQq zzche%>#P&-uU83|oGz`PYM)Ioud6>3x+Tu&ufJ|SSjHM2;hLHQZ%$|2rPRECEJxl` zIF*N~TGainWtreOPdC99<}p*yiFH^b8%1*yensrvw?$gMzpJN zsTnU8>ezRbC*}Mgxp@3A!+N2ey+tjJ8WqsmTA8jjlP~U&|JL%k zPeM+dg{1FHF~Z%35BB+yBP5?+E7-txWmr{uh3TPdJbV(?ytZ`tI}6AZ6}-{=O_}KZ z+fBG+?W*sC&ssYXC4|lMTia3N0PTLg)ua|bke!aXx@nCRoehsl2UK2hCfXGocmrup zPC37|2?i>5=Cv_;QA}g0Kr>YdP7Iu$zF-dySKZko&&iuHCDK^GLO>@>_420PckU$&RMF70$a;;oWNk#JldQmWwu}=wJEQ-$ zUCl7m9BPH?n#E#E(NHi-LN^_#8!U*qemF*CM;+%WwInJWZNVmf$+syUiC zDMlxt?`Q8%&rgXjya+T;KmLfZB^u>8c#e!yyU>>A^s~gLim}H${*-2Ygq5qELNJZr ztF@SWzG8M{;yZ`F@C&71mEd9@4rlp^k z=Q$Xc->Q&7HTfv7`Yp=$BE#}ojHNRd#Q~L~b9XBwxrRCJ#mXW9XcHv&dURPR^U8T0 zInSkdhLh~5TDl4`_x$|K$-~J#^E0b|>nwBg2C(za=@atSMr%S%M_^qN5LL$TpAu^3NRDOYu z#ATROVPQViqti-B`g-`c$<;mmk$W(pEN|t!IV@8X&{vh<$of|0za?yMqP{(m#8_Ub zV-x3M@9+f`wd^+;TF%0F9DH;1qWwqq+~5n1BTgZphmYu$h=}?OZ@F`4`gVnOdvwcH z^gbds7anvh0DL;f#NC{#l8oQW@OT%7+_zTOpQuv(XU^Sp2L*DcINDziFgLHoy@q<) z4Kk$~Ri2(({T;a;I@fk`Z0pLrQmvHv27Z|K!%pECeNuBycCQg#Qs-8eXb;`?Ip})O zjI0Y?`J6h*Zc=z=ti^lvdj7)jJkjBNT|VtzTAz8}z%%sN#m-O1nBw(xZ>=YdL_|GQ z*d+P~)|!)e9PUweqki#&uUvC-PxexB7wf`Rv=))e)1CU&goF597E``?pL^!LgXY(h z7ptQ=F1#34f3*Aa7P0P|(X8XC8F)Ep86twj>0ZDMpP_Md@?VeJDVLl*o?_;&13T<9 zuW{Xevoh|fn6t^#*_R@Peh%wO#f;c})wPLM`3XGqm3W=tCar6Tq^FfN+mE-%C4Aqo zRd@WO`*m*2v`=&n{?vRGD{v5=o_foG*lVx^TPJ3hVBYEFRI4VE=7cXsRE~yScym!q za)Wam=qjQb<#T}sTwn)TI z)8YH{JabxX_3K`rlkgDw49fnDq&c^$r?F4S=307nKjOSAVRQC_@(1zndtvK7i7Pi+ zO!i*vaK9Sy*(dG)EA5F(@pZt%oxS*rCH-ZbQLl4}bfOp8Sh}FU2oC2g4f$vMUQdNa z_wsh#iK#b!*|?dE?ziy6utXjk}`)zb6Xuk}V;z0#h!(N_HXmXa{9@-OMQ~p$y@G)*b zxmS<5u=sE%`Szv0tmQ29n7OE#c>Fi;!^nq&t#gEW5ci-#?`NjZJC45}BQY*k2EPL< zf;^^|H(gQhfzMZK;gN)Qw9GG;{ZTd%+30I~S_4&lJmWb^ZZks0UK|Fdz!JJpRaI%C z+l@j{M^RRd7CsF4z%F-@j_I?ugFNH!k*w#@z@s%fCAKFB z1(Qy|A7Hn*d5pm{BW z&euwAzlnR$9vg^nLiT`&3OUf(BBHZ30$+AOkMw+-^!zS(l>1X2?WsE_t)64Qy$!u` z-2I4St_EbfT6ub#d;)HC2CnyiLHR2YK_w*bALB>Yp}+dQbyA!Bjef0bLHJ2f2}*gt zIP)NbQ&~>v!EJt@g^YmWf)C<7oj{L%khX*JoSvQMwsZY_)^;P{(rM8n{>MA#PXXDx zic@gs@74AAnR>*h-K(%4V``>pH7)lKVkNME3C(oIajzjcQ9(?#VQrdv4NH5oUI#3a z9QVe`lgI9>`kA;S3RiAs+K(!)`6%?#&7c8(AZMUF3O+z*ypC06rpgpIcTlB8!{Zy~ z>Ta|Z3to5#8>BbunIfzc8gf^w>N$Ly+KuiFaG5aTWBW|l_2=#Dcao8LScFlmJzl0d zC-523o@tNQgP1SP*H{V96dH++YtAcw5F3%0!7D#{p#j&l5#KZqMG->I-$%#mt{y4E z%4gMM36A>2S{@7jo9T}KHuqhe+A*^qu+x_d_h8>sJ5Rr-vlbpl4+^PKM_ppl&s=EU z7n!$wrs8sH?d(M|0^TC}8()_!g#2w*jn{x*%zjvSS^hS@P{IIL^Bx|yrP^Ab--#jh z`=;J+^lQ#b{MdI!k3Re6yi{fd=GXUatl)N8!$IV!xPom8MFNfgdNg@D#8|Ao{ zvgGd5!K4gyV)xY`v;JL^N0WQ{%sV?o?*xvJom@|!^`#HQ&-5pSrR1##dw>4J<$!a( zt7Ln&TYWA>$;7Lw9tlHa37p9pyTuZFM%zUb)u-Ahuw)9SZD;S%R`*U@-p7Dep$PB?8HkeJj_KDi? zJjhxgsXBd6nJ{L?SHmOdb5%U~UA)Qv(_F!*N$J>xkCgdfhAQXW0sHt!WIA%UB{>A| zc9B0_?GMy4FA7dD_aMetKE?JZ{HyO{%rE1~m(fY!0lg-Fj(Czi&-GMZUr{EF7+Ser z)&?&voq#vRnfkiO5veM0HGYEo$4>tDV-Dm4?{R()L_U!fjrBAJd~9Dpalv@zU8Q1u zLfMbR7ug4oby{kjtz#I>cm~PQt*~-6OAMD)IKZ>NI5p;JPh-_`zKVqk$C{K;wU9g` zd{z6ZtIhx#`Tvg7Bwrtd>~I}VXj%u{rcTuSNK9rMU3W$Xs?WT8ycf~?CR6t=pslJ# zNpHrJ*WwN`#}QD^_JOg;CbJ6we#txnGp~)MWqu?AQj{cLC(qt=)ueV?RbQa*re!@~ zRo;p!fmD4fUdcnFr--Oj(;{{PqwF5enZX~@AqaQ_>@E8c(uH_F@_zI^v?uL1RX%*8 zwVqT~6*zR#M7=vwV^b{yZC1}O&8}E53J-eUxje{e^-~>~sUPhrvu1F^y7yz2!{8fz zFW50@e`+y4`48=+KUQUaRCgy4Fe6M?Hg-Q`bSJoKh zR^Zdjp=TXuElqXuYB*QxePk1BJx{UuHPVTs5%hgdX=)-S z_hS+r(wkP#oz@=`pXr^=}5he%hBxLjh%JYMJGKuoqK-7e*S#sATU?E zS(d-e^?p33$ejES+FiCqzFX?b9J^+X63)-!O{A_Lg^VS#2PLKbs)x~!RAq3CQZtTE z%q48or{yC_`fAO=luUTYNlf2gKYqO&7?Cwl&WQ+U%#4$jptg^#z`LLi{HQy(cR#2% z=EkAI@^{v!UDwhif+hX8^-lKfR}NR8-M!12Iw!h?jCXRE?rJ9=8Xen-j|G>@qd=e!*mdkX{Qi= zNWw+5$v0r;dq0!l=;yNc&e{M)+D~jMnqTp$ zrT^8&NgiSx#lysvndw9C0HgeUX%c-wm)b~w#wVE26Y5UHHdHKZPdYz{xfS=_4vM)m zQZXx6;7>dz{9T|Vq9=yL3h_K&oA0sPSK@qL0q@CUN;<%s==7Sg1s=uO&d}h)$l8%T zs1Qkpy)U&;$s1 z>r!{0dX=#ZNw-b@m@|UdOXWw_)SuU*>-k#1_VFOnXc>;YIpHRs}7E1gnnY`2W2Er)DC; zMdsw~z@J7VM?Y_SdQV)0<&ak+zZL(KU0+jp@!#Zuz%Ov8_A}WdlkdnA zqdnLr%&5wL_3X*?^mg3_8A98`O|saE@9n{*Eaj8%m3JBAtD(W*D)<;0YMiBf z7MyE%=VaD0ZV5z6b2ZLW1F^isIX*G;zzh%8x0q9FL2`0cb^Y*|`*&_O`thvlR+cd$ zLSP*6iZ{r!NUms6vw5SWfiGbXzxPfXQ+-f6>nNWG&(Z_<{L1oVWy?^_XCu>VP5MZ) zFgqAQuSloV8W;5$xLUdjx`T($;|e<@l%G-+C#is5<;h%?`GKp7@MM!MKa4rmcWcUd zl^JOrd{r<(W>k4zbRn5VYcQ~c_lULyYyHX4tV_GI$Q%73_y@0-|K~V~DiHS zH(*cL2G@M?k;uoCX9o{=RULp{@Paf33Nc}G37`YG55jqgmq0{p;at^OhHS$OXpeDXE%0CF%$7Y z|GnE$H)!vs&t23SI1jF_K{Xw9YcGA16Q0x>k&W@Yw_K~7z|-(Twbxv;#O0?7RS8=C8>a0ImNPLPvy_i`MK*HVMhu(8l4MoU;@_cZeD9qo5OY@&HfBd*OLum3#(VezSQ!+$ST#lhl#+ zVk|g^kp+i%nHiWF*_+M+2lvoO!ib0P463&YHh@rXARFjpX=A|vHl)|}Eo1o};40Sw zC4mWRh-7>fR`_D;1fUxLKf_h2z9R{lM}GJg0zNE|8y;l_kyq(()?0lBDo zY?7iE)5L3SLOxI(r%mF@RMVMC<G=U4T zVDP|Pe;Ic8G$-_5iMJ&u__~aJ)SbJj`|uR9#_FJ-pjpo&$PV1Jr~5H8Rb#TgWF-#6 zo3B3eY4>-rBH0MImP!cY)^ACUPrxrwb^TRSJEaed91?RP2kao&IYH;K%Y=_r#6Y>ul+(*$OFRFwFoU$u<5*Dl-m%|B-Q>vZ8EI z#ZdLu3?)4CDBejJG)nSTLXIA-Px=%JpzZOuO%J$-RQ8OHRpK$U5teTrT1V@uJMu*! zXpIK}p2xZb>%{MX?=|jRq}JmwpHB!rjdAXUwnOV<^?J;_+h(wLvoso9${OMA+C?*t zEqbGY#0BGQ+oeOUw&u*_St3v9$7MXgJDK&tw)&m1QzLUUz%Os)(RO}m5SVd3RC zkHb&nR|@2#qlNILagZ%m~cpQ^h(rVmt`F|y(puH-z(I>xE zoc>mvz1V9HvJfsUut&Th=_*D2nO@b=5(_Zm)$B z0!PtPSQ!smQm%=YP}C>L2Hod z&=a1m?vsK`L3ki?Sn@;pn#H0(_%O#2t0t z4fw#u_v0Z)IVSMKDa1hNVmM12%a~-c>RRx8fKt3s=khAh%=}f{1r@HAx?mm4Ssrv= z-8%%-H-oOOLXG?+?9Z*NUlkm5sjZ%sc$a7TOwuP6_XmM#a18d%5u!I)NN3})!p^ve z%iJN0yw}xWhqwoR%>{k^Ts+G?R?Ylyv`^d+rHERQvAb|-kvpNr4P;aDDJ1WS_{?>V-CEJE1~5Qwu6>$t6vI{l|dVS9&ac+i{?{J3@f)T zgmjysk2k@{Gx!xfB&z>XQIpq}UaaGQkK!#`xAYu{X~vV%K8kfxuVB5^8jwrde1=*N z)8_pC%YRoOSMo4uNmwdo z;j)ABj=q3*C#bY0p8)~fgH&EU=9Yx-YTU(h!&l0F{V86q#QVg=tl)M$NyNd3$!pVD zE{4Zj{nX{T{AA7eY)@~A26*;xw>bz~s;Ucq)zclF=MSf#pAti4wVkOaFQobnzCWX3 zH@PC)%#&b6ekAlJ=f#uK-s%p8Z+J?T+n)bEi#PDB#1+<@czTc8d==k{=5I+iC zM)vwq!#BDXYe~06-5bVJ#Q)OjH`|`wwYwK8WWbd5Hf7gpt(V^C{$!klSk?xsMkbi^ za?yaYDOmx|5vo;R@~_Tz3Zhry*dndl#{tLKgSt&^3T=rEYE*GKey9HgYYHC5`KQ)^ z_>p|nXYtnE@I$KJVGq4CJ<48A-_P}8f#^w|J|imO^XLSqHEoPw{y1!SwcvZP!tA+kgR`P*;le zfQPfv;o1l9;ElW1^KDxL@2BsOK1i*2Y*_?q%7-8(P4;~N^*Z+l#e6~iY)YwPuaTn&5>7b1P+Omxo3_4sW(XC(3Jb3pdv z8N3lrWulf0R>k?MxWQHH>FXYH{3BOis2ZU;plw3^dE6ItkHSw;mRPI)Frf6=YbURO zd8s~|kM(=b^I_aqg{6-;x2MaPQSlo25Lf7xE37?ZbI)C9nGfn7`JLgl;Cws_C$g@5 z-lkJ5=3yt|)yj1S#_3MBSR3=71fOABfDBJ^>d(!urhgA&NCpqB~TxTZ@b4=9kxVr{TKF_}sIb@{>fLp7>8^CR!KGh-FX*@Gm3p z_(jXJ@ZHA8?MvpaYW3RSo1%5}PLG?-*)bxHekQ)@^qaVfp8{ltRk^XMhsXj6I^rJf zXq|hjr>@>otmn7-IzkWst5~_K!R7cqV6Z}ge~7){i-9%L+NSIxU&4myN(K{r%KsBB z<&ThgbY8Ohi`eKYC7B6V_7Dq5r5nA>I+eM+SXO!|){dp^Pn>pl19#NRmuK=zFTjUD zh23HubD7#{r9c^6?ayay_n0fUiIt*`f@h|#$%^^STv|Dv_ml75i7`*&x#=65_-&g! zpgL3KJXH?X<2qNiQrEEWa|*%S=~Zh&g)an$I?hapK5HB%zd8H-)q!>%5Gh|kPkVN? zp9CEp9pr8{b;56xkEqI;qSD8`=ao@}^L&!M^(wK~Lb{186H|Gm%Jow23cIs(@})=O z8S@LccBka$GqdunyB2xU{f|7m4m_W~fM@RnczDwA@N9+WTHp|?p#9{wuJvkS_)2I# zbs)ieKC`eNZ<^sgZND>fN=C`ZaoQQ3S2;Ubv%}~C!{4NP4Q;L{)~A}?ZKvb*tJ~IY zdZ2q65i9!bVS!55zl%HADej!d{O~2%O6-hq^!s8*sOnnqF7a#e1L^c2r`Fdk`?W7U zzYGi66lGeWnW9qmU*3zE;JqHY@tfbA6aWNdT;GV1h+TD3M)ggOy03TZ`5vNY!#vy5 zkJmzzyw|ufJ2vXv+!a04gWRuoT{u+Nz7u%+Ws?|r|M*Dc$)JZac=*HGyOwz%Jl{E+ zr1jPnsl$`e$g|_`;D6PfZ2az#>5|WJx#MnZ!6eUk%*byOPj%NHyaL2MRi=6@bt5`O zR?uIvCKrSJC)LSMFlzNec>-Q0H%d3l$ob=(Z?<|rw%3?TKUCPD%P7D1qHYOty<|zM zg*G(c+Fb(F?$M)IQNJ_V>up1X-Xb@Q2R_Bs$foB;O_FUbRlh_siyQnG-3WQn8;y zxV#-tyKf#XK_4_ZbTp1^{>kUrFp~9E%7b?DF@B%_G{;xj*|d*uZ8$%T9MVTK*n(F(%hrVgn%I>}EwDLdu-5E&|Xq1tcYxD=~EH>&c3E~_@F zLc)5%B>EiBnBIwg?c0}8&QStNc@{lnWaX&gPQ7ZP5Ot@m4dI*zo^({KhV;>=YQ86K zlh-k|hNGvE^!h5e9*>C?b2hb3H>w!vpCH5asL{&(oAY0X-X6_9j+1e6?fR`s#>YVu zbzA=ZF+ItcDu3~hv8t-2;MCM3=&0=YQO9j!CdZ}g=k()vhSYJ|*^f;0deIq=PlNxF z6hob>x_Ce*WO>P6rbztx!&v&eY;q#xsk!vd*8@wxf+jXC^&=55N7POrz3V8?w1UX5m;EO9K6O=X=9D|96K|h2$fbR-t*&J#uEm0k(dIVs$2rbWUfJP{vlBk>M;*jb z=opW$N85xAkCN|MZG!5c*^l$6-An%^EY>({Qp!3P-G%N%E27JY>6M8=3*mS0#Plpd z^0w^sv5uCfp!aJ|;LFfA^x9Nh=lbw4G(VX?>TS}k@-UFD@8Y`m)uN%lir=Ii=|fWQ z2i~f(NgfTp=cEQ{Wb8I0;@$4GcXD33D(NN|a}Vl;(#~OhR|PB~LwqSs-IiqC$t7`T z$+q?ul}Hh60uQ68`eIs~*8a)jFe_OCMuS#VvavTh;kQ5SN+(1zH=0!46X-YSXT5w@ zD3V_dceRE{IXg=%<)^cMC+o>6ypDhp9_CKIQBU6Jk$Sa$yGA4;Om3dF>}V1wX?9su3iP69hPPOjp~T-dPcbO;C(* zfcbT3;hlk&fe|0v@9A=OtwYvYeJwSAom^lGr+9O7mU}x{WASambt7~;zU(qgvYsbl zVjLHL9a&M1f0PEEuKgBz+gEF}usxrX{#ks8Q4)#U@hGs_-fK6mcDirRx?gdCyTMp5 zJrC83Q5M-x95l;2AJxspW8*xYA?fAu>&Rz^KPztXIkZ1?I?Ih%9iAR1)Mh_aKi@WK zb|orN+RutN^C&z-YDsuY&N?U;Ag_y>7g`v*#q*<|nd|YF!{y_Uv(f3nR2r}!V+G15 zkU4iX?f*WTb|Aiz6$VcEhEsU^S{a%U&upA2SM_diw|eBrR>0}X9V#Els^sZT*Ei}O zpptJq&NgbrYcXc_94yt{s|KiRm%q)sqZs4mc8^>S`An`p44T1xL}T7Tf`_bni0;Z4gV)^6q4nF7 zCNJ#4|WqidDufb4sMOZyYAH4-)m8SinZxQ-4DvRsA^dAZ&6iX}5=9e2=ZbY&9l z)AxQ&N+MKIWNCcdiQgT?o^*C&pFg(-m|7%oi2X7T^f^j>kp#^RpD21JzoPs+I!}9A z#EocL?)B@F9-f{axlMgvIY{4XP)<52k*7mSPvD=>uQ2UyS)vEgx=yT5-9Q|yx{1$c zBob9F0IcyOH1kZguVktbx7E+pOB4}2yIjNrjzrw=4s0n8i7)IV`csuEBKiJAHMpJ( zgVri8@HefTMNFMjEe!JWb^J+w8c#1u%?pig#`ChSoJI<#$?HNR*aMz}%{r^E(MZ#g z*?7=K(Ltpz!Yw)htKSDYH}+cjkqW7LWb3&i<4WeBT~FU5!FYmr&C)cLPrx2p9^V(vUjveqDYCIxIK&4kDoJ z7?s|>(`?Gm!xp|7(QNI<@|&{p`JO!lY|+o-?B~Bce)sM6Q;+5CO4hqmfzP}>CXCx_)FBEaC{hW z5ve6U@;PwAvlY5IJ?kts{KHxm3*vz~!-GGEeZ`Z5+p10=BZY0nqmloooV{zejvcXI zNQtZ}{@z%v-(Cc=BnczukGL7Xtgs&v;s}z}V_WcUD#lXcgaLFpmP!7(-XP;J>XfA6 zr5KmDRC(u(>C=kmGB+}Mg5&5&`SxH>-mJd;<`p**;7PcFqP^R%eCWTg6t zyS4ZZsFkC8qy1m4l5`ic$~{RcyVTUNLa)o(x_OFWv{lk{iZyxp4+f30Cia{$+YA-a z;o3BHhAtu5lrO_-^}1!-STa0)v|XXAF*q6frQF{6!79&utn5=6m1lwtsgRP3Aa?K@ z)sC%&PjV{PW8NnrfqfUwCVJb(U5MO=@-1XxlVLrkS1wuR zi%n}HdqYl%?rZE}t%A75rx@#Xt)4SofKh$nWJmQ?5;96}?fka+XK(HNP5b}txI*5Z zcueQUWY+#{xT+Muia(AjCvhDzcJlnTn1aEa1mwJrKZZ;rE$eC*;J^;K_6A<&b}gZG zv(FvL1xEQVteLt7`93^L$(_rq0hytQNcwox*!qU7viVncire%FphutlzaCpHCdsJNVX&ur_B@$mGUF9p)R0ooof3(i>KZ=)_!E>IZGZ3#@kqqZ@RM)fRzzEB7Aj4O7&j~-Z8J9;Kg(x)q)+>5e5Xo-Yx?yjPqtTTG7 z^MJbVPT_1vdT@XqEvF9e5pzo!6tWFVC*Qyu8gYCkaZ}$_Kg`LnY*%*}LUoE<3Ydwx zv{OIfB$Gky=pgWeeW1%gJ9d|Gwho;0ad*9XT0Fk&o>$S8D4aYFI;M}YZ6o1n{AX%? zWHfpUp!im~(*HkpWPrC<+uf1;h(Cl51=mzN)G6HW63M>Lxf-Z_r1iZ><;@cIK7PWs ztgWfNt8RDa7a_yWrl$X?ugfNcdtuY);CT{|u^R~-r(dN}sA0+XTOWhZkF`xg_-@RO z&0+VH;|iPWE#l3v;f3(+6((LC)GY2i1HkXOHUFYagj zMC^t&c5e*3+FkoXW@{!M{BsTNoP$x3D&DO;0PS&@hjsLdwkKtYGeL3C;H`EQKMl{> zceEZ>C#tM1ea4|82qGtFite)ya51i3i{Ck0O;m7ha?DB54dU)-Rz4lOo|2(RiHapX zt(t@A;_RJ@>vf^btpc9HO9WCZ5H>^+ka>;n1^3O!2I(D19{A-w^;58NtUmgVoFtfM ze0A5JjC18RkZ3C9L~ZUr!h;nJ)HA$^GQg+K4o2c9Rq}N9UhdD3kE~1ty$#q?-c|lZ zE7AFN`g(k>tlHUw7@t+j&*3Lle|p-_WnmMBnngpdG?!|Z;6%Hqp%3}Zi-Z5VP1&c~ z>a wI|9dZ-&o^jHS%<=b}Ofow>8F4mcf@x#NfEZy9Phge;A}pYVFu@M*vbw<;@` zmL+SJXtvzzlC;1Na=)8KKyKl6zk8u+MdE7ti#jL!^{!X_e3@xnUXOE;SkGmcu}aCq z=sceFX?N!7KLii+4OzTa@IzgKYt=5FcASLx;05=Or{*EU-Z92JYx&4^^@@I(SDEe+ z)2;+NCyahcUXgX1DMtkupSh%2x&|ln8zekm@IrmiW%9^9Iz~AVXPJ2My z8v&;hZ(~o=#`O70eT$B#(()p&9+mgt>K-T4?bxPk?qfFz3C2`x{E4fxUZYG$!>6Wg+gsbR@o&?R>Qj!I34^(lcmJAyv@|b z>Pb-Nq~aSqLsgXV!_gCxWn$C5-v{r}e)Uqvk1LhwfuhCx6(NVj8e{i4-V~J=@~wR* z2)aJ0#5%=I{{McQCN1A4{S&QJ`R(+*@zHNZr9g8S+Mn;d)4B`Qzx12-v-+3%4CClO z&-EXoVekrlW>%Fd#YDoYt9!LBEj&@7(mpTC4BDffJT2c&^=I({C)Z)6pebCUem&Xk zwByM)bCBbqqg9A+s?VDeVvb0-j6viF{*?FVb$ovePtr*};_|JaDOF_g3jcdQ%#)U} zRL#%6k7q!KPj{p`nxHq)p6r^tM~xF%6!4|^Mm2GAIcJR=lD~VKWQ*lpANl?HDHf)@ z_DR6u+Ga{G-j^yL+I?B2tDk)h9f%G~Sd2;551)^AaV-dtn6CmiM9ky?MhTPW+vf)p z>b(Z-!xtlH*Wbw9_(bq6`fQrJOp1?s<-9B4rh#-OmdJaLbHzUK$h)7HdFC`{8hJ)^ z#!06d_0J|m@=BKB*goeN+Ou)+oW2?*wT?Wm;ZD}hc*EKmYh9_nr@8r4U-t4o_v~g^ z(-!pg+l&p#8Yu zxS_VBII{n~D_%wag;#m1>HmPuUJGv5a$srES9kuVZs-9elrC z%Nmuc@$2J-GlTj_FEP|x6LfpRH-!v8_cRq#>Lo-UqraE)?gf8kd;;geFLN22ZBmAg zMsVT2W2~RQZJ;>Mbx3u5|2isQ>Ok~vO>{=3DL%e;t+C$6u?jdb$Gz27mfsrk$ll90 zo^U-%$$sz=*uhqVwZxm^d$0(ls#ay4)cNqe^|o{!JgKM_SZ@cuIq!w8P3TqgHCN(E zSH0u|F#qQbb5xR*OVlY(uFIh5bt3P*2GM%b?)A-ex#lkZ3i#mB5mVcPME)&Z{pz?QC;WNAknx$koQET0Dy(&6>%wDYj= ztYMjcpO*FDKly7h$SnE=OMRB_$8lN=UNc`V!n*Iph|}ypRz=Y=dAjqBSAef(Xe5WO zt)Q*OvxCP|uF=29$@lHq<*zMmQ)?iVSb|>D9s~sSdgZTe&W@FxX8h?dsn^$<9c#l^ z+J08#jm!!06M9H@>g?yA>sE|~<*-EJg+njuSLh*EIo+??-^Y?7H}`|HSlJjC{S;Po zBOrtCmsZr@KWRnVgeKxX^;vALI!@sn+b?_5L~{1+=1;mh ze>!+=;Olwequ&RtWOvZi%O?fCnm?|&XU+a6%_&&uZ6j8idS=VKjW?vPOX7oLvqqh=D5%?jBB*Fn}W7RghD*VJWmt?KaS$XJ4#vwPBE4dNB zV=Xmw^KO}tKKiW&I&J9Hm&5!;iQ#2p?aClyd9#M7MT zS6e^To)pOn#*Dc~t|gAdD--rcF5d21zKOZs3)rFHz0kPy%p?u*x726I(YjK($f7cs)cc&a}!oK@l#k(WmIQKcbnAhN(d z3j@_J)LQYOSZ|#XJIM}acI-1Qv(?kU>AOI4;3Ht4GLp-k5sG@fqh6Jv+WQS8)ejx9 zHa#Wrbl*cX4zqgg{EBARPMD)uU!HpmuJPH?3-G3J2gPc~)=1SplO`PL#Bb?7GJJS; z%I;7_QF~BCF!) z-F$;2vpzHqD~7K`EqV|ujev!-IT(|bf*DpW4pHQ9rL%Y8*UZ@b_-}U-`z&K$8Pk zT*6bmJ~@w-ru-^1DGJC2g9$k9D5M^5wAx0k`fi+qB3f1bSup_b;GNXE6WjqtQVuHh zUk@JC=SqcG8cBKV$BpLL540efRS~~zoO6`o)0Blx93d`1(|{2q##L2o2WL0IkYudd znDgL?SH}f90{GMRVEgCu-`mBOX9k;+Lg26k5T8vsY_!~?KQr1v#)Nu!576*=@P#V; zbaSBxuxuCWcJV?#8grh-p{Z&y1AtB0%o!+IC%`0?@ERQvn4tH!{a)ZBZa zaj(9ZTW2*X_K{VGm*n^L{D0QBldltc_|x-a+u>~1AUz=t?ycXH7ta!d2xjb)EFAs| zvx0l=-RJAg%>Q)f&v*F=selW#@4G4D^K!HmY9`1J>KPjTmkFd_AeIcNkHOJ&;;y%P~C?ytQq7tE?Hdp zpLp=v)w1TUhvY{0udxd4Cc<0HebeKY67iCJzF4&^mMHCJl)k}FQ-u)CED1A3W!*&i zDV5x3hh64Lunk=J@BZ2%I#?MERmQ<`)x(xkD9y9`ahFH|S@-@LYaTQ*f0=_Z7}1f< zR6ApD`tQaeC3Aa)63^Oy0B4KC?=+2kyS-;S#VRfN<;B_~ z)6TD`1EM}#`&o#}v61FP^ax{kTqHpmLv%oToalCbH-(@G^pmY)gxVW5r+xbl$+RG~ zPs8=Ky5q!pV>vHt`ea0|^~k!`@ySZV>8`lwyhS(zoM<;ZJmEQS1~$ML@_-E2?oG{# zM3K?5xA8DeBUJ?sNuqlz$CE!&;nCTpzzGbatgT`DQ_~S=Lp?OFbRYD@%D_8)23nCw z&yTW0>0S4CPn%cypkWAYBhN!oPU;^+2b|~`!rB7(YJfLl z%{nuNUBj0G)A&+~$P*9w)P>`xJ}OUDUwfPMa9*`~m1yo->%P&npZ1_+huu-^Wo9)! zwdiD$#kkcp?yY83;A#2&*c@!Y#dyvfpW`c2S#e_cbO&xl4iAS}tv6gr$i1gymUchh zmA8!#rn^sf{Cvs_vY%%q5Cv&fWR8`!TY^5NF4vLc`kZ!PnC}KygSx_ALRPhZ>i18Z zr9(c^d2j^w0xKxLfJ`0U!PIw|TPgY*tx`w5@FGXTSevxB@KZDXOO&5_tIL$x53DPj zk`TN1)fBPS2WLy&&?8qZ&p6*bGCwC&(VW$TIe#um&!EySG4|Q9G}pUu#_42rP5!>p zG=nQT`@yMX#^5u0iPzSlma>nV&P#ml#qUQ8aEzL3Su5iIV@3M9T+VW1z&{bhTotOv z!(QMUD(o_7FMsG7(Z9X*ZhQsZGjGk%Yj^^&LIlp$ar{nBNP z!J9eaa(~y@3j;osAq1zae`)URt{#1sy}s%uP^JT}#8Pvgd@BA3U)G^Zkc^wKVtsI} zKo6%X|4u&h$M{P(qcWL!#s&CQhT!`cm+GjW;ha3aT#~b_V>O`F-1X z3eB&v*Ba!c^*uza^qt@(;gRU|?VZ1ldvC@6#pY-ih7bgaM|PV^m|1=JJvALmX@tP%U63K z$Lja^y+#|M(e^Mo53y=b6~EX{e4mtte6#mS(Q@E)3H!<&FK5|%+@JqS@!-`X zQDyAaR{J`<-h>!=H7|~ot=yPzqU=`JA8VcK=9J#)W4qB?-=+i@@(P>cv>t}34n&7hS0={O zDVlhGXyJNhFnMX8)|)<>W*`qhCFfq)?8MALJg*ZfvE1m!$EF=U6fnTCF^euT0pyK!1m)OI#28PfU zXz=V~=sl|16LqEKXvh14dI^_n!0en)t+m3VWc2CPe-_>%wQ5n1=)dYdJX*Ra>f2My zGhSamB$iNZ7Hc`D6W3VlC&3p)xY8d;*7YVusxT*C^*J`AU)@FUFEJozBTZL)9#m7m zvbqlOg7QS2gr5FE;HA<PZ;e*{4(yvsrxY834#TKtLf`gS5J1*;~^^-iq_03Pv+_SnxE=A%3ppPvOxEd zqHW@-Io|+#4UJg^Fe?g0>(GrVEu?%7&*(H9YsU=r{pGWA#(3kP!AuT~_!qv=8f@+6 zIq`Ym4~#3*3SM}>?%ox>(PVTg=}GZ{a4^qlbF_zEBlUqQ=l9GfQ9UuaOlR=Bbmq>8 zyZxXxG*@S7)pUJVNcu}X+{@arYC%;%n4`YErshe^!gX{&s4h^WrV5K79BVA=dArPw zO;+SH@{CW3Sr0>dk%zq&{SDWmE2hq_>z^K9uUV@(4Ve-KHzFzQrMnaB`tx07sgOqH zW~+{#8w>rT5#Y#PhZ1d}lT^#GjzZf=3(t+`-3nNaG5?=_2AX1&iMi2ET4~?S3}m^F z(3T@@)Z7ebUw_>pwBIn{a|Y#&pa;Ao?zEW7E;jcuZwo{8?P2}V3tzNIC+qNQVutMS zQA8+T#N75PodcBA%WBfgF-MomN2kNi_IzqAsk&kDrKxPdOWy`>DT}2q7gEbvI(-r! z8`;V83^dAKT(ul2A9~U$lk2StX#p0ZcjYYL6SPL=^n1&Ju7Qj2X!M3CBWcv-U`_594>Y)je##3=d6o;2n7k{EJ@%b>s_*mw`cj$#@BUm%%bD z+EXDNs$If-K^p4HelZT&VeWBNJ>JkC?}v2?Vyf}-vbc`qX${Upt2Sc21g;;&Sb76q zL61LaIQy65pXuE}tK5mz))58G7l9Y$C2eN~jjCbBuzeh@3|vDG#uF|_PixKwR6YhX zXmT>+snN$>9te;#oIiKkB%SBNY`WDx*uAd*V4}H0gz`1S0n_?NW@nm=;uH`ydQrZ zJ!qAJNpK*AL_*$AQFrzO7jX#uuRa(g`FULNc&tPGB~Cer`{E<@NM4FJWy7ac`yNub z7(QnvJat#}$)MU+xOAOb@b+_w|YJq4OgGBkM`E{SvdnqI-*v3;9z71 z@6adltp@}42C1@6n~wpjM$3Iga4+2Hcgf%|9^8w}unI+^sj($BbZ;SF>erOcM5Yz< zN?)t1l_(sE)q7~S3qf@_mS?@|h{-*5wI{jqh!Cd z=0qdmP&zx5VTRRC;5>o-kE6G_`LRstd0~w#`Fn94iN!W(2EK-j z&bu5~;Tn}$?wBI-4doSo9Cs3)n&-f>5)ayMo`b6Q6M<@8@0*It)Di7~H+M+muhezc z9FA7Rn!&5=0v)bB1l!Zs{}MCR+S>19-u32!m#~29*~tE2`HIT2vTB}y=X$)=olkvN z>!mg>GKY_rewgVlUk;3tm(#AC%#$&ed^Tc-!{GR}eQjkPn3EOqKVKn{oXIaaYH_Jf zqo~=5%qy~*>SbFG_5Nm+C}AnjexSOm>KA-Q)bwTZ;iu86vuKtuY!&lMhQzb;^ZR>i z>ES5nL%hqJ(+u@CdE5N9gk`jt_S3_G%GC7HO5!`8VAWJ-$?;qaOuQeI0~hE;^)itE zc@RH4=aD|@a2hg=ooUvp=MAs)69B_CguH;S*yJUn|OkZ z2QxotoZzUax69^+^flq5%kh@ZnlsJE{qsDdoi&;R-R(Fh$AY)&e8gAY(?Iq^yQ9G< z*kDeimMAiJ_AAEXuFein{KI_Y2~t1YETdp+PVUmMju~yj_PN#^0fwX>(Ts4N{mSGj zb}A|#sXfY$h3U-6lRDr}XiMZ-PmY#eFs=I&Hp=)fTV?0h7a_aHDu| zeJNx;s*5l;85=?Shu~rS2L6}Uw?>wf@LYCrUyNt6+Mt|O-gV!P?78z>iXrH{lGaDR zNygFc{>~D!aDNjeMp!w+L3Hung$gS)lU9~q{XW)BwwyIwin)~0hN^RK_qrObL~JEJ zttyovmrctZl{JS2Pe5sQh1`vtn>~&5%U)S>lK!llY8YM;S{VC-M((Re^4@d?t|cyg z{n1aYYi6Z~p;PrgGCEpIETJ+X(opP=10JLZE;tWgN@rfNYOP*YAw3VD*(EMPtC;q} zOnqzKV@zy$Eb$z8!Fs`;tQ()>hei#eLautIrEyOi!=9d zX2-Plz=8LN7!z%%h=mwdT+Z*@M@BV^aLMZUNf@7>Y1D^vv8^M1uQEN(T(9O8#r}9~ zUD9UI{Xr&fs&hDX65WB+Yh7gh@htAe^Um05bWxC1K}m8Qj4ADhCBo~0tLHWS)Jlx& z;$0+AJ@d&`q5)X+X=6azdn6~1E;~@;c#Z4pMAhS{+xHPQ8@y?Iemk(ZDS{SFp}>fd zwA|*i@l@0?e&%=cDAFnW43(*zj2LwL{IpH+zK$rDoPgT z@;O~4d>xjPXiYU5_!hhn&Dz3< z8boIFUpp7nXCIIm>fX>L^!K zN6HMEsZpW35K(+zJ;$%S)%<;+$HuKK6Nz(GTa{}Q6%s#mGvUiZ7v)}LJJ2rFqs2+` z1Tv4M9A)l5%HFjR!;%*4AABi&qq%^`v+FBJ^6)sZwa)E5caFSo#5lm*SE>2Uoo}Nj z@NV4GcQc*^2O;N%0J@L1Gx1hA)EP2iXDPdcErPe156!HKeqy_3GnL6g(P?7Z4!r_>h<5f9K`QiQtX*6kidS1BG-h z@}DPb$RgmygKf*Xb45L-9l<9y#=Ic!_f(Kfz=QA{^NgH3`eHBp%@q5)$=l*%AObJ; zWRj0g=iNIuGS9^qW8D55Hj46@k2e%qkkN1!fa}^(k1jw~B~Nchx63>6o9rjvvFNM2 z_>0Y%o=26`pVwmY%iUE>@f3CL!&q&+4@T)?4W> z9KjEB_(}cE-FaUF>#UU=;7L|68ol;zjNB<=B9GL8NSxR+MkQhtr@As`D8HpQN zz1rD#p6jAZ2+t6`1wGV7sgqZII*R;H)qR|sm!5!&=_>#$+r&viW&4Zqb~vI{4!I~m~a;?Z^E62FQtchGxHSIrE|QWoRZnFW$D=uO3_)XcT2r*mJJc4yTi>O8Deh>-gSe;B@ZOP>wC{K; z=1AFGye@czcbV^cU`hEDuT-P*407{fg9Z&GABue-tiV14>_P#nI1Lue_Z}TH}(76jMbkO zr-A>oa+JuXQ z@-2}A#SPXo=9d0TjeTZa-@}pqF=jJA!`S4(WT%98Vo7{#v}YfGE_q?|USschNusp| zcte^?zPot^TPrM(7p>W3LvH;@uL~M=8WSe(aJP?FrCWdj?E?q#GjE)SZmIcFXUsAd zwBIqUb)Xxory_3f^A{$S<7x}AN_F0Pt~>XTa4`OT$k$lKSwp`f$;fzmXsQoRV*o5< z<`l^zI$t}+SiKcDUWf!)n|2&M>@a99g5Rv)=AatA9V+pU0#jIU=g!qv=~*uv&-z`= z%ZPltnE%tZzD_L5JA~%&JTi8kxKzeZSx7S4NIi0mj{j-lZ`6uN3Vf?G*c90x1}{7h zz2RP;%;<^7Yo`GGLQE?iICf~tZ~QiR6-_qX=I-?) z{DEHP1Uknv%8iNJ;biv)k)Lw}q_>nw!26^_1uex|{FOAF)verT%AEX6cLxxEjyl`^ jgm_l{1G0ZWBQ1V8{&&_JKN$*fMLs54Kjn`%tnL3FN@g6# diff --git a/DashWallet/el.lproj/Localizable.strings b/DashWallet/el.lproj/Localizable.strings index 2d2c3789f2eb6baa6af22145de1a2f1d4249c6a8..663986876e2dc6f463e1c76e0cd893c18b610539 100644 GIT binary patch literal 94894 zcmeIbeUqHkdFTIsUd0_J1f0@JfbCr`+20x=9;{si3KFKA;-A(`OVid&_oTarVe%J# z5XM!;Ra#qdz$7RIwtrhCpoo#Nz*trL0(#yNDW%rhe-L=;^-i z^KhN(`RSbc%jTI;voYG)=?|yP?s)XlV6(s3Tsmm_!z+Vfzdt_N{N<@X`BL+x=KtZJ zFa6b@{K=`mY|e~F`|%esGigSbo98EeAL{htuB1v>EsRcCSB~+NybQ_TJ$SXSbQm7d-oD zcAIH`z*iqMS~uU%KH$U0v)|7?Z20jN9(Zj7;dBt~9nKV!KHu}xk$I~hV zli6p!cMg~{c@HSuWf?LF13Xk{ zp($T{-01&B#f$6G&m!wrKF9JN7EsQN`@N|^@_28;sc!<6!SuiZV-L)JX8h!i*dl25 zo0);-T~>Gv`uedg1nOcj8leh4Hhv4A4K1a=dLc&u>7zf|8oy#d>J9G~H@-5MfUM14 z@K1-5Jpq)Q?*JZ7PrT-%!)tMnNEjU?u=|Y&RV>-~_2IP!+40%okHxP6{VVfOai(Gc zgNX6!>IoliuI|`d4+CT-`~AJ&c+l23@wXmq(kknC?ZaO+=)@>rb@<%QtYr0qzmiwX zr!|-SwPM9zTU_u;z&Zog+YVXY4am-J-4jk%Oq9yF#qc(on9KH&g-!iQM zdjAYL{E6|xY7KCm6`gEe80{hcq^E5Uu9`TRvS_4AY@jhEV>IT^scnKUdedeCVe;Ok zelr+0gDJvi8-?i`YwZvWcn^UH>sr{~$!7Ksl8(RQiBFApq3TJwl12p|`#yr{Lw<#q zK%Y{z>{lD;LjzR%i=ZaO>qo|S;FW1w#z5w1k{H)ixva*a^=eT5c7|oEaV^e-<6i~d zu7?&wfwOm|Com10`yO=&4Hx(0yZM8|M0)jYJzb%UoNhMzFAdOIcYBk`hAb5!W4|xs zhVz{CF88nO^~PZe?4$Dbrze}IM)qTWDq)SiFy!IA@vt2PHp$zv9N?7wleYfZYl09` z1(5#;Ovc z`s>09!yS9-niLfoSbF7Ex4!uFy1>Wsz^1nhO+G{sXF>z9A8OX+8SpMsv7&!wVZRfL z5$v(zv^zrs{`!nzq@&!`e5}P~8Hzp=o4VYBAuZexQ@gg8f9S(%1t;TByB6i?=3Gxo zz)%OLQ=2poJ^0{*uC-vuZH%_J(PGBEZ9AiW4_&%=gQMQqgGh(d?E^@6pL6Eed%Ng{ zFAsKjk*_hicF{5p`n~boF%|=vp1}6uJHHLs-6>WAH31h_IbKIqlyQ^5B%9%Pplval zu$eu72v~LCoPzxYX*oTEGC)wL2m+w5psDht{uaDHu+zP!h1-jo?gOnLfp9GnJbh`j z=NkF!9of0eDDymtpY_LRc3iEn0rf`>ahe_O#B<${=p&HnhqL#WBZEENTr|JHv*s6A z@+zxsr?aIPW60GSI*e>?V%t5*UI4lL@k(ph& zy>HHnj`8PG5FZfK39~Z~P@&Fe>hEy-VAwx^QGPdZi8t&R;MPWd;?lG03@3Jqn?!^y zTzz+|3>GplwUJLMa^{TDcQFg*M?bk@s%tzj9Pn`6v+-@wm+(rv`w?*txk-MYoT)EjOf4=(f(5}7t` zaG-ZMlpiwY?}5i>;^o)*;#C+qS~^o!Ie$)H8TW%j+NYYmZg2ZOf2?D#+S~Saa(qmh z{Q-ZWlcKFmaO#dF0;SV57z+I%TDa636dzPE{J+KrTY_p|KdWE+C!34-*GBt8WG05< zbPE~lT9Xtjj%HW0$Om(IG;Vf!S2bR5c);wFX>+MJ;i=x{clIVzxr8QC|Inoecwx6j zxILR`?`1~W#Uj;xTnF8nH7`@kjH!oieT`=_M)NdNby$dL2yg\Dk*9XB&kbKu{^@t_cB*e_V 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 z z-xF6<4T`vdxH~a!{+h1><@S(2qJYGn>fC@uHEVNX&eW$$$Ake3fn2d+*j$|KDsB7R zLsBgfek3ZgwWioszURAo$=|NG{}%h33LWOl$xD;>wx>w^y0>6>=WTHhdIO$@E)s^9 zE6WPaICbxG*oHRC^!kCHd`H4?IxHh}-Rq?~QPt}`9PRtp$;C)H;xbQ#mP?q_FGP)| zd#Z<)+gwm=E{NPJueKdTX)B)&%FVg1Exvke4)+Tiv&KYQ4VhMEd(BOy>iip%c)L_; z6(xuB+FFav3E80?LZ^(1@+f~56(=nY=i6;-ch8O=r#<^Uy91Sci0!CvURZ2<)NwjR zClr&XA_RF$#DpFi#iL}!)^&DgLX)ZHO4ZMzM@8#e%U*rLsN?P^|ZZxK3F{Hb+tLT*NV zeasxW6!twa9Y?tOx6Mm|xyt)vpR!X5Zlhj;=|FIMy+&+Ys(e`$y%5I>2m3?^a4`3L z5%KU8?6q7v9{7cdoZ-RN6nsNUS~r&@e5=z-*2Qg4GrL0>H8zbqG6ZwK9lB!*VN#xC zprNG}(ITikwBOwT_oa z)}@FPP_6OlE@-YN15ddx_4~#7bJ7Oyc13tio3b*51wlbfk+~*FDa~slF3w)MetFKb z8K6gCr5Ezyh{h24{U7n~wwTU%Tbw*m=P$Xot&6+9yXVvS?nOKjkScen>US!~iChuW zCNsd^C_3%BR?Wm#B|4gf92m6_@b|&;yRPS{p2%D8>xtMuazxCQVULL{_fz|oDe`=N z<&N4)QS_^5;Inv2_NaRrE>m=P_MDX4!5e8j&L%=51UreA^8wju6Y*NjJ4(K*#C*RhqFrZEqy$Hy%Jda=<_HvK?)4F_HW9^ zRd83^xc!XGIiWuqbcNSJ_4Y9`-YhL3_3E5tJGCy~DS27#N^fCCM z-5qn=6qgNG?_OJS7_8Pkz}RU>rngEQhG{>}Z?$`>j58ciQ)+5~`EUdOBNL!eYVYoe z-&!peG|AVd%I)K0{PpNpZQ6xvrI?IVS#aM z3(q!9IHvAaFNG^lWy?cf$OWHb(n7d?{M{GyLmY*g{)dYVA^U5UoIX|rr=8G0s~n1^ z+iG22wcgOKV@y|#+Yo+Ai+AjS;WLI9U+u11%B&Z|_(2KPi-9G$gcvTi7WtYMFYA2! z;i!UON*X5@A}3Pna|$^Tk(}eew`f5=Wp0S3U)1MIsHWp$$VHtr%|v86+OZXMTBZAb zc!uIAXf;-h4@+J0CmZ@s=R6=e&J`KM*);su;$mFS*8NlC(?#wGoF55I(Z{W^6gXG? zd_=9sGp<95+POE!+sV~23!~1~y&QY}o5Cq$lR}1Xp7KE2=X*+^Vr$fkDT7Y_oZrAh zn^6-SRKMpL322Ax(2$f@G@H69A`Vmp>T~4M$0h!y6LI>i_1H@6%k{7;cIfCLR4Pc! z^Omn!!)kPp8xsNp}pac1L}8^lfoQ z&M)~z{Cqh+|HalPCpUgouHu@L8$a9n{Y?BM>-o~g&o@rQwLjl@bpvd?T6{f4-(N4k zOD=sqv<+Eyx=ztZZ*PE`;XKtbTh{qq>OR`}Jm@f_o;=LqTh#$8d#8VwJUVP;>}V!f z2{Jyp=~};xf7-lgKl@cSKXuPJbron+zg)g6!bq z@gMerx_NzmoOys~27KVS260xnP4o6-S@HZsah|JVJ-~l2&sx3rEz7v0= zdFP8--VtjdCziN;J|Lm;vG+g@N9FL%#gpY8PH-Y3t(jo1dS&-+jmnt+7*~FC>))*r z0sT)~|J9M7WzT5iYOe&gG+_~qulfScal5)zDceWG#%%eN3)G`Ib3DBA4>uLzqu#YX z84~Na_K_VO%UEGnUpr^yPHwsa6}eYs2h+?F-_+8+7m|@^Fj5oGUXg2^VZ=$3tQG4A zIm%z0!O7oHil?>GLqKdXYfy(7K5AO~YUAwkv?Yg(;YosrsH5g_3J$)(!YAi-CH`}a z64(`?9L_=;LgM|Pqx|9cw;8th6JaxB9n)+3SAl)m<~N5ejsKY#+sQq(b$3(DAVo@J zn`lylIGHIn?F4sr75g1O0DeZweIq`*Gd_DN=$Lhwc`CmPE^~ac!nfb`q;M09N|#4g=)Su7XfcZd%-I0|4aIDjurI? zyv4BsJScEQRnyVf<6JixYdUwKbo4OQ)||eGAD2>9_CZ2udm>{cNKT#sU*zc>bPZ$o zLz|i$nS7;4p<`swEAa?y73Otb9&{1!jWsjH1DY|Zq>AA&dF!qd5xP|ExbO>cxT|rlabwd2bJuNzV?LU zWb8VW?(?_v#JBb|rQLz@{@6YIdY?Q4HsIo+G+1Q?bikvfkjqdX{wb1EXN;>had+0W zKh{y71hoqE!+UCaTQ8l!HFY=1$>L#XCUtM=^p@`G-JMXR-ZW;1Pj`BrERy3~(5US* zL+vZ(-p9>|u)#;J6(%-zUuaM2X0V!S>g!>2ZA{KW zfqN9E-Hn;HHH~Bx&il-oh&^b^Ki2Lu+pK08oyc}$lE%5vTAy#sMwIUkJAhJ3I6GM}%?oZ>m(Dm${}2fusHJ$bg+Bl16z z9ykjCJ-scmYdMjLa*bl*7avk*B=uLfI34OTTdmxO{~-Jfb6Y+) zOtD*e4MY|a24XavIl;-k>ZsJjFe=|P=k=4onLSG^chxHQ=bY$#0&FZ!M!p(aPv=?S zhx$3~s+PyY(sCYO<8!f!gG<*zyz9+a75=eKAtyS~*ECN>yjFX^a<(ltU)#ba9*`ob zLpZ*eQLeT4YGeQO)++B+TL47tsesbCBcAi+Xa~DLR+p~0Law&m-F0$OJ8$D`M61ar z&-*^|B*Yu262kYkh38mNUuBmxa5kh5xD%9MHmgYSbE(;G&jM0>#&VVDQC*EU_d1Cf zC?4(khe#7V71oU0&6B0UMRqdl+j_oM9ZfhJRAW5oyaCM-B;P;=0zK#R+*%AMvPHar z->$|_oeN0^FLfnU(6m_TB_#E&P>p)wn59_rGvBbc$^#Q0c5fLWqN3_G%i z7*pli_Pr2P4y(FiDr+*;nSd-t#>mp1uV<;QZN@Y8`94$(xYD}b*Ix#Qz8iamrXX|Q zp0PH>-PC^VtkSxMQk--k=&+7K|1x$?aM4L3yM{=yxTTNhPC->uKAbK|HopG(Y+hEY z8Vj;AP?@=mct9;T{##iqlS|N`)HvXI>g)<>q7MiBzfNsa!|B+fYqOC4NEO@4>iEhI z($oS|tH_xmbo05U>O@dr_HiOLka~VgE9>T1Yrh{zM`=1Dtgf_Sx_UxIb>lMyAMzh$ zeI5w;NvuJgQjT5|b0rR~YciG%uJPv;q0iVgch@wL)bsla={)hA9u(+(cxQR%cwU7&k6|yg(f9zEPI0Rz8m`o1AKD zimdCzY73yP944a#4n1p}+B34q(^y18ae|Grc36LsWX{_WWoMd1)|ygNp>JjH5VOv2 z6Lx1>RE>p|IkYpV3dxBq%3AV&v>WHBpBu=|Wmya8kEu!UJzjX$`hs1k&z<+O9fvv9 zz8eEygX*)d0v047YpN@S_3dk6JE=xYcv)^k^Bh}{yUI^=&<=oAnZ#P;Twv_)R)owd6H@{4_X59)UUzb&42!$O_4FsY(ZVstP^P3o9bS z+fM)D$#cmXFrkU##9EkAT~FZr?*Xr-T+0Tx6wsCP&Dy-HP?>B8x;*Q77oKYI=cR&_ zy>B&eP8-`?KqWb$g1@LmWI|MZ_k6h3r265^4zGdPWK@1muR?Z^s|6h6k{6=2Kv8t7 z<#Xm=4>CbNTcssEMbNobOMrJ$=|^@^0BxNmmGqtM&!Hqg_Nr6Wu{LyVgD43($q+Co+IQ$FF28n>WNiD#o${p~8eUh-D%`b<1oys#!MH7})f-XBH?L?`m z0vp6K@FV71IX;o()R?SiD$jx73aq<23Vtdu0$fZ@w#_d2gft+2_nT5xr}`0DKX~cG zckIT>eze0?NLIa6RssDnAL@K$bNYSg<>l%#Qoa}T&spMRqp%<8v`u;&|F+hZ>y#ae z_(A{Q3rP0GPcpsKj@o`C3x;Q>({`YV>@1RJL%hOvKUvax3Icax@l$u`t7@D7S4G;q z9kKr8d?v8UgZrxT&XvAh>VpNr_Zf_=(Rb&m21=Y*aOHL&xep*NDUt~ zed%ZP9>fEI18|e|bJSR}!m((-qded%wJe$!Us+bVvy(UWoTMFM4on(#AFFXxp8@n~ zU;~Yfe7_}N)8AP6!q{9q`Tf9^>M#!mjkv}V$pSq=Wz5Us^f=-%!VlghJ~h`rviQex zZQ`=c*m?c~c^IBnV;u@as@JjH=fg)sjzLLK1hR{`4D)co23aX`P{ebLN9!c2n5pE> z*n@0LsLFGx$V?FdG998_@ntoODC?y0LtHcTX3)KJC3Bu)Pi&UTcc^*!X|%&DeIBjP zt{pp#_WSW<;z{TiM~2Ncc<-kB`Z~P1e_zSLp9Urr4=@IO#XPc5u+TRN#KdKxQ7BSW zn{;15=(6Wnzydm(ZHzDl{`6ThUTUnn%$^)=p(L@sKgT zWcU6)-m5$f5Q5h}BBQ=uzJ;q2>zW3dF#n=deo{$1ANQ+r9ZR&#mKJ6mbt&e`;iJIjm2Ae?N5g-4T7_6bDfx9*^R5 ze;vDyPm`;;GbkDPN;SCq&42|tpi1Y>Mcy?Jm+g%=?Fp)dXULKybn*clL5F89%|;DC z(dGg&hr@T7=|_gaRYar6)}X2Iu_Pfl^^oh2#HX53h4&??st7Rm=F}aYgRQ_*;7WJ_ zi)*afbPOMx3#x?BDyk4rlu`2@krd3`crT=eBiqNKLdqCN3XbMBe4t)HfAT8FlM${S%@^&wzj%Hv2{AhYbAqOZyttfhC;ix4HC0c52pQ%8Rf-D3&u z*}yvXf+jlucHk+jkKgx3e#Lf;=uNblx``YX^j6bUW@VsqRqM32lkKgf_Pi45;O=EC zD=Y>gLgE6tU7R^m#;1+jp_K&PI9Hhq%_Hzpu4Z^@=@^|EdSHgW>bsmN1zKflhwHEX zk&|0o;!`SZA7T;K{D38h{l}?6bZnv}Jk!FHpdI%gvkIM|YTvVb<`eU$mA}C*RX&Kf zWAXQv=y;rv-KZr;+P|_r`c+|j;OV>PPWiI*kn}y&KejJke?G&o;Jj8MI1l3P#|y4IQdb`CalrhY5pKR*sBfz`^-lozHkDnO~H*W{P0#UFJ% zMYbZ+881XssLzNC(S@YXRD;@|TAQ`{RL~(ou_x>PiXn7!>HAt@EyL^4kR-CS>Hp%~ z1xXe(q;%$E@riO;55*oRi%8y)-9R5Z_ndQ=kuJ7%xWbaC&B)50SBz9Pp`)kcPvT9g zC9WmM@!~;_?HKWLq76FDM^mq~dw8JO`9%9vM=V}ZB$nSli0`GZScn#5;Jd z{!;ccPcEevE4dxM9%hO^2ujK>A=MATkv^{gz8-t7%2R^6=c%$$H zy=rrL3wy)bqkg8H{E-~|PsL}$FKZ#l3OZ=Dwi*;C65SDFhR&c!TZxa9Y{cQkAH`=k zMW@!c^?Y<8}NFP?4sa)Hy)eIkj(mj2<|n`O%<)F(R`VEv>De@|p4cSAQR zl*yFvc7^MdzBTN4+BNJ*TvZe(zHaUmXVw$ww5odUZo3{;ndFM7w*Rv5hU9NQuXk+b z-0iUkNOs4VmQ~H}jqBLmLxp-QeQfpGMuD=h(x8*)3Z=nS?OTvD#I4|C#a)4cp5&*u zd^Y@7o&uj}k^xu9&Ot*7tN22gQ$^S+RfxYz3Ywe=EBi#_P0O3;SRf!l;DNgUkufr-Oo}0uSZr%O@+|f3&n0EraBfi$-I%` zrrXh==gyA7hb5$1yBY@8#8gU2G1<+WR{eI7Y}8AroHT_qGoSu zF1Oa}NMp)8awfTtj$$*r0qa6h0@q%7XYI{+&#^SstD#Ai(32)WkJ$*lZ7}8^+!s8I zE>tCqqmz3Blj0UKNs6A-^8QR<1gw2fY-62VHzu>!sj0H|w%4XPp`q22tM+WMTH*?- z2O`IT#Hi1@d%kek$FW{GT6!7kyBfPkUT4lc0ni-{iS$shcn=0_WUjDY(#KAWU;#so zSZO-hK{ed+8hcx*mqM%OPk{=_OYXAnZ0kR-J({%4ta+3p8pa3EQ+0F%Ns@j58kq=) zvLA{B)!3+`UWSs)$769%!l0)l=1vBGVt+~c3U{aCFC^RC^$&?P!6^+!eJb7IPk3X{IR~17L$2>4T|tT56Uw$KPTqOS_)+N}610T{kX!5*rB*IC0S9GUGn|9W^(MN#lECSJ)A9rPH{o zC$YG4joDy|e|jINxXLLm^_zQm_f)u7ua=@G(-80kU6WY&r!5Kq4#&kl3++rMcg}fg z{c8Hou=@iK*#(&Jj$!OV$Z~Wo(o)g*Jmq`7-u`|Ce-qO%WFcaeE`u0LvYsTu-`w5u z#u%lgrbm-_N^QcVKr$RfCp~vR3 zm4uGMN>n6O-`J`}+tQnewQYMy_O82tk=UPa*^Tz6+rCupRR(tzn`j-lN!@I`;G4+5 z#uKb}bzS)QG_XXc26hQnzv5W6{+BkuS{dS@_j^}8C3$bqhjMa`)~dV0UiQI|>+-I$ z;?>{ni_Gc1xVx_vA#=iK@|l%!!3RE8ykXftvH|?0WpD9Z0o~V5X+!fcp{?GLpB3(} zYnq7cn^tRI0Z*joKl~WzOhkf?r)7Lj;$=g zgSYQ)tHU;3>dvH=-b(e|d2Lg-aUCK$j?Lg(DaxdMmkq`5{N@-HHhpdN!<%iKjK7%;DrksBIWIAl%g1w2KPNOG znPEt&_*1q6StFY}WsB@??L~II@Vpumepcc^E0oNkW=@eUG%PvV%%m7%@rH6L!co5M zFXL_duhB93*e3`luYqq_&R_#=tQ9jEfG7~Pw}~ai)K%F>iIV9TJm+7Ljd!nom(tc2`*_cb{qf zT00Qc*d)Z{`i=TXt_F|8z)8xr-5A2bDm0rl$8<$?U#!jX@nL~Qn z0?TUkR*KdAJpU>;EH~Jfn!DMVb;u2PPtWByWC7fbEdj41&F1TU{eMFbNrMcl9MkSP z8P=$(v_36RNov@Z8oRTOU93+oC^JjEqwX}~FDz@tc#)jQyw;yyHw@H^k$X!V{c~XE z%fOO41ZrqjpYM{lQ z{(k*u2;_Pm4SU7$-!>YtY)!-w-Vd8wI$sme=@cMK2gkI#t=u=+MA@oo?b{Pdc#zq2 ztjf-;V<6M;5NUEGgOe?Q3*MF1rZP$Mn6MdI$ks=PKM07iBeDMO-dbrwd@8IcY&mhN zEC;l9J-5AuY;Qb?co7|a2MgY?t80oJzR&rijA-ukt?R`HK9457n99(kR5!8`@&{GcX)a&H%b?oO(qYbJc&9K=BYLOLC8f#l8~J3 zsj(^3&juw?T|+MyQERG^kT*#77T@cX90?hAQfAx~N2~L@)IEP~QBS{VA;t#7wn+cQ z6E&UZ&L`Bq!IY)8vq^`>F|r4V2c>}PEcwS;1S2jYaw zq5h@!-IZ~QSPt*cm0S0E25RfGjon%A%X+B{xbF?xANH+X37-iIL3(yhN(Tlk6l}!I zQ`Ax?ad*7<6fJCZwuU%jd*__ELqB21BBiM8Y-f2V)F;EzBO1yC?N0(iCX`cIGQ49q zq1|7w&SY8jO0EL$@SYlCG79q74wPsc+|N`o?d^CCdadvC>V#!%$6IP_-|}G4kZkwO z#h(T|%Da!h1vuZDCQ?nWlQQxLKdfZlT6v zqIw&8C(_1y+A5cEh@TJZ1)D~3LhR(1i(T_Z?Df^os^N{~pG~j&J8|->*dyp;&NC5t zr&dW+#T5{WIo7pf{Rt1mUxs(iwKtECyh}W-c;UYdJB?lPoQc{Rd8KFw;)F=7A1_0rl9>-Pt(k>lhW>J5gLT^*AqzrLG$F!nCZ45KpwYkvsu@vw8qY!W*XM~^*a zE~*)Omu#3r1=>0m2BgHhsBu^oQhY?}c2!$!NkL5}^;5_|>@Lyd*UMfJoq0Uu%H!qi zb?3WU3Gi%X$)5^%s3ns$Y4Nl?=7#6N`tyyP>GkdSR8#m9W<}g;8C2i%wa}b!*4SMs zsaiP6J^McN4VhZylBu5SqwhDO-R>^Z`H_Or78-2A7GLY+UNo67qCF;_&aTVi)4p;J zq^b-Z@vQCH-g_eg%~KtfE*8CT&2)t-@u0Y4Xj^EK6qGaWW z6t|%dKy^MuP1_SQ6!|5_Pej<~7G8?y=-eN5mpJGDi_mjEkM(@nS6MiC#l(oOgxnpg z)LKrrH7{U~nO`;+-`bW6PZq0&nl$l??%AFK*GU=5z9cQ4F1Q4u=`6(h->-PJWD^~s z%i)pd^n75?Gpu?ZT`z>#@yHZ=qzT=rfB;QB>=#g#+k6}??*gb4W!MuCa<*|_Lq826 zG^dIYGGfb4TwfW+#JFRmmg|d1#5Q52AZQ4w8*Z7+wf@LT<2+ zLQO@%&|FMN&oebrCiv-GW9p$R)w}k0qwnSFtdb;-aXkLHni09Ux%eNQwpbr@e|HVK zJu5=@&jF7m*zpdodjhv;8csE-@sgh==3kDLQF*3$q+`;#<)=R8z54m1Kqa0g@Az1F zHB8?o!Wmnok&1$TGh!dkex zUGu!K02!TbN5VtEdyscQXC=`#K2apl`S!XJyzk<2g#wmhTLI&hvA5WJj=>SNB5n^y z)?Hez^JukNzI#3l)!l?IlzxNuT|=3bbVXf5)=>|`Eeq#iGuGEaMI{yUI#k82`VHdT#9wOtJ%kRqz<$xg{YAh`Ezouy zVC#kt8K%02I?iHP-w4QjLWC+vb^_~^-rZSlOgu_Fg^pw_3H+(Mb_6vR17~=u8^u%d zdMjp~(!kNTy)m67WfFfaiz42L{|xj;0!#Teyh8jxyvUZ**1J+mvg8nT6CNx)P(x(8 z!fOR8=v~*=eMO(dr|!BVy2JFG-G%>w=x6*vg~>6vbG5#DE_(;fdp){h3~JZh6N^&U z?(b3`XkD5ksMwk-yNR31|AyODO`*x5XsSICVflW1-p`MrqWM5b37+fRko!#e96KAG zOJtTjl)6yW)06qhVa}+I)flUuSgL*yD4BXrY+uEv;XZyt&%9oICVeg+CD(v#YG;tJ z3QhE)53BC3kgW~paK-SPEbs1c60NKP`3&d_#S$f$l>MNpqsNLJ%R>|FaA~+Z> zC8z#S(3qw$)jM~5OJnD$+j&p>mi6hfdt(Z6u2Fy0GX37ruS}-dT>M3$Mq<49ZPd)~TB*mz)&>RA|qMxg!NZ13xLp9X7T558cTaWJj zaO0M87MP%s?etdA-)F^wq8e8+tespSNs>J!Q*z5^AX265`tG3&%&3X_-iSGQ8mTPz zmI}ZjbpcpFEoS{`uC>0leaf(^2%Msa`e{Y=Gd$T^c641M)Lu2L`W&8e-rhk@q|HuP z{w=?5e64ta-W6gL?b_GLN88UNS6!JV_Mzo|6|}%aOiAUw>!6n;L$A!I5iy~Y$^`1Q za&BB=EkC<2zE_VnF^44(HfJT{rq%FA-WnVPo2+d38g}Gp8&-w7kylE&v)B46@ z*i*$+ob@DYql&aP!DQj1GfUg|VK;X${)yO*}|4 zUyA$~JI3~^S`Lk>eIbg#(}0GYIyQ*r4cAk;9B1O)o8f1uiUTcB$LV(p#j+i{as9Ww zgiNhy$W4FCAR~|5?sR{5?M+zDPG7U#yYgx6Ti2(ZhOpgPxUY^=40+1=^s8H(g5=*l z2<;W8*I3su*52t9ZksqROJ&glvDvcC6Nf>TGC5_~nyM_YEN$Vx)gMc04?zoIuYNTwT|7 z=x%>n<9?k|kbDL4Gk7;w&AegGOs5)&h(jwA#{478eA)c2Ok=H7Jk=QxqS4s;s$FC1 z$FPEIl{fg=3ALZBNg_LkIWu#$?jJs%%p1`+CJN&P^nS`Z_lMKPk+)bx&~y zI`lfp$Im(!Brp9tT;JP{xt6m*(|EOdejSM?Yo=(XYAjUkr|E>!ACAf^0?FJRu%PbU zHX0;b)A7ibqx=17r@#$;yP99|rDyi!kTWy<#Lv~`x^?aH*IUVazWg~n0RpEG@ zLaXe#R`Xif`!-|O+QrBQMJ~qYceN`gyS|eufct_9a`n_F9*%evy}}u3!e6YPd9bXV zPxC~KTsKvAWCA`YksabXSo(e6P+Q61m~p=rGPJzS)1Ku2=o~$KAk~zp)&<$42~gBe zXl9h%xcP_YVq=efam%;URpdH3Ct4bCcfs(2KKvhC;HZNFtdI{n6jNu{NTY-&yn;} z&y36}EI8$vz$@}h*wvHJVUkESwux-xr2$Pshh{k#Z%m5KxAt8>db;9`&a2f{$(f)_ z^pVa^6gPVM7m-PLQj=^P-$JhGI?1Z`^c?96{ANxQfdVwqT(S4}LJs;9MK$}HvoS5D zd5xZ{WvUvpxbdHP6F$127))g>FyBdo`%EzX<5pST^ zS$NxSew}$hKCp4ESvJ(ELyN?2=j%diWg*s@GPw^2&cVWD^A(YAaasKKHR5j#q7?hfwJRlonx zwOZ61-|AWEUUatSdJ;8ORuW5?sXLmPjcg|N>QkcR=hgCh+~;AAXC3dXcXYUaM38%3 zhL6-v?te zo{yn@;|y%-QaH~BKG$BOA^tq1!(7&hW}Fju1nalNzx!e(2VzZ3-p;oJG79?6%a75Y@*Ue(=F1vQMAk6F><*>-ZZ@_+DRYbmDFBlY$=)}$;hx*m(@ zwXpMTSGvZ5UB-sg$;r-q&6&^DDyUSK-R5i^SM9X4fm-YKB4$&Na@$NAi=kNCH|W8%uHay7oigxFNcaixLD`%8)9a)jvIV@Rh*Y8zJF^x2f#^Dlz2#9qU~|(kC9?FuJ0A^E6KQJ&kfI}jj~$2d=Y;AkSB6S%oR?z*}| z5_rxz;-Y(FN*1{51>CLp)1obrbE-%iLhU=|qpP*hr>tU3VKnEMI?Xb6o^ODVaL2qGO>x%li|M;SAMhD zZCPKc48T~AkuwD?OhkhC##t-srr#`ole=T?KT!hu53u~zCFQ&VISS6CCW83kMjykY zHp~9nvb^N|s6YVd$g39gP znY9f59kC}f1P||xx6}FDMjE8~IlIALgyZROlFkqx&S{}SljmkFNbA`+J5^&(cF(!s z&diqUJb9`pe0D@%558W@)o^Vtz8gD1)bY{yhwebm4WT98n6N1qPh^3J@bK<>$JY&a zTa7Zbe_9PP6}`aApPHis%^>HI>FqURcu8yV(~zj|hO}h|=k<;ZoxI{Z0#gnvRzv_Iy_Tpy|fFR)X_Ri zo|NM)Wvut@^VyEnHMxVyb$2?qhI-pR)_5`eUu;yL zI6LR6$i-80lW^lJ%0AV8&ubm}{cTLTg<#m3wZ5I@TqSIhT>qI$%g%*)zm<4wo9F%O zps||9&lF0i{S+)xRH(<>OJY}$B*ER%!wmDusm8dUj+$!*yDzH3G$wb67<>8_=o?%-`Z&~NL?n8DQPs_ zBOV2_o=SH#;wntfhvK>3IYRn0_+)!*%NryoLIxZ!(bYwY@c`$sCVKN~;GFZl$o^8b z@=1qx@~k<92@6J*5?rV1Vr&Jdl22Mai2-?@0IsE+sNDJ;~_Goo(-)^J9b8$580`loPmYV&u}KnMcjVaNw!X1 zy&9z1O@tR&XN1Nex8!OkfBbIO^FZ-_;Xy|MRV$qtN90-d#4O(%uh5@IUF;z` zwWdu?o*mA3T`yJ+#`;z3T3g}AW8XdvzW~iag~*)yQbVvA6Z_D(%q&id!td27)N~i) zo43%-7q^6uuoXlL?P*)Ko5>8E-F)j&yS({Dc-u;lp%?QXO%*c!kx%o7NDs#$gA_CD}#wp+cpX4y%Fx#o7I zHGQ3q_zpG}yVu+9#@!UgYHTKsPDlS~p0VP4=j0;n)qMZ)Vxx;1S=rogwj6W4Ox-7y zp5T*3(okEd>N?kfvdgl@GfPUG#I;JyF5h3Q^qO+9xzi$0)>NI8=&9OQg_ktt+a7LP z-BagFe-_eCK2Pn>O;!3hSLRN^<#7t0OKq~Ak0e7=y4vH5pct%uUGtrQ`D~GP^|bGL z{pywE+%T%pxYM$LT50B#@usiEIUQ6IszT}8<@awCAET8@!^%SVmD9LAi>u}zc}K#c zGmCXaw|1IZ2X#!D1F*B}a~u?FP%c}0j}?waVh^E;x}za=cGAgM{W-ZYq{r|iXnX1W zG|eV#%6qD#4xi+5M1qM4LlIQx_to5aC;Lcl3X8MvuBf#GGepUg^F-S{$S~eEHd)Tp zAc}D0Dlso8ZYDhghRWzwM_L%R)dyodM;r@f5TmBo{ z;&zVJMBG_f7$v~6~pPKV zO>mVKnde*rJAB~0rYg?AS$Zm&2k72TJji;lv%6EV9#7A8^wnolNUv&^8rI!~h^J6> zKwbMcpohfQ|^f-AULx$XHWaSphNgTUYESX??&Z?NQ{zk{iqH1xs9_ZYUv^rFo6FgAM#hkzxrgJV@30#rUVnbyk}{7tq$9*SdC{Z%4BZvjDPukN{NvUkbT8-|1WXLc8dkyFaRU&lRMewbfGf z?#H|3RndWENr5HKt|_ycG<7Pd36B(t5;fUd@Exko`u$tc{@4dJ#_B8b9b+q6CzWdA zAKhNyoVt(Nb9*%;Al)YI3_g3|M895(9pPNHiyP2b%}cvaNnQ*1sZU{=UcuS$*s!Wv z$ai9K*Ao*nKG5clr{d;G*<%pOCvGDHblA~lu1sy_ed{QHyO#CSN-s~xU>}}EHWwUp zaZ;f{+_<&Hrr_FKNCxAzP|Zc0q{Z2k9adX~+IQi?b0=JhHT7m+S93`V{nQ)&WOF&K zfbX%V?>yeKKQM_$Xb-;6%sK|1R}3RRmg7&xvyghk_~`A(HQ;^KvxSu}8b zYpjk6W%Yu5QCD$aEY^q4p;ATFy!{Y~H73{7aY6(fKWdyd`}MaTHPz`~mP&iY%w^?K z)kbum?l7pfrP=lI7i)PoV812E5}v+r%7e4R?Oy*Tv3Y%FGx~Vf*rY*M?9N#uO3hP^u=u z7goGaC3S_!{-6w~f=H_9Ls0WHK2x<3PoVFhom0(P@e!EThph8NEBu9=`dr(2=*OMNUGZ zdy0MbBt%(jUp3W#;$!;Ys4-KP1+K^%L05dW7y={cQal-^vvO+ndr`ysN&F^BowFkp z*(V-IWCV1g>Q#6+spM ziF>I&OwA@a1HYULJ1w)D_R+zNYV@W~mk=Li4`16`rMJ}<#Jc6Gb8`B5BUk zAI~3r*zd1na-JfEzG7~MP7Z|v>C$os7!|7RDL-_T&8O3lo?|OZ4y|hrkS2;+Db}Y` zD(aZ%^eWLN$XK82r2aN*c*T>PJ=*>Cy30f>BnOKmqC)mq>?l5XdNH;E9Z^)JYSQ{N z4zlA(`)!)Hm4@~}+Av=XZh$(F3%2tzBI$STHpSmKo}3{~4TFv+cPdI-jc3-4Ny%7s zaAGARhee^1_bo+kCp&u$hUJNL``mm7!a1kDLRH7G zN9uZFr97K`B2K}Yb*9)d%L}iGK3Te?Rps68>UuSXw4i#HZHH4Yp*n)2vG1C3eK=+} z=guAvuEzt^WWly0m``g<75fFRhl$CWigG#PHtZ?*XxD~L7esr)F26f)r@u6-NgfT> z9~5XiuC)kudUwAj>NDiA9p5;+aVq|LBX)zBJ$>w0K3L9*T(+cc=h2TD1&Pc}U z#8QFU@aEN{|4jU3<;NC(xXEvDuew{HRQElIa?PRAg6IdjWZ=}=s;zZqZIfzu6BF{s znWOMp>>$$qjo5qf@mqnF^I;`$jtiK9YWaz#%o73GYj|Dy^GcCi^-0vyEbYl$?g#5N z^6TN(wV0oSBYXxN`c(ZuoT!pL(*22xbcuMPn0Es|(wwD!L`fCwE^^9}u;MiDpEQ^D zd2CORhxo$1Z)kYc&x77!Kkx^TBAmG*9AoKc%}dV2kQ|n$(azC<+T}~IKkORQ8Hgch zM`aH?cC)N^rQf%)NG)76)-RNjK{fwSzBu4ek<Q*?Pi2TRdrOZNK5gvjTPbFIUC|9 zjV2We!c=QLr!~X3$px!f9FA4$$y0HdWqZ)Sv5rl(5w2BmF(2JN5!iVwiZaw!B0C53Q52Rn{|&6Q`ExXjpW9`_Ap6;aIPx zLo^3g!B89YbTM5|+|wGgv0e3YA4BhHzNf=!%ATtHcrjMhzeUEb!@cW;q-%OH|peIsD+3ATGF+#wS3R%&|Y-@6ES-yVhr+LGb`D@1<9|PXMAdDf|^^1U7;sxD^ngJ zIYwlYGP!7zgn}7{_<5XOiEQ9(OG9F*Q~TeFYjCQSYujwu^AjECnRvrFZ_9XHd1)qO zXI`3V*+|e~BqHIbvWqyCJb%ZmID-*ioii+{;*=hipZZqVFOr0cR1$q;n(UnOz#j;l z_vh2IBdQ!^#kryrBmUi6u@+()8Hc=5tT22tR@KtV`uKAU?uUa8$;3D!J~jJbwRL1H zoEW&`E5k_#U(UM1M`JD2;njTcPr(tK<2ok)rs)`b<%U;I?;Zx!xbTA@rG*sOe9<9Afm$$1*Z#p_`SC+{$qUSQ+EC=G7H}- z{gBoW!#n!e9DI+A_L-2JDLj^I%qL;jGZ{mZqp31UJ+bVn+}qaKvh?pTq&1P0==nlU6e=``YGNcC*)4ur;W8-3_!HjlxFmc%l* zmG66MidrvL?WN@YLIwG0{pP8P(&^rn2STDiVZ&3J@_oMw&hMu@rJXT6Q>o>zbz%cp zYfpU~vhj|C^}|8jah3DNiSpRCfqMEGBI|}MXRPAMDx+3w$FoJc*R>DiLbRhu3*bd= z5xG?M8;&JEkS7tfdpwpOxv%IW#h+4w{V?Dm8YDSuK5Qe4bv>o*c}*s>G#ksZk5y2v z53l1;yLKb%R?xRtR>?c~x5`SkYrs0c8s8BaK<8o|YFb#k7j0}mwMdKq-CrrT{;-eZ zMpuhBJ-^?-+HP(~g{$r07+vm!Tksl(b#Us!*LekGr)rrvA8H+0Pj@Pw1!NakN-ctlETO^b6W)lbJ{$HKtA;b{8mu0J@=l2i5AUh1x^+r-?~ZzAjuuN2s~swZTFEF- z{r_})Lq=SgRbmFUJnUDJIbn9J`Pely&tikJI>^Nf$Aynekqn`N?2$W<3D4{=Y(8&17fA3{QVBJkMHZvFp5@>2`b z_n^%Mo6b3^vz{*(j@86q>T!NMJ49rGiM29)gy;wMiZc3mJj1S@x|72dCg?Aym-<5Z zL3gS`AIxWdEb9oUY*SXRjVmjggwF}Bkq1QnsaIrt?j!FzuyqA*mcNQ@AwnS8qv#J$ z$(bxSH`+s$KFB*s#|3hByu%4ReHF5o`}|aQA8;}P z)JJs5-=WbJ9ii4sGr;m1y~mvmk{YUuKL# zUsDwsGa?D@$T=T-JhYU-xNPsS z`C#antfHF}NA7^ahI_E|r)2%X?%@Uvd@iK3Ps(;hgI2yS8d@h;qs!gwp9gyb3)NCOZG*AX##I?DZyr1Nu`SGJ zeH2zKnMw9T_NeGr9e+eMusPaVifLHXMIyPXUT0332JKo8Tg9Fwn zh^Yl9MlTyH>xLZ@_f$ez)!_QELOZhV$*_P;;QZUI->D_TmpBxwt+66#ph!hUt*Hf# za(G}1{Mc60%E-^kf3q#u+ikU8pxy(%)R~pm2<~JN$4=3TWFVDlbX;5weSsxz$p{Z% zaS>amo0c9TW$E}FufDINQ4hZ(zDN;zIJnnwdf>sD9;Yc)q!ZTy5^Tf1+U?ijb?|oe z?_gV7AKId}HYDQER^!-?^ppU0aT%}hL%XXk(^;X>GI>V6}l#@$0Nk-5oV&e-WB3xk|z=9g;=*jZZYu-*$O z!3MP2+HyRl_M~e&EJp4vWfy_(>$VhJL{5{8%KopfPpr_`B2 zo~U2jIw3(uAwMmx#(l+FR%H*AV%K8_`~^j+#FvHgfXs{4@?tK;=aB61Klw8M^OuxJw@i z3RMkE%9x)N+lTBh9TCb2@qWc3%xzGyEG1~aJ)Pp{qK56KMSq};=y0Xm^`p?b^t3uF zB3-JEduK>`D(2gM9qW}PSZZ3YtSMF7n}^d5Qy1~UkO7(tFP=X(xn}dq7?-F{jlSOx z3QmyY+nwha`%tKdmVIL|optAsz7hWow_VWTOCsbgWv6nCj zPRN9Z-diNoJH?_?ZPt9!!hM)^)GJ{*IReY(0_ic#9nA%!ef6yT8nQk|MS3-Md zR5>m4F^jl`Jmm?yj68AA&hVS5Iqg3R>l2;o_(H-)jQ|-s&N&}O;KtsNvaEfkydd6! zw}fWSUN8Gdd@Dap{t#DCCBtWR1kAqM*!!P}>(frFb@EM(v0vBi>*DcCMBbz23BZeSMsa{RqXx+)2c5%M3mf7+`dk)!6+#%D||Br+k zcKX$HlUE>%$@&tv!6(0~iU<*l!_Q#Gx->lzsni|$l-3{e%r{A*jBl~)cwkg*5bfk? zL=@b~SjRy#V$5A|L{>FWFYKy@>)+`ThL`Br~Ab(KP6)X!hmoTeb ztZDQVH0Ye(l;xkP#GO^@>F|U+r-JLu(y4j zO%jk?0aaFt+!;rB!2MgD?WKZ%4ilz}$S1**)ugF&u@j%qt~+*uyZwQUwjX_Wus3|~ z_2TdLSOGSc`@_@9FfGyb0(~G;T3u@DA|mt1;vb6~Q}&>??b23j-&9K%W{#14#ur0x z)RZD?44Rql_9O0v_ck>Zw6&PmuQ@WuM6D20T@@Sqarj}_5&T3q!$m25`>Jpxov==o zLjNtc3mRHF+Hoz_CQ;?3y+JRCo00AGYRDSouR~2li`HK~qeS*loqSqtW)!ErpRKDT9fbox28C9h2@WXXdvMa-ZQD@>ma* zs6AR-GPce8$nw$*Uh_~|XwdZz$BeKsk26o6SNb3 zp(MRyxVHM)&I0Xxb!l_vI`u~F>)&tb5=SYqAvDvbmi6N+N~=mwZ1)a6!K%ae8t!5J zmbz+gd)+4c_i%787I0GC&*EKFd~mXhXspLptILL3QyQR-Ve6Uf4J#mG$8YU(*RPob z2lR(Wv5E(*y<`^>A7`#X-RkCLUazT8PipsNdiIDpsmB%YWmMrX-CE)!?i?);)n|?)=D2V9*oaCkllfvo8shp<#X(B9WSr(qzGFtf5tRY z?G2bp#-yFR#9@z7&E2cv6zk$@(d#*pJmpu*`{P=!w)gC7)E!Hfkz*CQ^J_@FD^>@0fz9X3WYos|=yX;K%m<*)n2FSZtH9F@_s*XZd zKKIQnHFN*iC2)!Lt{qKWQ@MB~Y$1I^EJpeSNknu($@E$1A6s?G z*f~Qh{sA9(9`WVa%l89QU{+BFWVO%9w%l;P z)}hE6{JZ*jdXKzQPI$xmu*@P4YB+&ecxz$p<$iA;UhnxYwpo(WTf5SBc~X_^lceMP zrnXeN_qheQ75&Q9&?=HivgR^MF6O4ICp*F26z7h3*v zgtxD+Gk&nyKMJmbM_LE$ z^0BOCYSy%8$KKfY637=Lqc_%vIrao+hs85HTN!#7t5&p&?nUatYxGm2e?`xVob@M9 z-N?G-4+8L!-<{%y1J0av9R$6&o0d>*Z)r=B_=4PKVniIVNxS1GNN zyXv0VoF7gWMA2O&V_VZ$OF?Rgsfq3TYh}BQbF`&i(i;*dPc6O_`48#Z-CQTNNI8iG zT0+xnYON>P4!uHKOYmnse_|CJjY*CkCzpd0$rGjRp$<=^Gu8uqC=NyXia+_ZoovzL zaorv|JZl}N2~c=Q^3HM^j3oDzBul^GY)F$;o@Y9ml1O9nsI~FOc3>#G;p>dXSpJUP zn=2$jqqt3*kAPof?Ya93o6#$=y)gD{>aOWor@Cb~_7g7xi^{z+rgZ5YbdvOtVpyCe zl$Ks%%b7CSf|AGTf_bv=mrjd8jw7>qrn^GO!X6F?ROKTLNZ%*#$5)bGWRA8n55#R$ zNlJByrfoxO;p0i9|GRWdVYEAW0E@UfTS$>BE%5RU(4ReDp!|*do4^Z65~P8Q9O?x5iRbLei|3MB3* zYZ9GBwl5`&B~p7jWgEHISwUFn(6%I5N<*HVJ@)z*?-HL9x!HQ8z2(qHtZIFs_yM~| zbs0NliGXZV?a-BoIWF&_c(3$9ydUy{Cn3qwKSFNwHBG6l@LK5x;`9c7v*)-Q`l*c@ z_gTk#~k*T^m0AxP%#K%BxuaE=+TH{ z)5nIsRI9afUH1g#9t+wf>;32eo71%rf@qnCq-r)vp`2}&^q4T52)ZNscQq*VcZXeB zeBjnT=fN#s-FD0F;IOt$Y(xC6aMjB@s1LcSsc-Vq@&2DFzPDl|uKRICP;E_O#o>jH z*KbNvKNs}qv)N>qws+Of>t%&@2#d0=3!aK;%R5;n7nAGp(cEQbuRgn1r>Y9aY|lEz xE&Fo2Xu0Z=(E;RFkk8PCI)Bh1j@8I^%qr0KO=yv!)I+1RG7tSD4KyB&{@?bxhmimP diff --git a/DashWallet/en.lproj/Localizable.strings b/DashWallet/en.lproj/Localizable.strings index eda57d713..1e2203d9b 100644 --- a/DashWallet/en.lproj/Localizable.strings +++ b/DashWallet/en.lproj/Localizable.strings @@ -22,6 +22,9 @@ /* No comment provided by engineer. */ "%@ is not allowed to access Touch ID" = "%@ is not allowed to access Touch ID"; +/* 10/20 Characters */ +"%ld/%ld Characters" = "%1$ld/%2$ld Characters"; + /* No comment provided by engineer. */ "(1/3) Processing Payment" = "(1/3) Processing Payment"; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "About" = "About"; +/* No comment provided by engineer. */ +"About me" = "About me"; + /* No comment provided by engineer. */ "Accept" = "Accept"; @@ -118,12 +124,18 @@ /* No comment provided by engineer. */ "Add a New Contact" = "Add a New Contact"; +/* No comment provided by engineer. */ +"Add your Friends & Family" = "Add your Friends & Family"; + /* No comment provided by engineer. */ "Address" = "Address"; /* No comment provided by engineer. */ "Advanced Security" = "Advanced Security"; +/* No comment provided by engineer. */ +"Agree" = "Agree"; + /* No comment provided by engineer. */ "All" = "All"; @@ -226,6 +238,9 @@ /* Coinbase/Payment Methods */ "Bank Wire" = "Bank Wire"; +/* Validation rule: Between 3 and 24 characters */ +"Between %ld and %ld characters" = "Between %1$ld and %2$ld characters"; + /* Coinbase Entry Point */ "Between Dash Wallet and Coinbase." = "Between Dash Wallet and Coinbase."; @@ -302,6 +317,12 @@ /* Choose your Dash username */ "Choose your" = "Choose your"; +/* No comment provided by engineer. */ +"Choose Your Username" = "Choose Your Username"; + +/* No comment provided by engineer. */ +"Claimed" = "Claimed"; + /* No comment provided by engineer. */ "Clear" = "Clear"; @@ -326,9 +347,6 @@ /* No comment provided by engineer. */ "Confirm" = "Confirm"; -/* No comment provided by engineer. */ -"Confirm & Pay" = "Confirm & Pay"; - /* Coinbase/Buy Dash/Confirm Order */ "Confirm (%@)" = "Confirm (%@)"; @@ -382,6 +400,9 @@ /* No comment provided by engineer. */ "Copy" = "Copy"; +/* No comment provided by engineer. */ +"Copy Invitation Link" = "Copy Invitation Link"; + /* No comment provided by engineer. */ "Copy Logs" = "Copy Logs"; @@ -403,16 +424,31 @@ /* No comment provided by engineer. */ "Couldn't transmit payment to Dash network" = "Couldn't transmit payment to Dash network"; +/* No comment provided by engineer. */ +"Create a new Invitation" = "Create a new Invitation"; + +/* No comment provided by engineer. */ +"Create a new invitation" = "Create a new invitation"; + /* No comment provided by engineer. */ "Create a New Wallet" = "Create a New Wallet"; +/* No comment provided by engineer. */ +"Create a username, add your friends." = "Create a username, add your friends."; + /* CrowdNode */ "Create Account" = "Create Account"; +/* No comment provided by engineer. */ +"Create invitation" = "Create invitation"; + /* CrowdNode CrowdNode Portal */ "Create Online Account" = "Create Online Account"; +/* No comment provided by engineer. */ +"Create your Username, find friends & family with their usernames and add them to your contacts" = "Create your Username, find friends & family with their usernames and add them to your contacts"; + /* Coinbase/Payment Methods */ "Credit Card" = "Credit Card"; @@ -461,6 +497,9 @@ /* Buy Dash */ "Dash Wallet on this device" = "Dash Wallet on this device"; +/* No comment provided by engineer. */ +"DashPay Invitation" = "DashPay Invitation"; + /* No comment provided by engineer. */ "DashPay Upgrade Fee" = "DashPay Upgrade Fee"; @@ -508,6 +547,9 @@ Coinbase Entry Point */ "Disconnected" = "Disconnected"; +/* No comment provided by engineer. */ +"Display Name" = "Display Name"; + /* Explore Dash: Filters */ "Distance" = "Distance"; @@ -526,12 +568,21 @@ /* CrowdNode */ "e.g. johndoe@mail.com" = "e.g. johndoe@mail.com"; +/* No comment provided by engineer. */ +"Each invitation will be funded with this amount so that the receiver can quickly create their username on the Dash Network" = "Each invitation will be funded with this amount so that the receiver can quickly create their username on the Dash Network"; + /* (List of notifications happened) Earlier (some time ago) */ "Earlier" = "Earlier"; /* No comment provided by engineer. */ "Easily stake Dash and earn passive income with a few simple clicks." = "Easily stake Dash and earn passive income with a few simple clicks."; +/* No comment provided by engineer. */ +"Edit Profile" = "Edit Profile"; + +/* Invitation tag placeholder */ +"eg: Dad" = "eg: Dad"; + /* Input username textfield placeholder */ "eg: johndoe" = "eg: johndoe"; @@ -562,9 +613,15 @@ /* No comment provided by engineer. */ "Enter your 2FA code below" = "Enter your 2FA code below"; +/* No comment provided by engineer. */ +"Enter your Gravatar Email ID" = "Enter your Gravatar Email ID"; + /* No comment provided by engineer. */ "Error" = "Error"; +/* No comment provided by engineer. */ +"Error updating your profile" = "Error updating your profile"; + /* No comment provided by engineer. */ "Error Upgrading" = "Error Upgrading"; @@ -610,6 +667,9 @@ /* Coinbase/Fee info */ "Fees in crypto purchases" = "Fees in crypto purchases"; +/* No comment provided by engineer. */ +"Fetching Image" = "Fetching Image"; + /* Explore Dash */ "Fetching Info" = "Fetching Info"; @@ -682,6 +742,12 @@ /* No comment provided by engineer. */ "Get Started" = "Get Started"; +/* No comment provided by engineer. */ +"Get Test Dash" = "Get Test Dash"; + +/* No comment provided by engineer. */ +"Get your Username" = "Get your Username"; + /* Explore Dash: Filters */ "Gift Card" = "Gift Card"; @@ -718,6 +784,9 @@ /* No comment provided by engineer. */ "History" = "History"; +/* No comment provided by engineer. */ +"How do I get Test Dash?" = "How do I get Test Dash?"; + /* No comment provided by engineer. */ "How to Use a Gift Card" = "How to Use a Gift Card"; @@ -748,6 +817,15 @@ /* No comment provided by engineer. */ "Ignore" = "Ignore"; +/* No comment provided by engineer. */ +"Image uploaded can be viewed publicly by anyone." = "Image uploaded can be viewed publicly by anyone."; + +/* No comment provided by engineer. */ +"Image URL can't be longer than %ld characters." = "Image URL can't be longer than %ld characters."; + +/* No comment provided by engineer. */ +"Images Privacy Policy" = "Images Privacy Policy"; + /* No comment provided by engineer. */ "Immediately" = "Immediately"; @@ -772,6 +850,9 @@ /* No comment provided by engineer. */ "Insufficient funds" = "Insufficient funds"; +/* No comment provided by engineer. */ +"Insufficient Wallet Balance" = "Insufficient Wallet Balance"; + /* Coinbase/Payment Methods */ "Interac" = "Interac"; @@ -796,12 +877,54 @@ /* Invalid Amount Input */ "Invalid Input" = "Invalid Input"; +/* No comment provided by engineer. */ +"Invalid Inviation" = "Invalid Inviation"; + /* No comment provided by engineer. */ "Invalid Payment Request" = "Invalid Payment Request"; /* No comment provided by engineer. */ "Invalid QR Code" = "Invalid QR Code"; +/* No comment provided by engineer. */ +"Invitation" = "Invitation"; + +/* Invitation #3 */ +"Invitation %ld" = "Invitation %ld"; + +/* No comment provided by engineer. */ +"Invitation already claimed" = "Invitation already claimed"; + +/* No comment provided by engineer. */ +"Invitation Created Successfully" = "Invitation Created Successfully"; + +/* No comment provided by engineer. */ +"Invitation Fee" = "Invitation Fee"; + +/* No comment provided by engineer. */ +"Invitation used by" = "Invitation used by"; + +/* No comment provided by engineer. */ +"Invitations History" = "Invitations History"; + +/* No comment provided by engineer. */ +"Invite" = "Invite"; + +/* No comment provided by engineer. */ +"Invite Someone to join the Dash Network" = "Invite Someone to join the Dash Network"; + +/* No comment provided by engineer. */ +"Invite your family, find your friends by searching their usernames" = "Invite your family, find your friends by searching their usernames"; + +/* No comment provided by engineer. */ +"Invite your friends & family" = "Invite your friends & family"; + +/* No comment provided by engineer. */ +"Invite your friends and family to join the Dash Network." = "Invite your friends and family to join the Dash Network."; + +/* No comment provided by engineer. */ +"Invite your friends and family to the Dash Network" = "Invite your friends and family to the Dash Network"; + /* CrowdNode */ "It can take a minute for your balance to be updated." = "It can take a minute for your balance to be updated."; @@ -820,9 +943,18 @@ /* No comment provided by engineer. */ "It would seem like you are attempting to restore your wallet a using a 12 word recovery phrase, however you have only entered 11 words, would you like to automatically recover the missing word?" = "It would seem like you are attempting to restore your wallet a using a 12 word recovery phrase, however you have only entered 11 words, would you like to automatically recover the missing word?"; +/* No comment provided by engineer. */ +"Join" = "Join"; + +/* No comment provided by engineer. */ +"Join DashPay" = "Join DashPay"; + /* No comment provided by engineer. */ "Join Evolution" = "Join Evolution"; +/* No comment provided by engineer. */ +"Join Now" = "Join Now"; + /* No comment provided by engineer. */ "Key Id" = "Key Id"; @@ -848,8 +980,14 @@ /* No comment provided by engineer. */ "Let me know when it’s done" = "Let me know when it’s done"; +/* No comment provided by engineer. */ +"Let your friends and family to join the Dash Network. Invite them to the world of social banking." = "Let your friends and family to join the Dash Network. Invite them to the world of social banking."; + /* Validation rule */ -"Letters and numbers only" = "Letters and numbers only"; +"Letters, numbers and hyphens only" = "Letters, numbers and hyphens only"; + +/* No comment provided by engineer. */ +"Let’s Get Started" = "Let’s Get Started"; /* Dash Service Overview */ "Link Coinbase Account" = "Link Coinbase Account"; @@ -938,8 +1076,8 @@ /* Contracted variant of 'Maximum' word */ "Max" = "Max"; -/* Validation rule: Maximum 24 characters */ -"Maximum %ld characters" = "Maximum %ld characters"; +/* No comment provided by engineer. */ +"Maybe later" = "Maybe later"; /* adjective, security level */ "Medium" = "Medium"; @@ -947,21 +1085,27 @@ /* No comment provided by engineer. */ "Merchant search works better with Location Services turned on." = "Merchant search works better with Location Services turned on."; -/* Validation rule */ -"Minimum 3 characters" = "Minimum 3 characters"; - /* No comment provided by engineer. */ "More" = "More"; /* No comment provided by engineer. */ "More Control" = "More Control"; +/* No comment provided by engineer. */ +"More Suggestions" = "More Suggestions"; + +/* No comment provided by engineer. */ +"Move and Zoom your photo to find the perfect fit" = "Move and Zoom your photo to find the perfect fit"; + /* No comment provided by engineer. */ "Moved from" = "Moved from"; /* No comment provided by engineer. */ "Moved to Address" = "Moved to Address"; +/* Validation rule */ +"Must start and end with a letter or number" = "Must start and end with a letter or number"; + /* No comment provided by engineer. */ "My Contacts" = "My Contacts"; @@ -1064,6 +1208,9 @@ /* No comment provided by engineer. */ "Operator Keys" = "Operator Keys"; +/* No comment provided by engineer. */ +"or" = "or"; + /* Coinbase/Buy Dash/Confirm Order */ "Order Preview" = "Order Preview"; @@ -1073,6 +1220,9 @@ /* No comment provided by engineer. */ "Owner Keys" = "Owner Keys"; +/* No comment provided by engineer. */ +"Paste your image URL" = "Paste your image URL"; + /* No comment provided by engineer. */ "Pay" = "Pay"; @@ -1082,6 +1232,9 @@ /* emphasized text in: Add as your contact to Pay Directly to Username and Retain Mutual Transaction History */ "Pay Directly to Username" = "Pay Directly to Username"; +/* No comment provided by engineer. */ +"Pay to usernames. No more alphanumeric addresses" = "Pay to usernames. No more alphanumeric addresses"; + /* Coinbase/Buy Dash */ "Pay with" = "Pay with"; @@ -1112,6 +1265,9 @@ /* Explore Dash/Merchants/Filters */ "Payment Type" = "Payment Type"; +/* No comment provided by engineer. */ +"Payments made directly to addresses won’t be retained in activity." = "Payments made directly to addresses won’t be retained in activity."; + /* Coinbase/Payment Methods */ "PayPal" = "PayPal"; @@ -1127,6 +1283,9 @@ /* CrowdNode */ "per transaction" = "per transaction"; +/* No comment provided by engineer. */ +"Personalize" = "Personalize"; + /* No comment provided by engineer. */ "PIN is always required to make a payment" = "PIN is always required to make a payment"; @@ -1136,6 +1295,12 @@ /* Network Unavailable */ "Please check your network connection" = "Please check your network connection"; +/* No comment provided by engineer. */ +"Please enter a valid gravatar email ID." = "Please enter a valid gravatar email ID."; + +/* No comment provided by engineer. */ +"Please enter a valid image URL." = "Please enter a valid image URL."; + /* No comment provided by engineer. */ "Please enter PIN to upgrade wallet" = "Please enter PIN to upgrade wallet"; @@ -1166,6 +1331,9 @@ /* No comment provided by engineer. */ "Please write it down" = "Please write it down"; +/* No comment provided by engineer. */ +"Preview Invitation" = "Preview Invitation"; + /* No comment provided by engineer. */ "Previously used at: " = "Previously used at: "; @@ -1190,6 +1358,9 @@ /* No comment provided by engineer. */ "Public key (legacy)" = "Public key (legacy)"; +/* No comment provided by engineer. */ +"Public URL" = "Public URL"; + /* Coinbase/Buy Dash */ "Purchase" = "Purchase"; @@ -1301,6 +1472,12 @@ /* No comment provided by engineer. */ "Rewards" = "Rewards"; +/* No comment provided by engineer. */ +"Save" = "Save"; + +/* No comment provided by engineer. */ +"Save Changes" = "Save Changes"; + /* No comment provided by engineer. */ "Scan" = "Scan"; @@ -1343,6 +1520,9 @@ /* No comment provided by engineer. */ "Searching for username %@ on the Dash Network" = "Searching for username %@ on the Dash Network"; +/* No comment provided by engineer. */ +"Secure Your Wallet" = "Secure Your Wallet"; + /* No comment provided by engineer. */ "Security" = "Security"; @@ -1352,12 +1532,18 @@ /* No comment provided by engineer. */ "See on Uphold" = "See on Uphold"; +/* No comment provided by engineer. */ +"Select" = "Select"; + /* Coinbase */ "Select a coin" = "Select a coin"; /* Buy Sell Dash */ "Select a service" = "Select a service"; +/* No comment provided by engineer. */ +"Select from Gallery" = "Select from Gallery"; + /* Coinbase */ "Select the coin" = "Select the coin"; @@ -1376,9 +1562,15 @@ /* CrowdNode Confirm */ "Send %@ from your primary Dash address that you currently use for your CrowdNode account" = "Send %@ from your primary Dash address that you currently use for your CrowdNode account"; +/* No comment provided by engineer. */ +"Send again" = "Send again"; + /* No comment provided by engineer. */ "Send Contact Request" = "Send Contact Request"; +/* No comment provided by engineer. */ +"Send Invitation" = "Send Invitation"; + /* Coinbase CrowdNode */ "Send Report" = "Send Report"; @@ -1386,6 +1578,9 @@ /* No comment provided by engineer. */ "Send to" = "Send to"; +/* No comment provided by engineer. */ +"Send to a Contact" = "Send to a Contact"; + /* No comment provided by engineer. */ "Send to copied address or QR code" = "Send to copied address or QR code"; @@ -1395,6 +1590,9 @@ /* 1 out of 4 in the Sending Animation */ "Sending" = "Sending"; +/* No comment provided by engineer. */ +"Sending Contact Request" = "Sending Contact Request"; + /* No comment provided by engineer. */ "Sending to" = "Sending to"; @@ -1431,12 +1629,18 @@ /* No comment provided by engineer. */ "Set PIN" = "Set PIN"; +/* No comment provided by engineer. */ +"Set Your PIN" = "Set Your PIN"; + /* No comment provided by engineer. */ "Settings" = "Settings"; /* No comment provided by engineer. */ "Setup Wallet" = "Setup Wallet"; +/* No comment provided by engineer. */ +"Share" = "Share"; + /* Receive screen */ "Share address" = "Share address"; @@ -1497,6 +1701,9 @@ /* No comment provided by engineer. */ "Staking" = "Staking"; +/* Step 1 */ +"Step %ld" = "Step %ld"; + /* No comment provided by engineer. */ "Support" = "Support"; @@ -1530,6 +1737,12 @@ /* Balance */ "Syncing…" = "Syncing…"; +/* No comment provided by engineer. */ +"Tag for your reference" = "Tag for your reference"; + +/* No comment provided by engineer. */ +"Take a Photo from Camera" = "Take a Photo from Camera"; + /* Enter Address Screen */ "Tap the address from the clipboard to paste it" = "Tap the address from the clipboard to paste it"; @@ -1539,6 +1752,12 @@ /* No comment provided by engineer. */ "Tax Category" = "Tax Category"; +/* No comment provided by engineer. */ +"Test Dash doesn’t have any value in the real world but you can send and receive it with other DashPay Alpha users." = "Test Dash doesn’t have any value in the real world but you can send and receive it with other DashPay Alpha users."; + +/* No comment provided by engineer. */ +"Test Dash is free and can be obtained from what is called a faucet.\nCopy an address from the Receive screen of your wallet and click on the button bellow to get your Dash." = "Test Dash is free and can be obtained from what is called a faucet.\nCopy an address from the Receive screen of your wallet and click on the button bellow to get your Dash."; + /* No comment provided by engineer. */ "The chain is syncing…" = "The chain is syncing…"; @@ -1548,9 +1767,15 @@ /* Coinbase/Buy Dash/Confirm Order */ "The Dash was successfully deposited to your Coinbase account. But there was a problem transfering it to Dash Wallet on this device." = "The Dash was successfully deposited to your Coinbase account. But there was a problem transfering it to Dash Wallet on this device."; +/* Don't translate 'Imgur' */ +"The image you select will be uploaded to Imgur anonymously." = "The image you select will be uploaded to Imgur anonymously."; + /* Coinbase */ "The minimum amount you can send is %@" = "The minimum amount you can send is %@"; +/* No comment provided by engineer. */ +"them (Fetching Info)" = "them (Fetching Info)"; + /* No comment provided by engineer. */ "There are no new notifications" = "There are no new notifications"; @@ -1646,12 +1871,33 @@ /* Coinbase */ "Two factor auth required" = "Two factor auth required"; +/* No comment provided by engineer. */ +"Unable to accept contact request" = "Unable to accept contact request"; + /* No comment provided by engineer. */ "Unable to connect" = "Unable to connect"; +/* No comment provided by engineer. */ +"Unable to fetch contact details" = "Unable to fetch contact details"; + +/* No comment provided by engineer. */ +"Unable to fetch image. Please enter a valid image URL or check your connection." = "Unable to fetch image. Please enter a valid image URL or check your connection."; + +/* No comment provided by engineer. */ +"Unable to fetch your Gravatar. Please enter a valid gravatar email ID." = "Unable to fetch your Gravatar. Please enter a valid gravatar email ID."; + /* No comment provided by engineer. */ "Unable to get new QR code" = "Unable to get new QR code"; +/* No comment provided by engineer. */ +"Unable to provide suggestions" = "Unable to provide suggestions"; + +/* No comment provided by engineer. */ +"Unable to send contact request" = "Unable to send contact request"; + +/* No comment provided by engineer. */ +"Unable to upload your picture. Please try again." = "Unable to upload your picture. Please try again."; + /* No comment provided by engineer. */ "Unknown" = "Unknown"; @@ -1671,7 +1917,13 @@ "Updating Price" = "Updating Price"; /* No comment provided by engineer. */ -"Upgrade Fee" = "Upgrade Fee"; +"Updating Profile on Dash Network" = "Updating Profile on Dash Network"; + +/* No comment provided by engineer. */ +"Upgrade" = "Upgrade"; + +/* No comment provided by engineer. */ +"Upgrade to Evolution" = "Upgrade to Evolution"; /* No comment provided by engineer. */ "Upgrading to DashPay" = "Upgrading to DashPay"; @@ -1679,6 +1931,15 @@ /* No comment provided by engineer. */ "Uphold" = "Uphold"; +/* No comment provided by engineer. */ +"Upload Error" = "Upload Error"; + +/* No comment provided by engineer. */ +"Upload your picture, personalize your identity" = "Upload your picture, personalize your identity"; + +/* No comment provided by engineer. */ +"Uploading your picture to the network" = "Uploading your picture to the network"; + /* Explore Dash/Filters */ "Use gift card" = "Use gift card"; @@ -1688,15 +1949,21 @@ /* No comment provided by engineer. */ "Used at: " = "Used at: "; +/* No comment provided by engineer. */ +"User (Fetching Info)" = "User (Fetching Info)"; + +/* No comment provided by engineer. */ +"Username available" = "Username available"; + /* No comment provided by engineer. */ "Username taken" = "Username taken"; +/* No comment provided by engineer. */ +"Users that matches %@ who are currently not in your contacts" = "Users that matches %@ who are currently not in your contacts"; + /* CrowdNode Portal */ "Validating address…" = "Validating address…"; -/* No comment provided by engineer. */ -"Validating username done" = "Validating username done"; - /* No comment provided by engineer. */ "Validating username failed" = "Validating username failed"; @@ -1778,6 +2045,9 @@ /* No comment provided by engineer. */ "Welcome" = "Welcome"; +/* No comment provided by engineer. */ +"Welcome to DashPay" = "Welcome to DashPay"; + /* No comment provided by engineer. */ "When the transaction is confirmed, the other wallet will be worthless and should not be re-used for safety reasons." = "When the transaction is confirmed, the other wallet will be worthless and should not be re-used for safety reasons."; @@ -1815,6 +2085,12 @@ /* CrowdNode */ "Withdrawal requested" = "Withdrawal requested"; +/* No comment provided by engineer. */ +"Would you like to accept the invitation?" = "Would you like to accept the invitation?"; + +/* No comment provided by engineer. */ +"Would you like to save the changes you made to your profile?" = "Would you like to save the changes you made to your profile?"; + /* No comment provided by engineer. */ "Yes" = "Yes"; @@ -1827,17 +2103,29 @@ /* No comment provided by engineer. */ "You are about to wipe this wallet from this device. Funds associated with this wallet can only be retrieved if you have your recovery phrase." = "You are about to wipe this wallet from this device. Funds associated with this wallet can only be retrieved if you have your recovery phrase."; +/* No comment provided by engineer. */ +"You can always delete the image uploaded, as long as you have access to this wallet." = "You can always delete the image uploaded, as long as you have access to this wallet."; + /* No comment provided by engineer. */ "You can authenticate with Face ID for payments below" = "You can authenticate with Face ID for payments below"; /* No comment provided by engineer. */ "You can authenticate with Touch ID for payments below" = "You can authenticate with Touch ID for payments below"; +/* No comment provided by engineer. */ +"You can specify any URL which is publicly available on the internet so other users can see it on the Dash network." = "You can specify any URL which is publicly available on the internet so other users can see it on the Dash network."; + +/* No comment provided by engineer. */ +"You do not have any contacts at the moment" = "You do not have any contacts at the moment"; + /* Coinbase */ "You exceeded the authorization limit on Coinbase." = "You exceeded the authorization limit on Coinbase."; /* No comment provided by engineer. */ -"You have chosen \"%@\" as your username. Your username cannot be changed once registered." = "You have chosen \"%@\" as your username. Your username cannot be changed once registered."; +"You have been invited by %@. Start using Dash cryptocurrency." = "You have been invited by %@. Start using Dash cryptocurrency."; + +/* No comment provided by engineer. */ +"You have chose \"%@\" as your username. Username cannot be changed once it is registered." = "You have chose \"%@\" as your username. Username cannot be changed once it is registered."; /* No comment provided by engineer. */ "You have insufficient funds to proceed" = "You have insufficient funds to proceed"; @@ -1845,6 +2133,9 @@ /* No comment provided by engineer. */ "You must enter your PIN in order to enter Dash Wallet" = "You must enter your PIN in order to enter Dash Wallet"; +/* No comment provided by engineer. */ +"You need at least %@ Dash to create an invitation" = "You need at least %@ Dash to create an invitation"; + /* CrowdNode */ "You need at least %@ on your Dash Wallet" = "You need at least %@ on your Dash Wallet"; @@ -1899,6 +2190,15 @@ /* CrowdNode */ "Your deposit to CrowdNode is received." = "Your deposit to CrowdNode is received."; +/* No comment provided by engineer. */ +"Your Email is not stored in the DashPay wallet nor on any servers. It is used once to get your Gravatar account details and then discarded." = "Your Email is not stored in the DashPay wallet nor on any servers. It is used once to get your Gravatar account details and then discarded."; + +/* No comment provided by engineer. */ +"Your invitation from %@ has been already claimed" = "Your invitation from %@ has been already claimed"; + +/* No comment provided by engineer. */ +"Your invitation from %@ is not valid" = "Your invitation from %@ is not valid"; + /* No comment provided by engineer. */ "Your location is used to show your position on the map, ATMs in the selected redius and improve search results." = "Your location is used to show your position on the map, ATMs in the selected redius and improve search results."; diff --git a/DashWallet/eo.lproj/Localizable.strings b/DashWallet/eo.lproj/Localizable.strings index 5adf4d5bd9a2bbbd956301c583b97c940d3525b1..89b946a32eb0619b336c3214be3005f7896be657 100644 GIT binary patch literal 74276 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 S_f|b=r=8y)q0N8&)&B=b0Zt?U literal 125120 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