diff --git a/Chronos/App/Tabs/Tokens/Row/TokenRowView.swift b/Chronos/App/Tabs/Tokens/Row/TokenRowView.swift index e29998a..ebde7f6 100644 --- a/Chronos/App/Tabs/Tokens/Row/TokenRowView.swift +++ b/Chronos/App/Tabs/Tokens/Row/TokenRowView.swift @@ -20,6 +20,8 @@ struct TokenRowView: View { let timer: Publishers.Autoconnect + let triggerSortAndFilterTokenPairs: () -> Void + var token: Token { return tokenPair.token } @@ -28,7 +30,8 @@ struct TokenRowView: View { return tokenPair.encToken } - let otpService = Container.shared.otpService() + private let otpService = Container.shared.otpService() + private let cryptoService = Container.shared.cryptoService() var body: some View { VStack(alignment: .leading, spacing: 8) { @@ -42,6 +45,14 @@ struct TokenRowView: View { .foregroundStyle(.gray) .lineLimit(1) } + + if token.pinned ?? false { + Spacer() + + Image(systemName: "pin.fill") + .font(.system(size: 12)) + .rotationEffect(Angle(degrees: 45)) + } } if stateTapToRevealEnabled && !tokenRevealed { @@ -149,6 +160,18 @@ struct TokenRowView: View { } .tint(.blue) + Button { + token.pinned = !(token.pinned ?? false) + cryptoService.updateEncryptedToken(encryptedToken: encryptedToken, token: token) + triggerSortAndFilterTokenPairs() + } label: { + VStack(alignment: .center) { + Image(systemName: token.pinned ?? false ? "pin.slash" : "pin") + Text(token.pinned ?? false ? "Unpin" : "Pin") + } + } + .tint(.indigo) + Button { self.showTokenQRSheet = true } label: { @@ -157,7 +180,7 @@ struct TokenRowView: View { Text("QR") } } - .tint(.indigo) + .tint(.gray) } } diff --git a/Chronos/App/Tabs/Tokens/TokensTab.swift b/Chronos/App/Tabs/Tokens/TokensTab.swift index 324d4e7..1c87907 100644 --- a/Chronos/App/Tabs/Tokens/TokensTab.swift +++ b/Chronos/App/Tabs/Tokens/TokensTab.swift @@ -67,7 +67,7 @@ struct TokensTab: View { var body: some View { NavigationStack { List(tokenPairs) { tokenPair in - TokenRowView(tokenPair: tokenPair, timer: timer) + TokenRowView(tokenPair: tokenPair, timer: timer, triggerSortAndFilterTokenPairs: self.sortAndFilterTokenPairs) } .onAppear { Task { await updateTokenPairs() } } .onChange(of: encryptedTokens) { _, _ in @@ -194,6 +194,17 @@ struct TokensTab: View { tokenPair.token.account.localizedCaseInsensitiveContains(searchQuery) } .sorted { token1, token2 in + let pinned1 = token1.token.pinned ?? false + let pinned2 = token2.token.pinned ?? false + + if pinned1 != pinned2 { + return pinned1 + } + + if pinned1 { + return token1.token.issuer.localizedCaseInsensitiveCompare(token2.token.issuer) == .orderedAscending + } + switch sortCriteria { case .ISSUER_ASC: return token1.token.issuer.localizedCaseInsensitiveCompare(token2.token.issuer) == .orderedAscending diff --git a/Chronos/Data/Token.swift b/Chronos/Data/Token.swift index cb406a1..d0a1c95 100644 --- a/Chronos/Data/Token.swift +++ b/Chronos/Data/Token.swift @@ -30,6 +30,9 @@ class Token: Codable, Identifiable { // HOTP var counter: Int = 0 + + // Extra Data + var pinned: Bool? = false } func validateToken( diff --git a/Chronos/Services/CryptoService.swift b/Chronos/Services/CryptoService.swift index d46ee30..a0c0858 100644 --- a/Chronos/Services/CryptoService.swift +++ b/Chronos/Services/CryptoService.swift @@ -135,6 +135,7 @@ extension CryptoService { let tokenJson = try JSONDecoder().decode(Token.self, from: Data(decrypted.plainText)) return tokenJson } catch { + print(error) return nil } }