import SwiftUI struct OtpPreviewView: View { let seed: String @State private var code = "" @State private var progress = 0.0 // 0.0 – 1.0 within the current 30s window @State private var timer: Timer? var body: some View { HStack(spacing: 12) { VStack(alignment: .leading, spacing: 2) { Text("Current OTP") .font(.caption2) .foregroundStyle(.secondary) Text(formatted(code)) .font(.system(size: 22, weight: .semibold, design: .monospaced)) .foregroundStyle(.primary) } Spacer() ZStack { Circle() .stroke(Color(.systemFill), lineWidth: 3) Circle() .trim(from: 0, to: progress) .stroke(progressColor, style: StrokeStyle(lineWidth: 3, lineCap: .round)) .rotationEffect(.degrees(-90)) .animation(.linear(duration: 0.8), value: progress) Text("\(Int(progress * 30))") .font(.caption2.monospacedDigit()) .foregroundStyle(.secondary) } .frame(width: 36, height: 36) } .padding(.horizontal, 16) .padding(.vertical, 10) .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) .contentShape(Rectangle()) .onTapGesture { UIPasteboard.general.string = code } .onAppear { refresh(); startTimer() } .onDisappear { timer?.invalidate(); timer = nil } } private var progressColor: Color { progress < 0.33 ? .red : progress < 0.6 ? .orange : .green } private func formatted(_ otp: String) -> String { guard otp.count == 6 else { return otp } return "\(otp.prefix(3)) \(otp.suffix(3))" } private func refresh() { code = (try? Totp.generate(seed)) ?? Totp.generate(seed) let seconds = Int(Date().timeIntervalSince1970) % 30 progress = Double(30 - seconds) / 30.0 } private func startTimer() { timer?.invalidate() timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in refresh() } } } #Preview { OtpPreviewView(seed: "JBSWY3DPEHPK3PXP") .padding() }