diff --git a/OTP/Views/KeyView.swift b/OTP/Views/KeyView.swift index 2f656e6..a28d7b4 100644 --- a/OTP/Views/KeyView.swift +++ b/OTP/Views/KeyView.swift @@ -12,6 +12,7 @@ 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 @@ -27,36 +28,48 @@ struct KeyView: View { var body: some View { Button(action: self.copy) { HStack { - VStack(alignment: .leading) { - Text(key.issuer) - .font(.title3) - - if let label = key.label, !label.isEmpty { - Text(label) - .font(.footnote) + 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() } - - Spacer() - - - if copying { - Text("Copied!") - .font(.title2) - .transition(.move(edge: .trailing).combined(with: .opacity)) - } else { - Text(formattedCode) - .font(.system(.title2, design: .monospaced)) - .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)).combined(with: .opacity)) - } - - // Text("\(currentCode.validUntil, style: .relative)") - // .font(.body.monospacedDigit()) + .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: Date()), colorChangeThreshold: 5.0 / Double(key.period)) + CircularProgressView(progress: progress(at: ctx.date), colorChangeThreshold: 5.0 / Double(key.period)) Text(Int(round(currentCode.validUntil.timeIntervalSinceNow)).description) .font(.caption.monospacedDigit()) @@ -79,12 +92,22 @@ struct KeyView: View { withAnimation(.easeInOut(duration: 0.5)) { copying = true } - withAnimation(.easeInOut(duration: 0.5).delay(0.65)) { - copying = false + // .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")!)!