diff --git a/punchnet/App/AppContext.swift b/punchnet/App/AppContext.swift index ec07c01..36a128f 100644 --- a/punchnet/App/AppContext.swift +++ b/punchnet/App/AppContext.swift @@ -201,6 +201,27 @@ class AppContext { return [] } + func deleteCacheToken(_ token: String) throws { + let normalizedToken = token.trimmingCharacters(in: .whitespacesAndNewlines) + guard !normalizedToken.isEmpty else { + return + } + + var items = self.loadCacheTokenHistory() + items.removeAll { $0.token == normalizedToken } + items = Array(items.prefix(maxCachedTokenCount)) + + let historyData = try JSONEncoder().encode(items) + try KeychainStore.shared.save(historyData, account: cachedTokenHistoryAccount) + + if let firstToken = items.first?.token, + let data = firstToken.data(using: .utf8) { + try KeychainStore.shared.save(data, account: cachedTokenAccount) + } else { + try KeychainStore.shared.delete(account: cachedTokenAccount) + } + } + private func saveCacheToken(_ token: String, networkName: String) throws { let normalizedToken = token.trimmingCharacters(in: .whitespacesAndNewlines) guard !normalizedToken.isEmpty else { diff --git a/punchnet/Features/Auth/Views/LoginView.swift b/punchnet/Features/Auth/Views/LoginView.swift index d86d47c..adcf368 100644 --- a/punchnet/Features/Auth/Views/LoginView.swift +++ b/punchnet/Features/Auth/Views/LoginView.swift @@ -299,27 +299,40 @@ struct LoginTokenView: View { private var tokenHistoryPopover: some View { VStack(alignment: .leading, spacing: 0) { ForEach(visibleTokenHistory, id: \.token) { item in - Button(action: { - self.token = item.token - self.dismissTokenHistory() - }) { - HStack(spacing: 8) { - Image(systemName: item.token == normalizedToken ? "checkmark" : "key.fill") - .foregroundColor(.secondary) - .frame(width: 14) + HStack(spacing: 4) { + Button(action: { + self.token = item.token + self.dismissTokenHistory() + }) { + HStack(spacing: 8) { + Image(systemName: item.token == normalizedToken ? "checkmark" : "key.fill") + .foregroundColor(.secondary) + .frame(width: 14) - Text(tokenHistoryTitle(item)) - .font(.system(size: 12)) - .lineLimit(1) - .truncationMode(.middle) + Text(tokenHistoryTitle(item)) + .font(.system(size: 12)) + .lineLimit(1) + .truncationMode(.middle) - Spacer(minLength: 0) + Spacer(minLength: 0) + } + .contentShape(Rectangle()) + .padding(.leading, 10) + .padding(.vertical, 6) } - .contentShape(Rectangle()) - .padding(.horizontal, 10) - .padding(.vertical, 6) + .buttonStyle(.plain) + + Button(action: { + self.deleteTokenHistoryItem(item) + }) { + Image(systemName: "trash") + .font(.system(size: 11, weight: .medium)) + .foregroundColor(.secondary) + .frame(width: 24, height: 24) + } + .buttonStyle(.plain) } - .buttonStyle(.plain) + .padding(.trailing, 6) } } .frame(width: 280) @@ -341,6 +354,17 @@ struct LoginTokenView: View { self.showTokenHistory = wantsTokenHistory && !visibleTokenHistory.isEmpty } + private func deleteTokenHistoryItem(_ item: TokenHistoryItem) { + do { + try self.appContext.deleteCacheToken(item.token) + self.tokenHistory = self.appContext.loadCacheTokenHistory() + self.showTokenHistory = wantsTokenHistory && !visibleTokenHistory.isEmpty + } catch { + self.showAlert = true + self.errorMessage = error.localizedDescription + } + } + private func tokenHistoryTitle(_ item: TokenHistoryItem) -> String { guard let networkName = item.networkName, !networkName.isEmpty else { return item.token