Skip to content

Commit

Permalink
MOB-2153 - Purchase domains Search filters (#628)
Browse files Browse the repository at this point in the history
* MOB-2152 - Implemented tracking of recent search history

* Created filters view

* Prepare to show filters view

* Added filtre button.
Fixed initial filters applied.
  • Loading branch information
Oleg-Pecheneg authored Aug 12, 2024
1 parent b7022de commit 1c3c2b1
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 28 deletions.
12 changes: 12 additions & 0 deletions unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,10 @@
C661DC2E2C06DBED00844AF5 /* PurchaseMPCWalletAlreadyHaveWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C661DC2C2C06DBED00844AF5 /* PurchaseMPCWalletAlreadyHaveWalletView.swift */; };
C6631BB22C69DFAC0045186D /* RecentDomainsToPurchaseSearchStorageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6631BB12C69DFAC0045186D /* RecentDomainsToPurchaseSearchStorageProtocol.swift */; };
C6631BB32C69E0780045186D /* RecentDomainsToPurchaseSearchStorageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6631BB12C69DFAC0045186D /* RecentDomainsToPurchaseSearchStorageProtocol.swift */; };
C6631BB52C69EA600045186D /* PurchaseDomainsSearchFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6631BB42C69EA600045186D /* PurchaseDomainsSearchFiltersView.swift */; };
C6631BB62C69EA600045186D /* PurchaseDomainsSearchFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6631BB42C69EA600045186D /* PurchaseDomainsSearchFiltersView.swift */; };
C6631BB82C69EDB50045186D /* TLDCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6631BB72C69EDB50045186D /* TLDCategory.swift */; };
C6631BB92C69EDB50045186D /* TLDCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6631BB72C69EDB50045186D /* TLDCategory.swift */; };
C663538B294319B400EF1DC7 /* ScrollViewOffsetListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C663538A294319B400EF1DC7 /* ScrollViewOffsetListener.swift */; };
C6637A6A2810074D0000AE56 /* SecurityWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6637A692810074D0000AE56 /* SecurityWindow.swift */; };
C6637A7128100D3A0000AE56 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6637A6F28100D3A0000AE56 /* LaunchViewController.swift */; };
Expand Down Expand Up @@ -3455,6 +3459,8 @@
C661DC292C06DB6A00844AF5 /* MPCOnboardingPurchaseAlreadyHaveWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPCOnboardingPurchaseAlreadyHaveWalletViewController.swift; sourceTree = "<group>"; };
C661DC2C2C06DBED00844AF5 /* PurchaseMPCWalletAlreadyHaveWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseMPCWalletAlreadyHaveWalletView.swift; sourceTree = "<group>"; };
C6631BB12C69DFAC0045186D /* RecentDomainsToPurchaseSearchStorageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentDomainsToPurchaseSearchStorageProtocol.swift; sourceTree = "<group>"; };
C6631BB42C69EA600045186D /* PurchaseDomainsSearchFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseDomainsSearchFiltersView.swift; sourceTree = "<group>"; };
C6631BB72C69EDB50045186D /* TLDCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TLDCategory.swift; sourceTree = "<group>"; };
C663538A294319B400EF1DC7 /* ScrollViewOffsetListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewOffsetListener.swift; sourceTree = "<group>"; };
C6637A692810074D0000AE56 /* SecurityWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityWindow.swift; sourceTree = "<group>"; };
C6637A6F28100D3A0000AE56 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6245,6 +6251,7 @@
C64CFE442C63916600A35B9F /* PurchaseDomainsCartView.swift */,
C64CFE4A2C63940C00A35B9F /* PurchaseSearchEmptyView.swift */,
C64CFE4D2C63B03600A35B9F /* PurchaseDomainsCheckoutButton.swift */,
C6631BB42C69EA600045186D /* PurchaseDomainsSearchFiltersView.swift */,
);
path = Search;
sourceTree = "<group>";
Expand Down Expand Up @@ -7670,6 +7677,7 @@
C669C37E29124C2600837F21 /* SocialsType.swift */,
C69F99552A9F167F004B1958 /* DomainProfileSocialAccount.swift */,
C6BF6BDB2B8F11CC006CC2BD /* TaskWithDeadline.swift */,
C6631BB72C69EDB50045186D /* TLDCategory.swift */,
C6109EA128E6B1CC0027D5D8 /* Toast.swift */,
307EC19A25383EF600D62BA1 /* TransactionItem.swift */,
C6A359342BB6BB2000B1209A /* TxHash.swift */,
Expand Down Expand Up @@ -9421,6 +9429,7 @@
C6102FC02B69361D0098AF75 /* HomeWalletExpandableSectionHeaderView.swift in Sources */,
C6F9FBB82A25C30C00102F81 /* MessagingWebSocketsServiceProtocol.swift in Sources */,
C6098315282B6F7100546392 /* EmptyRootNavigationController.swift in Sources */,
C6631BB82C69EDB50045186D /* TLDCategory.swift in Sources */,
C62900EB2BAAC135008B35A2 /* NavigationTopSafeAreaOffset.swift in Sources */,
C6B40E902B5F83370038CEB0 /* UserProfileSelectionRowView.swift in Sources */,
C6952A432BC50D1D00F4B475 /* FireblocksWalletRPCMessageHandler.swift in Sources */,
Expand Down Expand Up @@ -9655,6 +9664,7 @@
C6E69FF1288AD9DB000A8346 /* NotificationsService.swift in Sources */,
C64939EF2B63579700457363 /* HomeTabPullUpHandlerModifier.swift in Sources */,
30BA2B45252C4E2B0097817E /* DomainItem.swift in Sources */,
C6631BB52C69EA600045186D /* PurchaseDomainsSearchFiltersView.swift in Sources */,
C6C4217829371446005B791B /* DomainProfileTutorialViewController.swift in Sources */,
307852642771FB030039FF40 /* DeepLinks.swift in Sources */,
C6D3B9BA2A6EB8F80091B279 /* PushMessagingChannelsAPIService.swift in Sources */,
Expand Down Expand Up @@ -10585,6 +10595,7 @@
C631DFA22B7B1ED500040221 /* InfiniteRotationModifier.swift in Sources */,
C61807F32B19A7960032E543 /* PreviewWalletConnectServiceV2.swift in Sources */,
C6D6467F2B1ED12900D724AC /* DomainProfileSectionChangeUIDescription.swift in Sources */,
C6631BB62C69EA600045186D /* PurchaseDomainsSearchFiltersView.swift in Sources */,
C6C8F8D92B21835800A9834D /* ProtectWalletViewController.swift in Sources */,
C6D646CF2B1ED28400D724AC /* PrimaryWhiteButton.swift in Sources */,
C6952A382BC500D200F4B475 /* FB_UD_MPCAccountAsset.swift in Sources */,
Expand Down Expand Up @@ -10745,6 +10756,7 @@
C6C8F85A2B217FA600A9834D /* CloudStorage.swift in Sources */,
C688C17A2B843BC000BD233A /* ChatListRequestsRowView.swift in Sources */,
C6C8F8732B21822700A9834D /* OnboardingProtocols.swift in Sources */,
C6631BB92C69EDB50045186D /* TLDCategory.swift in Sources */,
C6C8F8392B217E9600A9834D /* CreateBackupPasswordForNewLocalWalletPresenter.swift in Sources */,
C6D646082B1DC02800D724AC /* UDSubtitleLabel.swift in Sources */,
C61808792B19BC290032E543 /* ClearListBackground.swift in Sources */,
Expand Down
36 changes: 36 additions & 0 deletions unstoppable-ios-app/domains-manager-ios/Entities/TLDCategory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// TLDCategory.swift
// domains-manager-ios
//
// Created by Oleg Kuplin on 12.08.2024.
//

import SwiftUI

enum TLDCategory {
case uns
case ens
case dns

var icon: Image {
switch self {
case .uns:
return .unsTLDLogo
case .ens:
return .ensTLDLogo
case .dns:
return .dnsTLDLogo
}
}

static func categoryFor(tld: String) -> TLDCategory {
switch tld {
case Constants.ensDomainTLD:
return .ens
case _ where Constants.dnsDomainTLDs.contains(tld):
return .dns
default:
return .uns
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,7 @@ extension String {
static let buyDomainFromWebPullUpSubtitle = "BUY_DOMAIN_FROM_WEB_PULL_UP_SUBTITLE"
static let checkoutFromWebPullUpTitle = "CHECKOUT_FROM_WEB_PULL_UP_TITLE"
static let checkoutFromWebPullUpSubtitle = "CHECKOUT_FROM_WEB_PULL_UP_SUBTITLE"
static let endings = "ENDINGS"

// Home
static let homeWalletTokensComeTitle = "HOME_WALLET_TOKENS_COME_TITLE"
Expand Down Expand Up @@ -1419,6 +1420,8 @@ extension String {
var asURL: URL? {
URL(string: self)
}


}

extension String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,28 @@ extension PurchaseDomains {
}
}

extension PurchaseDomains {
struct SearchFiltersHolder {
private(set) var tlds: Set<String> = []
var isFiltersVisible = false

var isFiltersApplied: Bool {
!tlds.isEmpty
}

mutating func setTLDs(_ tlds: Set<String>) {
self.tlds = tlds
}

func filterDomains(_ domains: [DomainToPurchase]) -> [DomainToPurchase] {
if tlds.isEmpty {
return domains
}
return domains.filter({ tlds.contains($0.tld) })
}
}
}

// MARK: - Open methods
extension PurchaseDomains {
struct RecentDomainsToPurchaseSearchStorage: RecentDomainsToPurchaseSearchStorageProtocol {
Expand Down Expand Up @@ -224,5 +246,4 @@ extension PurchaseDomains {
storage.remove()
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//
// PurchaseDomainsSearchFiltersView.swift
// domains-manager-ios
//
// Created by Oleg Kuplin on 12.08.2024.
//

import SwiftUI

struct PurchaseDomainsSearchFiltersView: View {

@Environment(\.dismiss) var dismiss

let appliedFilters: Set<String>
let callback: (Set<String>)->()
private let tlds: [String]
@State private var currentFilters: Set<String> = []

var body: some View {
NavigationStack {
contentView()
.navigationTitle(String.Constants.filter.localized())
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
CloseButtonView {
dismiss()
}
}

ToolbarItem(placement: .topBarTrailing) {
resetButton()
}
}
}
}

init(appliedFilters: Set<String>,
callback: @escaping (Set<String>) -> Void) {
self.appliedFilters = appliedFilters
self.callback = callback
self.tlds = User.instance.getAppVersionInfo().tlds
self._currentFilters = State(wrappedValue: appliedFilters)
}

}

// MARK: - Private methods
private extension PurchaseDomainsSearchFiltersView {
@ViewBuilder
func contentView() -> some View {
VStack {
ScrollView {
filtersSection()
}
doneButton()
}
}

@ViewBuilder
func filtersSection() -> some View {
VStack(alignment: .leading,
spacing: 16) {
Text(String.Constants.endings.localized())
.textAttributes(color: .foregroundDefault,
fontSize: 16,
fontWeight: .medium)
.frame(height: 24)
filtersListView()
}
.padding()
}

@ViewBuilder
func filtersListView() -> some View {
UDCollectionSectionBackgroundView(withShadow: true) {
LazyVStack(alignment: .leading,
spacing: 24) {
ForEach(tlds, id: \.self) { tld in
tldRowView(tld)
}
}
.padding(16)
}
}

@ViewBuilder
func tldRowView(_ tld: String) -> some View {
HStack(spacing: 16) {
TLDCategory.categoryFor(tld: tld)
.icon
.resizable()
.squareFrame(24)
.foregroundStyle(Color.foregroundSecondary)
Text(tld)
.textAttributes(color: .foregroundDefault,
fontSize: 16,
fontWeight: .medium)
Spacer()

UDCheckBoxView(isOn: Binding(
get: {
currentFilters.contains(tld)
}, set: { isOn in
if isOn {
currentFilters.insert(tld)
} else {
currentFilters.remove(tld)
}
})
)
}
.frame(height: 40)
}

@ViewBuilder
func doneButton() -> some View {
UDButtonView(text: String.Constants.doneButtonTitle.localized(),
style: .large(.raisedPrimary)) {
dismiss()
callback(currentFilters)
}
.padding(.horizontal, 16)
}

@ViewBuilder
func resetButton() -> some View {
UDButtonView(text: String.Constants.reset.localized(),
style: .medium(.ghostPrimary)) {
currentFilters = []
}
.disabled(currentFilters.isEmpty)
}
}

#Preview {
PurchaseDomainsSearchFiltersView(appliedFilters: ["x", "crypto"],
callback: { _ in })
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct PurchaseDomainsSearchView: View, ViewAnalyticsLogger {
private var localCart: PurchaseDomains.LocalCart { viewModel.localCart }
@State private var suggestions: [DomainToPurchaseSuggestion] = []
@State private var searchResultHolder: PurchaseDomains.SearchResultHolder = .init()
@State private var searchFiltersHolder: PurchaseDomains.SearchFiltersHolder = .init()
@State private var isLoading: Bool = false
@State private var loadingError: Error?
@State private var searchingText: String = ""
Expand All @@ -35,6 +36,10 @@ struct PurchaseDomainsSearchView: View, ViewAnalyticsLogger {
.sheet(isPresented: $viewModel.localCart.isShowingCart, content: {
PurchaseDomainsCartView()
})
.sheet(isPresented: $searchFiltersHolder.isFiltersVisible, content: {
PurchaseDomainsSearchFiltersView(appliedFilters: searchFiltersHolder.tlds,
callback: updateTLDFilters)
})
.navigationTitle(String.Constants.buyDomainsSearchTitle.localized())
.navigationBarTitleDisplayMode(.inline)
}
Expand All @@ -51,8 +56,12 @@ private extension PurchaseDomainsSearchView {
} else {
contentView()
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
filterButtonView()
}
ToolbarItem(placement: .topBarTrailing) {
cartButtonView()
.padding(.leading, 10)
}
}
.modifier(PurchaseDomainsCheckoutButton())
Expand Down Expand Up @@ -88,6 +97,29 @@ private extension PurchaseDomainsSearchView {
.animation(.default, value: localCart.domains)
}

@ViewBuilder
func filterButtonView() -> some View {
Button {
UDVibration.buttonTap.vibrate()
searchFiltersHolder.isFiltersVisible = true
} label: {
ZStack(alignment: .topTrailing) {
Image.filter
.resizable()
.squareFrame(28)
.foregroundStyle(Color.foregroundDefault)
if searchFiltersHolder.isFiltersApplied {
Circle()
.squareFrame(16)
.foregroundStyle(Color.foregroundAccent)
.offset(x: 6, y: -4)
}
}
}
.buttonStyle(.plain)
.animation(.default, value: localCart.domains)
}

@ViewBuilder
func contentView() -> some View {
ScrollView {
Expand Down Expand Up @@ -355,7 +387,19 @@ private extension PurchaseDomainsSearchView {
}
}

func search(text: String, searchType: SearchResultType) {
func updateTLDFilters(_ tlds: Set<String>) {
self.searchFiltersHolder.setTLDs(tlds)

let searchingText = self.searchingText
if !searchingText.isEmpty {
self.searchingText = ""
self.search(text: searchingText,
searchType: self.searchResultType)
}
}

func search(text: String,
searchType: SearchResultType) {
let text = text.trimmedSpaces.lowercased()
guard searchingText != text else { return }
searchingText = text
Expand Down Expand Up @@ -386,8 +430,9 @@ private extension PurchaseDomainsSearchView {
do {
let searchResult = try await block()
guard searchingText == self.searchingText else { return } // Result is irrelevant, search query has changed
let filteredResult = searchFiltersHolder.filterDomains(searchResult)

searchResultHolder.setDomains(searchResult, searchText: searchingText)
searchResultHolder.setDomains(filteredResult, searchText: searchingText)
self.searchResultType = searchType
} catch {
loadingError = error
Expand Down
Loading

0 comments on commit 1c3c2b1

Please sign in to comment.