another.im-ios/Monal/another.im/Views/SharedComponents/UniversalInputCollection.swift

204 lines
5.7 KiB
Swift
Raw Normal View History

2024-11-19 16:07:47 +00:00
import SwiftUI
// MARK: Public
protocol UniversalInputSelectionElement: Identifiable, Equatable, Hashable {
var icon: Image? { get }
var text: String? { get }
}
public enum UniversalInputCollection {
struct TextField<T: Hashable> {
let prompt: String
@Binding var text: String
var focus: FocusState<T?>.Binding
var fieldType: T
let contentType: UITextContentType
let keyboardType: UIKeyboardType
let submitLabel: SubmitLabel
let action: () -> Void
}
struct SecureField<T: Hashable> {
let prompt: String
@Binding var text: String
var focus: FocusState<T?>.Binding
var fieldType: T
let submitLabel: SubmitLabel
let action: () -> Void
}
struct DropDownMenu<T: Hashable, E: UniversalInputSelectionElement> {
let prompt: String
let elements: [E]
@Binding var selected: E?
var focus: FocusState<T?>.Binding
var fieldType: T
}
}
// MARK: Inputs implementations
extension UniversalInputCollection.TextField: View {
var body: some View {
TextField("", text: $text)
.padding(.horizontal, 8)
.focused(focus, equals: fieldType)
.font(.body2)
.foregroundColor(.Material.Text.main)
.autocorrectionDisabled(true)
.autocapitalization(.none)
.textContentType(contentType)
.keyboardType(keyboardType)
.submitLabel(submitLabel)
.textSelection(.enabled)
.onSubmit {
action()
}
.modifier(UniversalInputModifier(
prompt: prompt,
focus: focus,
fieldType: fieldType,
isActive: isFilled
))
}
var isFilled: Bool {
!text.isEmpty || focus.wrappedValue == fieldType
}
}
extension UniversalInputCollection.SecureField: View {
var body: some View {
SecureField("", text: $text)
.padding(.horizontal, 8)
.focused(focus, equals: fieldType)
.font(.body2)
.foregroundColor(.Material.Text.main)
.autocorrectionDisabled(true)
.autocapitalization(.none)
.textContentType(.password)
.submitLabel(submitLabel)
.textSelection(.disabled)
.onSubmit {
action()
}
.modifier(UniversalInputModifier(
prompt: prompt,
focus: focus,
fieldType: fieldType,
isActive: isFilled
))
}
var isFilled: Bool {
!text.isEmpty || focus.wrappedValue == fieldType
}
}
extension UniversalInputCollection.DropDownMenu: View {
var body: some View {
ZStack {
HStack {
Text(text)
.font(.body2)
.foregroundColor(.Material.Text.main)
.padding(.leading, 8)
Spacer()
}
.modifier(UniversalInputModifier(
prompt: prompt,
focus: focus,
fieldType: fieldType,
isActive: selected != nil
))
Menu {
ForEach(elements, id: \.self.id) { element in
Button {
selected = element
} label: {
Text(element.text ?? "")
}
}
} label: {
Label("", image: "")
.labelStyle(TitleOnlyLabelStyle())
.padding(.vertical)
.frame(height: 48)
.frame(maxWidth: .infinity)
}
}
}
var text: String {
if let text = selected?.text {
return text
} else {
return ""
}
}
}
// MARK: Modifiers
private struct UniversalInputModifier<T: Hashable>: ViewModifier {
let prompt: String
var focus: FocusState<T?>.Binding
var fieldType: T
let isActive: Bool
var promptBackground: Color?
var isCentered: Bool?
var customTapAction: (() -> Void)?
func body(content: Content) -> some View {
VStack(spacing: 0) {
ZStack {
HStack {
Text(isActive ? "" : prompt)
.font(.body2)
.foregroundColor(.Material.Shape.separator)
.padding(8)
Spacer()
}
content
.frame(height: 48)
}
}
.frame(height: 48)
.background {
ZStack {
RoundedRectangle(cornerRadius: 4)
.foregroundColor(.Material.Shape.white)
RoundedRectangle(cornerRadius: 4)
.stroke(Color.Material.Shape.separator)
}
}
.contentShape(Rectangle())
.onTapGesture {
if let customTapAction {
customTapAction()
} else {
if focus.wrappedValue != fieldType {
focus.wrappedValue = fieldType
}
}
}
}
}
// MARK: Validators
extension UniversalInputCollection {
enum Validators {
static func isEmail(_ input: String) -> Bool {
if !input.isEmpty {
let mailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
if !NSPredicate(format: "SELF MATCHES %@", mailRegex).evaluate(with: input) {
return false
} else {
return true
}
} else {
return true
}
}
}
}