Skip to content

Commit

Permalink
Fixed token tab
Browse files Browse the repository at this point in the history
  • Loading branch information
joeldavidw committed Aug 1, 2024
1 parent ce19371 commit fc833ce
Showing 1 changed file with 110 additions and 98 deletions.
208 changes: 110 additions & 98 deletions Chronos/App/Tabs/Tokens/TokensTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import SwiftUI

struct TokenPair: Identifiable {
var id: ObjectIdentifier

var token: Token
var encToken: EncryptedToken
}
Expand All @@ -24,112 +23,71 @@ let sortOptions: [(title: String, criteria: TokenSortOrder)] = [
]

class TokenPairsCache {
var vaultsHash: Int = 0
var encryptedTokensHash: Int = 0
var tokenPairs: [TokenPair] = []

func update(vaultsHash: Int, tokenPairs: [TokenPair]) {
self.vaultsHash = vaultsHash
func update(encryptedTokensHash: Int, tokenPairs: [TokenPair]) {
self.encryptedTokensHash = encryptedTokensHash
self.tokenPairs = tokenPairs
}
}

struct TokensTab: View {
@Query private var vaults: [Vault]

@EnvironmentObject var loginStatus: LoginStatus

@State private var showTokenAddSheet = false
@State private var detentHeight: CGFloat = 0
@State private var sortCriteria: TokenSortOrder = .ISSUER_ASC
@State private var searchQuery = ""

@State private var filteredAndSortedTokenPairs: [TokenPair] = []
@State private var tokenPairs: [TokenPair] = []
@State private var tokenPairsCache = TokenPairsCache()

let cryptoService = Container.shared.cryptoService()
let stateService = Container.shared.stateService()

let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()

private var encryptedTokens: [EncryptedToken] {
if !loginStatus.loggedIn {
return []
}

guard let vaultId = stateService.getVaultId(),
let vault = vaults.first(where: { $0.vaultId == vaultId })
else {
return []
}

return vault.encryptedTokens ?? []
}

var body: some View {
NavigationStack {
List(filteredAndSortedTokenPairs) { tokenPair in
List(tokenPairs) { tokenPair in
TokenRowView(tokenPair: tokenPair, timer: timer)
}
.onAppear {
Task {
await updateTokenPairs()
}
}
.onChange(of: vaults) { _, _ in
Task {
await updateTokenPairs()
}
.onAppear { Task { await updateTokenPairs() } }
.onChange(of: encryptedTokens) { _, _ in
Task { await updateTokenPairs() }
}
.onChange(of: sortCriteria) { _, _ in
updateFilteredAndSortedTokenPairs()
sortAndFilterTokenPairs()
}
.onChange(of: searchQuery) { _, _ in
updateFilteredAndSortedTokenPairs()
sortAndFilterTokenPairs()
}
.listStyle(.plain)
.background(Color(red: 0.04, green: 0, blue: 0.11))
.navigationTitle("Tokens")
.navigationBarTitleDisplayMode(.inline)
.searchable(text: $searchQuery, prompt: Text("Search tokens"))
.toolbar {
Menu {
ForEach(sortOptions, id: \.criteria) { option in
Button {
sortCriteria = option.criteria
} label: {
if sortCriteria == option.criteria {
Label(option.title, systemImage: "checkmark")
} else {
Text(option.title)
}
}
}
} label: {
Label("Sort Order", systemImage: "arrow.up.arrow.down")
}
.menuOrder(.fixed)

Button {
showTokenAddSheet.toggle()
} label: {
Image(systemName: "plus")
}
ToolbarContent()
}
.overlay(
Group {
if filteredAndSortedTokenPairs.isEmpty {
VStack {
Image(systemName: searchQuery.isEmpty ? "qrcode.viewfinder" : "magnifyingglass")
.font(.system(size: 64))
.foregroundColor(.gray)
.opacity(0.8)

Text(searchQuery.isEmpty ? "No tokens found. Add one by pressing the + icon at the top right corner or the button below." : "No results found")
.padding(.top, 4)
.multilineTextAlignment(.center)
.foregroundColor(.gray)
.opacity(0.8)

if searchQuery.isEmpty {
Button {
showTokenAddSheet.toggle()
} label: {
Text("Add Token")
.padding(.horizontal, 4)
.bold()
}
.padding(.top, 4)
}
}
.padding(.horizontal, 24)
}
}
EmptyStateView()
)
.sheet(isPresented: $showTokenAddSheet) {
AddTokenView()
Expand All @@ -142,52 +100,106 @@ struct TokensTab: View {
}
}

func updateTokenPairs() async {
guard loginStatus.loggedIn else {
filteredAndSortedTokenPairs = []
return
private func ToolbarContent() -> some ToolbarContent {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Menu {
ForEach(sortOptions, id: \.criteria) { option in
Button {
sortCriteria = option.criteria
} label: {
if sortCriteria == option.criteria {
Label(option.title, systemImage: "checkmark")
} else {
Text(option.title)
}
}
}
} label: {
Label("Sort Order", systemImage: "arrow.up.arrow.down")
}
.menuOrder(.fixed)

Button {
showTokenAddSheet.toggle()
} label: {
Image(systemName: "plus")
}
}
}

private func EmptyStateView() -> some View {
Group {
if tokenPairs.isEmpty {
VStack {
Image(systemName: searchQuery.isEmpty ? "qrcode.viewfinder" : "magnifyingglass")
.font(.system(size: 64))
.foregroundColor(.gray)
.opacity(0.8)

Text(searchQuery.isEmpty ? "No tokens found. Add one by pressing the + icon at the top right corner or the button below." : "No results found")
.padding(.top, 4)
.multilineTextAlignment(.center)
.foregroundColor(.gray)
.opacity(0.8)

if searchQuery.isEmpty {
Button {
showTokenAddSheet.toggle()
} label: {
Text("Add Token")
.padding(.horizontal, 4)
.bold()
}
.padding(.top, 4)
}
}
.padding(.horizontal, 24)
}
}
}

let vaultId = stateService.getVaultId()
guard let vault = vaults.first(where: { $0.vaultId == vaultId }) else {
filteredAndSortedTokenPairs = []
private func updateTokenPairs() async {
guard loginStatus.loggedIn else {
tokenPairs = []
return
}

if vaults.hashValue == tokenPairsCache.vaultsHash {
filteredAndSortedTokenPairs = tokenPairsCache.tokenPairs
if encryptedTokens.hashValue == tokenPairsCache.encryptedTokensHash {
tokenPairs = tokenPairsCache.tokenPairs
sortAndFilterTokenPairs()
return
}

let decryptedPairs: [TokenPair] = (vault.encryptedTokens ?? []).compactMap { encToken in
let decryptedPairs: [TokenPair] = encryptedTokens.compactMap { encToken in
guard let decryptedToken = cryptoService.decryptToken(encryptedToken: encToken) else {
return nil
}
return TokenPair(id: encToken.id, token: decryptedToken, encToken: encToken)
}

tokenPairsCache.update(vaultsHash: vaults.hashValue, tokenPairs: decryptedPairs)
updateFilteredAndSortedTokenPairs()
tokenPairsCache.update(encryptedTokensHash: encryptedTokens.hashValue, tokenPairs: decryptedPairs)
tokenPairs = decryptedPairs
sortAndFilterTokenPairs()
}

func updateFilteredAndSortedTokenPairs() {
let filteredPairs: [TokenPair] = tokenPairsCache.tokenPairs.filter { tokenPair in
searchQuery.isEmpty ||
tokenPair.token.issuer.localizedCaseInsensitiveContains(searchQuery) ||
tokenPair.token.account.localizedCaseInsensitiveContains(searchQuery)
}

filteredAndSortedTokenPairs = filteredPairs.sorted { token1, token2 in
switch sortCriteria {
case .ISSUER_ASC:
return token1.token.issuer.localizedCaseInsensitiveCompare(token2.token.issuer) == .orderedAscending
case .ISSUER_DESC:
return token1.token.issuer.localizedCaseInsensitiveCompare(token2.token.issuer) == .orderedDescending
case .ACCOUNT_ASC:
return token1.token.account.localizedCaseInsensitiveCompare(token2.token.account) == .orderedAscending
case .ACCOUNT_DESC:
return token1.token.account.localizedCaseInsensitiveCompare(token2.token.account) == .orderedDescending
private func sortAndFilterTokenPairs() {
tokenPairs = tokenPairs
.filter { tokenPair in
searchQuery.isEmpty ||
tokenPair.token.issuer.localizedCaseInsensitiveContains(searchQuery) ||
tokenPair.token.account.localizedCaseInsensitiveContains(searchQuery)
}
.sorted { token1, token2 in
switch sortCriteria {
case .ISSUER_ASC:
return token1.token.issuer.localizedCaseInsensitiveCompare(token2.token.issuer) == .orderedAscending
case .ISSUER_DESC:
return token1.token.issuer.localizedCaseInsensitiveCompare(token2.token.issuer) == .orderedDescending
case .ACCOUNT_ASC:
return token1.token.account.localizedCaseInsensitiveCompare(token2.token.account) == .orderedAscending
case .ACCOUNT_DESC:
return token1.token.account.localizedCaseInsensitiveCompare(token2.token.account) == .orderedDescending
}
}
}
}
}

0 comments on commit fc833ce

Please sign in to comment.