122 lines
4.3 KiB
Swift
122 lines
4.3 KiB
Swift
//
|
|
// KeyView.swift
|
|
// OTP
|
|
//
|
|
// Created by Shadowfacts on 8/21/21.
|
|
//
|
|
|
|
import SwiftUI
|
|
import OTPKit
|
|
|
|
struct KeyView: View {
|
|
let key: TOTPKey
|
|
let currentCode: TOTPCode
|
|
@State private var copying = false
|
|
@State private var copiedLabelWidth: CGFloat = 0
|
|
|
|
private var formattedCode: String {
|
|
let code = currentCode.code
|
|
let mid = code.index(code.startIndex, offsetBy: code.count / 2)
|
|
return "\(code[code.startIndex..<mid]) \(code[mid...])"
|
|
}
|
|
|
|
init(key: TOTPKey, currentCode: TOTPCode) {
|
|
self.key = key
|
|
self.currentCode = currentCode
|
|
}
|
|
|
|
var body: some View {
|
|
Button(action: self.copy) {
|
|
HStack {
|
|
ZStack {
|
|
HStack {
|
|
VStack(alignment: .leading) {
|
|
Text(key.issuer)
|
|
.font(.title3)
|
|
|
|
if let label = key.label, !label.isEmpty {
|
|
Text(label)
|
|
.font(.footnote)
|
|
}
|
|
}
|
|
Spacer()
|
|
}
|
|
|
|
HStack {
|
|
Spacer()
|
|
|
|
Text(formattedCode)
|
|
.font(.system(.title2, design: .monospaced))
|
|
.opacity(copying ? 0 : 1)
|
|
|
|
Text("Copied!")
|
|
.font(.title2)
|
|
.opacity(copying ? 1 : 0)
|
|
// this nonsense shouldn't be necessary, but the transition only works the first time a code is copied, for some indiscernible reason
|
|
.background(GeometryReader { proxy in
|
|
Color.clear
|
|
.preference(key: CopiedLabelWidth.self, value: proxy.size.width)
|
|
.onPreferenceChange(CopiedLabelWidth.self) { newValue in
|
|
copiedLabelWidth = newValue
|
|
}
|
|
})
|
|
}
|
|
.offset(x: copying ? 0 : copiedLabelWidth)
|
|
.clipped()
|
|
}
|
|
.padding(.trailing, 8)
|
|
|
|
// I don't think this TimelineView should be necessary since the CodeHolder timer fires every .5 seconds
|
|
TimelineView(.animation) { (ctx) in
|
|
ZStack {
|
|
CircularProgressView(progress: progress(at: ctx.date), colorChangeThreshold: 5.0 / Double(key.period))
|
|
|
|
Text(Int(round(currentCode.validUntil.timeIntervalSinceNow)).description)
|
|
.font(.caption.monospacedDigit())
|
|
}
|
|
.frame(width: 30)
|
|
}
|
|
}
|
|
}
|
|
.tint(Color(UIColor.label))
|
|
}
|
|
|
|
private func progress(at date: Date) -> Double {
|
|
let seconds = round(date.timeIntervalSince(currentCode.validFrom))
|
|
let progress = 1 - seconds / Double(key.period)
|
|
return progress
|
|
}
|
|
|
|
private func copy() {
|
|
UIPasteboard.general.string = currentCode.code
|
|
withAnimation(.easeInOut(duration: 0.5)) {
|
|
copying = true
|
|
}
|
|
// .easeInOut(duration: 0.5).delay(0.65) does not work any more
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(650)) {
|
|
withAnimation(.easeInOut(duration: 0.5)) {
|
|
copying = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct CopiedLabelWidth: PreferenceKey {
|
|
static var defaultValue: CGFloat = 0
|
|
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
|
value = nextValue()
|
|
}
|
|
}
|
|
|
|
struct KeyView_Previews: PreviewProvider {
|
|
static var key: TOTPKey {
|
|
TOTPKey(urlComponents: URLComponents(string: "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example")!)!
|
|
}
|
|
static var code: TOTPCode {
|
|
OTPGenerator.generate(key: key)
|
|
}
|
|
static var previews: some View {
|
|
KeyView(key: key, currentCode: code)
|
|
}
|
|
}
|