From b31401246042142047738c942f40e87eb537dd4c Mon Sep 17 00:00:00 2001 From: Oleg Date: Fri, 15 Mar 2024 16:40:13 +0700 Subject: [PATCH] MOB-1889 - Buy crypto (#443) * Extended pull up view with items list * Added pull up selection for buying on the home screen * Added Links to buy crypto to domain or wallet * Created function to run buy crypto flow * Open buy crypto page in SafariVC * Updated buy crypto titles * Remove timer to load search suggestions if failed or 0 --- .../project.pbxproj | 6 + .../Extensions/Extension-String+Preview.swift | 13 +- .../Extensions/UIImage.swift | 2 + .../Extensions/UIViewController.swift | 11 ++ .../Modules/Home/HomeTabRouter.swift | 31 +++- .../HomeWalletView+Entities.swift | 37 +++++ .../HomeWalletView/HomeWalletViewModel.swift | 14 +- .../Search/PurchaseSearchDomainsView.swift | 12 -- .../NetworkEnvironment/NetworkConfig.swift | 7 + .../AnalyticsServiceEnvironment.swift | 1 + .../globeRotated.imageset/Contents.json | 15 ++ .../globeRotated.imageset/globeRotated.svg | 3 + .../verticalLines.imageset/Contents.json | 15 ++ .../verticalLines.imageset/verticalLines.svg | 3 + .../Localization/en.lproj/Localizable.strings | 5 + .../SwiftUI/Extensions/Image.swift | 4 +- .../ViewModifiers/ViewPullUp/ViewPullUp.swift | 47 +++++- .../ViewPullUpDefaultConfiguration.swift | 44 +++++- .../ViewPullUp/ViewPullUpListItemView.swift | 137 ++++++++++++++++++ 19 files changed, 381 insertions(+), 26 deletions(-) create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/globeRotated.imageset/Contents.json create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/globeRotated.imageset/globeRotated.svg create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/verticalLines.imageset/Contents.json create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/verticalLines.imageset/verticalLines.svg create mode 100644 unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUpListItemView.swift diff --git a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj index f3a275353..24ec91e52 100644 --- a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj +++ b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj @@ -1292,6 +1292,8 @@ C6B435072A53F42400BC644B /* BaseDiffableCollectionViewControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B435062A53F42400BC644B /* BaseDiffableCollectionViewControllerProtocol.swift */; }; C6B5136228320707001E99B5 /* EnterBackupToRestoreWalletsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B5136128320707001E99B5 /* EnterBackupToRestoreWalletsPresenter.swift */; }; C6B5136828322EA1001E99B5 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B5136728322EA1001E99B5 /* MainWindow.swift */; }; + C6B540C12BA3F77200A41D42 /* ViewPullUpListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B540C02BA3F77200A41D42 /* ViewPullUpListItemView.swift */; }; + C6B540C22BA3F77200A41D42 /* ViewPullUpListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B540C02BA3F77200A41D42 /* ViewPullUpListItemView.swift */; }; C6B659242B68E49500CA6A68 /* HomeWalletTokenNotMatchingRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B659232B68E49500CA6A68 /* HomeWalletTokenNotMatchingRowView.swift */; }; C6B659252B68E49500CA6A68 /* HomeWalletTokenNotMatchingRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B659232B68E49500CA6A68 /* HomeWalletTokenNotMatchingRowView.swift */; }; C6B65F502B54DA84006D1812 /* HomeWalletViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B65F4F2B54DA84006D1812 /* HomeWalletViewModel.swift */; }; @@ -3451,6 +3453,7 @@ C6B435062A53F42400BC644B /* BaseDiffableCollectionViewControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDiffableCollectionViewControllerProtocol.swift; sourceTree = ""; }; C6B5136128320707001E99B5 /* EnterBackupToRestoreWalletsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterBackupToRestoreWalletsPresenter.swift; sourceTree = ""; }; C6B5136728322EA1001E99B5 /* MainWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = ""; }; + C6B540C02BA3F77200A41D42 /* ViewPullUpListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewPullUpListItemView.swift; sourceTree = ""; }; C6B659232B68E49500CA6A68 /* HomeWalletTokenNotMatchingRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeWalletTokenNotMatchingRowView.swift; sourceTree = ""; }; C6B65F4F2B54DA84006D1812 /* HomeWalletViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeWalletViewModel.swift; sourceTree = ""; }; C6B65F552B54E42C006D1812 /* HomeWalletTokenRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeWalletTokenRowView.swift; sourceTree = ""; }; @@ -5616,6 +5619,7 @@ isa = PBXGroup; children = ( C6D646442B1E084C00D724AC /* ViewPullUp.swift */, + C6B540C02BA3F77200A41D42 /* ViewPullUpListItemView.swift */, C64F8DFD2B60F2140075D37F /* ViewPullUpDefaultConfiguration.swift */, C64F8E032B60F4AF0075D37F /* ViewPullUpCustomContentConfiguration.swift */, C64F8E002B60F2590075D37F /* ViewPullUpConfigurationType.swift */, @@ -8650,6 +8654,7 @@ C6C995C2289D313D00367362 /* CNavigationControllerDefaultNavigationBarPopAnimation.swift in Sources */, C6FBCAA82B91D00100BA39DF /* HomeExploreUserWalletDomainsView.swift in Sources */, C6B6B043296FF7D600D4E30F /* DomainsSortOrderStorage.swift in Sources */, + C6B540C12BA3F77200A41D42 /* ViewPullUpListItemView.swift in Sources */, C617FD992B58BC2900B93433 /* WalletEntitiesStorage.swift in Sources */, C692C32A282E4C6500C31393 /* SecuritySettingsAuthSelectionCell.swift in Sources */, C635195128D0441000FC6AF8 /* SetupWalletsReverseResolutionPresenter.swift in Sources */, @@ -9821,6 +9826,7 @@ C6D646AC2B1ED16900D724AC /* DomainProfileTutorialViewController.swift in Sources */, C6D6467A2B1ED12100D724AC /* DomainProfileGenericChangeDescription.swift in Sources */, C630E4BA2B7F5BAC008F3269 /* Padding.swift in Sources */, + C6B540C22BA3F77200A41D42 /* ViewPullUpListItemView.swift in Sources */, C6D647192B1ED84500D724AC /* CollectionReusableRoundedBackgroundWhiteWithAlpha.swift in Sources */, C6C8F8372B217E9600A9834D /* BaseRecoveryPhrasePresenter.swift in Sources */, C6D646612B1ED11400D724AC /* DomainProfileSectionHeader.swift in Sources */, diff --git a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift index 3b3b62b42..6e44cb1bd 100644 --- a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift +++ b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift @@ -31,6 +31,7 @@ extension String { case communitiesInfo case setupApplePayInstruction case unstoppableDomainSearch(searchKey: String) + case buyCryptoToDomain(DomainName), buyCryptoToWallet(HexAddress) var urlString: String { switch self { @@ -98,7 +99,11 @@ extension String { case .direct(let url): return url.absoluteString case .unstoppableDomainSearch(let searchKey): - return "https://\(NetworkConfig.websiteHost)/search?searchTerm=\(searchKey)&searchRef=homepage&tab=relevant" + return "\(NetworkConfig.websiteBaseUrl)/search?searchTerm=\(searchKey)&searchRef=homepage&tab=relevant" + case .buyCryptoToDomain(let domainName): + return "\(NetworkConfig.buyCryptoUrl)?domain=\(domainName)" + case .buyCryptoToWallet(let walletAddress): + return "\(NetworkConfig.buyCryptoUrl)?address=\(walletAddress)" } } @@ -1140,6 +1145,12 @@ extension String { static let profileSuggestionReasonFarcasterFollows = "PROFILE_SUGGESTION_REASON_FARCASTER_FOLLOWS" static let profileSuggestionReasonFarcasterMutual = "PROFILE_SUGGESTION_REASON_FARCASTER_MUTUAL" static let searchProfiles = "SEARCH_PROFILES" + + static let selectPullUpBuyDomainsTitle = "SELECT_PULL_UP_BUY_DOMAINS_TITLE" + static let selectPullUpBuyTokensTitle = "SELECT_PULL_UP_BUY_TOKENS_TITLE" + static let selectPullUpBuyDomainsSubtitle = "SELECT_PULL_UP_BUY_DOMAINS_SUBTITLE" + static let selectPullUpBuyTokensSubtitle = "SELECT_PULL_UP_BUY_TOKENS_SUBTITLE" + } enum BlockChainIcons: String { diff --git a/unstoppable-ios-app/domains-manager-ios/Extensions/UIImage.swift b/unstoppable-ios-app/domains-manager-ios/Extensions/UIImage.swift index 7c2e80f30..26c39f46d 100644 --- a/unstoppable-ios-app/domains-manager-ios/Extensions/UIImage.swift +++ b/unstoppable-ios-app/domains-manager-ios/Extensions/UIImage.swift @@ -162,6 +162,8 @@ extension UIImage { static let filterIcon = UIImage(named: "filterIcon")! static let qrBarCodeIcon = UIImage(named: "qrBarCodeIcon")! static let walletAddressesIcon = UIImage(named: "walletAddressesIcon")! + static let globeRotated = UIImage(named: "globeRotated")! + static let verticalLines = UIImage(named: "verticalLines")! static let twitterIcon24 = UIImage(named: "twitterIcon24")! static let discordIcon24 = UIImage(named: "discordIcon24")! diff --git a/unstoppable-ios-app/domains-manager-ios/Extensions/UIViewController.swift b/unstoppable-ios-app/domains-manager-ios/Extensions/UIViewController.swift index a704480f3..0b068b084 100644 --- a/unstoppable-ios-app/domains-manager-ios/Extensions/UIViewController.swift +++ b/unstoppable-ios-app/domains-manager-ios/Extensions/UIViewController.swift @@ -264,3 +264,14 @@ extension UIViewController { return presentedViewController?.childOf(type: type) } } + +import SafariServices + +extension UIViewController { + func openLinkInSafari(_ link: String.Links) { + guard let url = link.url else { return } + + let safariVC = SFSafariViewController(url: url) + present(safariVC, animated: true) + } +} diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabRouter.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabRouter.swift index c2050f097..839b0246a 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabRouter.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabRouter.swift @@ -80,6 +80,22 @@ extension HomeTabRouter { })) } } + + func runBuyCryptoFlowTo(wallet: WalletEntity) { + Task { + await showHomeScreenList() + guard let topVC else { return } + + let link: String.Links + if let rrDomain = wallet.rrDomain { + link = .buyCryptoToDomain(rrDomain.name) + } else { + link = .buyCryptoToWallet(wallet.address) + } + + topVC.openLinkInSafari(link) + } + } func primaryDomainMinted(_ domain: DomainDisplayInfo) async { if let mintingNav { @@ -119,7 +135,7 @@ extension HomeTabRouter { tabViewSelection = .wallets } await askToFinishSetupPurchasedProfileIfNeeded(domains: wallet.domains) - guard let topVC = appContext.coreAppCoordinator.topVC else { return } + guard let topVC else { return } switch domain.usageType { @@ -184,10 +200,9 @@ extension HomeTabRouter { Task { @MainActor in let domains = appContext.walletsDataService.wallets.combinedDomains() - let topPresentedViewController = appContext.coreAppCoordinator.topVC if let mintingNav { mintingNav.setMode(mode) - } else if let _ = topPresentedViewController as? AddWalletNavigationController { + } else if let _ = topVC as? AddWalletNavigationController { // MARK: - Ignore minting request when add/import/connect wallet } else if resolvingPrimaryDomainWallet == nil { await popToRootAndWait() @@ -378,7 +393,7 @@ extension HomeTabRouter: PublicProfileViewDelegate { func showDomainMintingInProgress(_ domain: DomainDisplayInfo) { guard domain.isMinting, - let topVC = appContext.coreAppCoordinator.topVC else { return } + let topVC else { return } let mintingDomains = MintingDomainsStorage.retrieveMintingDomains() @@ -392,7 +407,7 @@ extension HomeTabRouter: PublicProfileViewDelegate { } func showDomainTransferringInProgress(_ domain: DomainDisplayInfo) { - guard let topVC = appContext.coreAppCoordinator.topVC else { return } + guard let topVC else { return } UDRouter().showTransferInProgressScreen(domain: domain, transferDomainFlowManager: nil, in: topVC) } @@ -415,16 +430,16 @@ private extension HomeTabRouter { } func isMintingAvailable() async -> Bool { - guard let topPresentedViewController = appContext.coreAppCoordinator.topVC else { return false } + guard let topVC else { return false } guard appContext.networkReachabilityService?.isReachable == true else { - await appContext.pullUpViewService.showYouAreOfflinePullUp(in: topPresentedViewController, + await appContext.pullUpViewService.showYouAreOfflinePullUp(in: topVC, unavailableFeature: .minting) return false } guard User.instance.getAppVersionInfo().mintingIsEnabled else { - await appContext.pullUpViewService.showMintingNotAvailablePullUp(in: topPresentedViewController) + await appContext.pullUpViewService.showMintingNotAvailablePullUp(in: topVC) return false } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeWalletView/HomeWalletView+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeWalletView/HomeWalletView+Entities.swift index 8dd0292ea..3533a771c 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeWalletView/HomeWalletView+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeWalletView/HomeWalletView+Entities.swift @@ -268,3 +268,40 @@ extension HomeWalletView { } } } + +extension HomeWalletView { + enum BuyOptions: String, CaseIterable, PullUpCollectionViewCellItem { + case domains, crypto + + var title: String { + switch self { + case .domains: + String.Constants.selectPullUpBuyDomainsTitle.localized() + case .crypto: + String.Constants.selectPullUpBuyTokensTitle.localized() + } + } + + var subtitle: String? { + switch self { + case .domains: + String.Constants.selectPullUpBuyDomainsSubtitle.localized() + case .crypto: + String.Constants.selectPullUpBuyTokensSubtitle.localized() + } + } + + var disclosureIndicatorStyle: PullUpDisclosureIndicatorStyle { .right } + + var icon: UIImage { + switch self { + case .domains: + return .globeRotated + case .crypto: + return .verticalLines + } + } + + var analyticsName: String { rawValue } + } +} diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeWalletView/HomeWalletViewModel.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeWalletView/HomeWalletViewModel.swift index 6de1ad3b3..30462faec 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeWalletView/HomeWalletViewModel.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeWalletView/HomeWalletViewModel.swift @@ -84,12 +84,24 @@ extension HomeWalletView { })) } case .buy: - router.runPurchaseFlow() + router.pullUp = .default(.homeWalletBuySelectionPullUp(selectionCallback: { [weak self] buyOption in + self?.router.pullUp = nil + self?.didSelectBuyOption(buyOption) + })) case .more: return } } + func didSelectBuyOption(_ buyOption: HomeWalletView.BuyOptions) { + switch buyOption { + case .domains: + router.runPurchaseFlow() + case .crypto: + router.runBuyCryptoFlowTo(wallet: selectedWallet) + } + } + func didSelectDomain(_ domain: DomainDisplayInfo) { showProfile(of: domain) } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/PurchaseDomains/Search/PurchaseSearchDomainsView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/PurchaseDomains/Search/PurchaseSearchDomainsView.swift index aec0c5d34..4fb447963 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/PurchaseDomains/Search/PurchaseSearchDomainsView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/PurchaseDomains/Search/PurchaseSearchDomainsView.swift @@ -295,24 +295,12 @@ private extension PurchaseSearchDomainsView { func loadSuggestions() { guard suggestions.isEmpty else { return } - func waitAndTryAgain() { - Task { - await Task.sleep(seconds: 5) - loadSuggestions() - } - } - Task { do { let suggestions = try await purchaseDomainsService.getDomainsSuggestions(hint: nil) - if suggestions.isEmpty { - Debugger.printFailure("Did load 0 suggestions") - waitAndTryAgain() - } self.suggestions = suggestions } catch { Debugger.printFailure("Failed to load suggestions") - waitAndTryAgain() } } } diff --git a/unstoppable-ios-app/domains-manager-ios/NetworkEnvironment/NetworkConfig.swift b/unstoppable-ios-app/domains-manager-ios/NetworkEnvironment/NetworkConfig.swift index 834340b42..737438804 100644 --- a/unstoppable-ios-app/domains-manager-ios/NetworkEnvironment/NetworkConfig.swift +++ b/unstoppable-ios-app/domains-manager-ios/NetworkEnvironment/NetworkConfig.swift @@ -34,6 +34,13 @@ struct NetworkConfig { return "unstoppabledomains.com" } } + static var websiteBaseUrl: String { + "https://\(Self.websiteHost)" + } + + static var buyCryptoUrl: String { + websiteBaseUrl + "/fiat-ramps" + } static var baseDomainProfileUrl: String { let isTestnetUsed = User.instance.getSettings().isTestnetUsed diff --git a/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift b/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift index 7be2dfd56..6574783c3 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift @@ -445,6 +445,7 @@ extension Analytics { case finishProfileForPurchasedDomains, failedToFinishProfileForPurchasedDomains case searchPurchaseDomainNotSupported case createYourProfile + case homeWalletBuyOptions // Disabled case walletTransactionsSelection, copyWalletAddressSelection diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/globeRotated.imageset/Contents.json b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/globeRotated.imageset/Contents.json new file mode 100644 index 000000000..197ff79e1 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/globeRotated.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "globeRotated.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/globeRotated.imageset/globeRotated.svg b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/globeRotated.imageset/globeRotated.svg new file mode 100644 index 000000000..4825080be --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/globeRotated.imageset/globeRotated.svg @@ -0,0 +1,3 @@ + + + diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/verticalLines.imageset/Contents.json b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/verticalLines.imageset/Contents.json new file mode 100644 index 000000000..d7e6e70c2 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/verticalLines.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "verticalLines.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/verticalLines.imageset/verticalLines.svg b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/verticalLines.imageset/verticalLines.svg new file mode 100644 index 000000000..72c2a6f5d --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/verticalLines.imageset/verticalLines.svg @@ -0,0 +1,3 @@ + + + diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings index e5b6125cf..88210531c 100644 --- a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings @@ -1045,3 +1045,8 @@ More tabs are coming in the next updates."; "PROFILE_SUGGESTION_REASON_FARCASTER_FOLLOWS" = "Social follow on Farcaster"; "PROFILE_SUGGESTION_REASON_FARCASTER_MUTUAL" = "Both users follow each other on Farcaster"; "SEARCH_PROFILES" = "Search profiles"; + +"SELECT_PULL_UP_BUY_DOMAINS_TITLE" = "Buy domains"; +"SELECT_PULL_UP_BUY_TOKENS_TITLE" = "Buy Crypto"; +"SELECT_PULL_UP_BUY_DOMAINS_SUBTITLE" = "Find and acquire new domain names"; +"SELECT_PULL_UP_BUY_TOKENS_SUBTITLE" = "With Apple Pay or card"; diff --git a/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift b/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift index 4c736bde5..0d3e457ab 100644 --- a/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift +++ b/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift @@ -88,7 +88,9 @@ extension Image { static let exploreIcon = Image("exploreIcon") static let globeBold = Image("globeBold") static let searchClearIcon = Image("searchClearIcon") - + static let globeRotated = Image("globeRotated") + static let verticalLines = Image("verticalLines") + static let cryptoFaceIcon = Image("cryptoFaceIcon") static let cryptoPOAPIcon = Image("cryptoPOAPIcon") static let cryptoTransactionIcon = Image("cryptoTransactionIcon") diff --git a/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUp.swift b/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUp.swift index eeb7e0162..6c5dd36d6 100644 --- a/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUp.swift +++ b/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUp.swift @@ -13,6 +13,7 @@ struct ViewPullUp: ViewModifier { static let headerSpacing: CGFloat = 36 static let imageSize: CGFloat = 40 static let titleLineHeight: CGFloat = 28 + static let listContentPadding: CGFloat = 4 @Binding var type: ViewPullUpConfigurationType? var typeChangedCallback: ((ViewPullUpConfigurationType?)->())? = nil @@ -57,12 +58,16 @@ struct ViewPullUp: ViewModifier { } private func closeAndPassCallback(_ callback: MainActorAsyncCallback?) { - type = nil + close() DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { callback?() } } + func close() { + type = nil + } + private func didDismissCurrentPullUp() { appContext.analyticsService.log(event: .pullUpClosed, withParameters: dismissAnalyticParameters) @@ -99,6 +104,10 @@ private extension ViewPullUp { } .multilineTextAlignment(alignmentFrom(textAlignment: getCurrentContentAlignment(configuration: configuration))) + + viewForItemsList(items: configuration.items, + selectionCallback: configuration.itemSelectedCallback) + if let actionButton = configuration.actionButton { buttonViewFor(configuration: configuration, buttonType: actionButton) .padding(EdgeInsets(top: 14, leading: 0, bottom: 0, trailing: 0)) @@ -122,6 +131,42 @@ private extension ViewPullUp { } } + @ViewBuilder + func viewForItemsList(items: [PullUpCollectionViewCellItem], + selectionCallback: ((PullUpCollectionViewCellItem)->())?) -> some View { + VStack(spacing: 0) { + ForEach(items, id: \.title) { item in + selectableListItemView(item: item, + selectionCallback: selectionCallback) + } + } + .padding(Self.listContentPadding) + .background(Color.backgroundOverlay) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .overlay { + RoundedRectangle(cornerRadius: 12) + .stroke(Color.borderMuted, lineWidth: 1) + } + } + + @ViewBuilder + func selectableListItemView(item: PullUpCollectionViewCellItem, + selectionCallback: ((PullUpCollectionViewCellItem)->())?) -> some View { + UDCollectionListRowButton(content: { + ViewPullUpListItemView(item: item) + .padding(.init(horizontal: 12)) + }, callback: { + UDVibration.buttonTap.vibrate() + appContext.analyticsService.log(event: .buttonPressed, + withParameters: [.button: item.analyticsName, + .pullUpName: type?.analyticName.rawValue ?? ""]) + + selectionCallback?(item) + close() + }) + .allowsHitTesting(item.isSelectable) + } + @ViewBuilder func labelViewFor(configuration: ViewPullUpDefaultConfiguration, labelType: ViewPullUpDefaultConfiguration.LabelType, diff --git a/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUpDefaultConfiguration.swift b/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUpDefaultConfiguration.swift index 12c118849..87782c627 100644 --- a/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUpDefaultConfiguration.swift +++ b/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUpDefaultConfiguration.swift @@ -8,11 +8,15 @@ import UIKit struct ViewPullUpDefaultConfiguration { + typealias ItemCallback = ((PullUpCollectionViewCellItem)->()) + let id = UUID() var icon: IconContent? = nil var title: LabelType? var subtitle: Subtitle? = nil var contentAlignment: ContentAlignment = .center + var items: [PullUpCollectionViewCellItem] = PullUpSelectionViewEmptyItem.allCases + var itemSelectedCallback: ItemCallback? = nil var actionButton: ButtonType? = nil var extraButton: ButtonType? = nil var cancelButton: ButtonType? = nil @@ -183,9 +187,16 @@ extension ViewPullUpDefaultConfiguration { } if let subtitle { + height += 8 // Space from title height += heightForSubtitle(subtitle, contentWidth: contentWidth) - height += 8 // Space from title + } + + if !items.isEmpty { + let itemsHeight = items.reduce(0.0, { $0 + $1.height }) + height += itemsHeight + height += ViewPullUp.listContentPadding * 2 // Top and bottom + height += ViewPullUp.sideOffset } if let actionButton { @@ -370,7 +381,36 @@ extension ViewPullUpDefaultConfiguration { dismissCallback: nil) } - + static func legalSelectionPullUp(selectionCallback: @escaping (LegalType)->()) -> ViewPullUpDefaultConfiguration { + return .init(title: .text(String.Constants.settingsLegal.localized()), + items: LegalType.allCases, + itemSelectedCallback: { item in + guard let item = item as? LegalType else { return } + selectionCallback(item) + }, + dismissAble: true, + analyticName: .settingsLegalSelection, + dismissCallback: nil) + } + + static func homeWalletBuySelectionPullUp(selectionCallback: @escaping (HomeWalletView.BuyOptions)->()) -> ViewPullUpDefaultConfiguration { + var selectedItem: HomeWalletView.BuyOptions? + + return .init(title: .text(String.Constants.settingsLegal.localized()), + items: HomeWalletView.BuyOptions.allCases, + itemSelectedCallback: { item in + guard let item = item as? HomeWalletView.BuyOptions else { return } + + selectedItem = item + }, + dismissAble: true, + analyticName: .homeWalletBuyOptions, + dismissCallback: { + if let selectedItem { + selectionCallback(selectedItem) + } + }) + } } diff --git a/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUpListItemView.swift b/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUpListItemView.swift new file mode 100644 index 000000000..aca302da2 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SwiftUI/ViewModifiers/ViewPullUp/ViewPullUpListItemView.swift @@ -0,0 +1,137 @@ +// +// ViewPullUpListItemView.swift +// domains-manager-ios +// +// Created by Oleg Kuplin on 15.03.2024. +// + +import SwiftUI + +struct ViewPullUpListItemView: View { + + let item: PullUpCollectionViewCellItem + + @State private var icon: UIImage? + + + var body: some View { + HStack(spacing: 16) { + iconView() + + HStack(spacing: 0) { + titlesView() + + trailingIndicatorView() + } + } + .frame(height: item.height) + .task { + self.icon = await item.icon + } + } +} + +// MARK: - Private methods +private extension ViewPullUpListItemView { + @ViewBuilder + func iconView() -> some View { + ZStack { + Color(uiColor: item.backgroundColor) + if let icon { + Image(uiImage: icon) + .resizable() + .foregroundStyle(Color(uiColor: item.tintColor)) + .squareFrame(iconSize) + } + } + .squareFrame(iconContainerSize) + .clipShape(Circle()) + .overlay { + if case .imageCentered = item.imageStyle { + Circle() + .stroke(Color.borderSubtle, lineWidth: 1) + } + } + } + + var iconSize: CGFloat { + switch item.imageStyle { + case .largeImage: + item.imageSize.containerSize + case .smallImage: + item.imageSize.imageSize + case .imageCentered: + item.imageSize.imageSize + } + } + var iconContainerSize: CGFloat { + switch item.imageStyle { + case .largeImage: + item.imageSize.containerSize + case .smallImage: + item.imageSize.imageSize + case .imageCentered: + item.imageSize.containerSize + } + } + + @ViewBuilder + func titlesView() -> some View { + HStack { + VStack(alignment: .leading, spacing: 0) { + Text(item.title) + .font(.currentFont(size: 16, weight: .medium)) + .foregroundStyle(Color(uiColor: item.titleColor)) + + if let subtitle = item.subtitle { + Text(subtitle) + .font(.currentFont(size: 14)) + .foregroundStyle(Color(uiColor: item.subtitleColor)) + } + } + .truncationMode(.tail) + .lineLimit(1) + Spacer() + } + .frame(maxWidth: .infinity) + } + + @ViewBuilder + func trailingIndicatorView() -> some View { + switch item.disclosureIndicatorStyle { + case .none: + EmptyView() + case .actionButton(let title, let callback): + actionButtonTrailingView(title: title, + callback: callback) + default: + defaultTrailingView() + } + } + + @ViewBuilder + func actionButtonTrailingView(title: String, + callback: @escaping EmptyCallback) -> some View { + UDButtonView(text: title, + style: .medium(.raisedPrimary), + callback: callback) + } + + @ViewBuilder + func defaultTrailingView() -> some View { + if let icon = item.disclosureIndicatorStyle.icon { + Image(uiImage: icon) + .resizable() + .foregroundStyle(Color.foregroundMuted) + .squareFrame(24) + } + } +} + +#Preview { + let chatPullUpItem = MessagingChatUserPullUpSelectionItem.init(userInfo: .init(wallet: "adasdsdf sd fsd fsdf sd fsd"), isAdmin: false, isPending: false, + unblockCallback: { }) + let legalItem = LegalType.termsOfUse + + return ViewPullUpListItemView(item: legalItem) +}