OTP/OTP/Views/AppView.swift

140 lines
4.1 KiB
Swift

//
// AppView.swift
// OTP
//
// Created by Shadowfacts on 8/21/21.
//
import SwiftUI
import OTPKit
import Combine
struct AppView: View {
@ObservedObject private var store: KeyStore
@ObservedObject private var entryHolder: CodeHolder
@State private var isPresentingPreferences = false
init() {
self.store = .shared
self.entryHolder = CodeHolder(store: .shared) { (entry) in entry.folderID == nil }
}
var body: some View {
NavigationView {
List {
KeysSection(codeHolder: entryHolder)
Section {
NavigationLink("All Keys") {
AllKeysView()
}
}
FoldersSection()
}
.listStyle(.insetGrouped)
.navigationTitle("OTP")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
isPresentingPreferences = true
} label: {
Label("Preferences", systemImage: "gear")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
AddKeyButton(folderID: nil, canAddFolder: true)
}
}
}
.sheet(isPresented: $isPresentingPreferences, content: self.preferencesSheet)
}
private func preferencesSheet() -> some View {
NavigationView {
PreferencesView()
}
}
struct CodeEntry: Identifiable, Equatable, Hashable {
let entry: KeyData.Entry
let code: TOTPCode
var key: TOTPKey { entry.key }
var id: UUID { entry.id }
init(_ entry: KeyData.Entry) {
self.entry = entry
self.code = OTPGenerator.generate(key: entry.key)
}
}
class CodeHolder: ObservableObject {
private let store: KeyStore
private let entryFilter: ((KeyData.Entry) -> Bool)?
private var timer: Timer!
private var cancellables = Set<AnyCancellable>()
init(store: KeyStore, entryFilter: ((KeyData.Entry) -> Bool)? = nil) {
self.store = store
self.entryFilter = entryFilter
updateTimer(entries: filterEntries(from: store.data))
store.$data
.sink { [unowned self] (newData) in
self.objectWillChange.send()
self.updateTimer(entries: filterEntries(from: newData!))
}
.store(in: &cancellables)
}
var entries: [CodeEntry] {
return filterEntries(from: store.data).map { CodeEntry($0) }
}
var sortedEntries: [CodeEntry] {
return entries.sorted(by: { (a, b) in
if a.key.issuer == b.key.issuer,
let aLabel = a.key.label,
let bLabel = b.key.label {
return aLabel < bLabel
} else {
return a.key.issuer < b.key.issuer
}
})
}
private func filterEntries(from data: KeyData) -> [KeyData.Entry] {
if let filter = entryFilter {
return data.entries.filter(filter)
} else {
return data.entries
}
}
private func updateTimer(entries: [KeyData.Entry]) {
if entries.isEmpty {
timer?.invalidate()
return
} else if timer == nil || !timer.isValid {
timer = .scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] (timer) in
guard let self = self else {
timer.invalidate()
return
}
self.objectWillChange.send()
}
timer.tolerance = 0.01
}
}
}
}
struct AppView_Previews: PreviewProvider {
static var previews: some View {
AppView()
}
}