@@ -33,6 +33,7 @@ public struct MFAEnrolmentView {
33
33
@State private var isLoading = false
34
34
@State private var errorMessage = " "
35
35
@State private var displayName = " "
36
+ @State private var showCopiedFeedback = false
36
37
37
38
@FocusState private var focus : FocusableField ?
38
39
@@ -169,6 +170,20 @@ public struct MFAEnrolmentView {
169
170
resetForm ( )
170
171
authService. authView = . authPicker
171
172
}
173
+
174
+ private func copyToClipboard( _ text: String ) {
175
+ UIPasteboard . general. string = text
176
+
177
+
178
+ // Show feedback
179
+ showCopiedFeedback = true
180
+
181
+ // Quickly show it has been copied to the clipboard
182
+ Task {
183
+ try ? await Task . sleep ( nanoseconds: 500_000_000 )
184
+ showCopiedFeedback = false
185
+ }
186
+ }
172
187
}
173
188
174
189
extension MFAEnrolmentView : View {
@@ -502,14 +517,37 @@ extension MFAEnrolmentView: View {
502
517
Text ( " Manual Entry Key: " )
503
518
. font ( . headline)
504
519
505
- Text ( totpInfo. sharedSecretKey)
506
- . font ( . system( . body, design: . monospaced) )
507
- . padding ( )
508
- . background ( Color . gray. opacity ( 0.1 ) )
509
- . cornerRadius ( 8 )
510
- . textSelection ( . enabled)
520
+ VStack ( spacing: 8 ) {
521
+ Button ( action: {
522
+ copyToClipboard ( totpInfo. sharedSecretKey)
523
+ } ) {
524
+ HStack {
525
+ Text ( totpInfo. sharedSecretKey)
526
+ . font ( . system( . body, design: . monospaced) )
527
+ . lineLimit ( 1 )
528
+ . minimumScaleFactor ( 0.5 )
529
+
530
+ Spacer ( )
531
+
532
+ Image ( systemName: showCopiedFeedback ? " checkmark " : " doc.on.doc " )
533
+ . foregroundColor ( showCopiedFeedback ? . green : . blue)
534
+ }
535
+ . padding ( )
536
+ . background ( Color . gray. opacity ( 0.1 ) )
537
+ . cornerRadius ( 8 )
538
+ }
539
+ . buttonStyle ( . plain)
511
540
. accessibilityIdentifier ( " totp-secret-key " )
512
541
542
+ if showCopiedFeedback {
543
+ Text ( " Copied to clipboard! " )
544
+ . font ( . caption)
545
+ . foregroundColor ( . green)
546
+ . transition ( . opacity)
547
+ }
548
+ }
549
+ . animation ( . easeInOut( duration: 0.2 ) , value: showCopiedFeedback)
550
+
513
551
TextField ( " Display Name (Optional) " , text: $displayName)
514
552
. textFieldStyle ( . roundedBorder)
515
553
. accessibilityIdentifier ( " display-name-field " )
0 commit comments