// // Copyright Amazon.com Inc. or its affiliates. // All Rights Reserved. // // SPDX-License-Identifier: Apache-2.0 // import SwiftUI protocol KeyboardIterableFields: AuthenticatorLogging { associatedtype Field: Hashable var focusedField: FocusState {set get} func focusPreviousField() func focusNextField() var hasPreviousField: Bool { get } var hasNextField: Bool { get } } extension KeyboardIterableFields where Field: RawRepresentable, Field: CaseIterable { private var currentIndex: Int? { return focusedField.wrappedValue?.rawValue } func focusPreviousField() { guard let currentIndex = currentIndex else { return } focusedField.wrappedValue = .init(rawValue: currentIndex - 1) } func focusNextField() { guard let currentIndex = currentIndex else { return } focusedField.wrappedValue = .init(rawValue: currentIndex + 1) } var hasPreviousField: Bool { guard let currentIndex = currentIndex else { return false } return currentIndex - 1 >= 0 } var hasNextField: Bool { guard let currentIndex = currentIndex else { return false } return currentIndex + 1 < Field.allCases.count } } extension View { func keyboardIterableToolbar(fields: F) -> some View { self.modifier(KeyboardIterableToolbar(fields: fields)) } } private struct KeyboardIterableToolbar: ViewModifier where V: KeyboardIterableFields { let fields: V func body(content: Content) -> some View { content .toolbar { SwiftUI.ToolbarItemGroup(placement: .keyboard) { SwiftUI.Button(action: fields.focusPreviousField) { Image(systemName: "chevron.up") } .disabled(!fields.hasPreviousField) SwiftUI.Button(action: fields.focusNextField) { Image(systemName: "chevron.down") } .disabled(!fields.hasNextField) Spacer() SwiftUI.Button("authenticator.keyboardToolbar.Done".localized()) { fields.focusedField.wrappedValue = nil } } } } }