import Combine import SwiftUI struct RoundedBackground: ViewModifier { private let color: Color init(color: Color = Color("CapsuleColor")) { self.color = color } func body(content: Content) -> some View { content .padding() .background( RoundedRectangle(cornerRadius: 8, style: .continuous) .fill() .foregroundColor(color) ) } } struct CapsulaBackground: ViewModifier { private let color: Color init(color: Color = Color("CapsuleColor")) { self.color = color } func body(content: Content) -> some View { content .padding() .background( Capsule() .fill() .foregroundColor(color) ) } } private let navigationCache = LRUCache(capacity: 10) struct NavigationLazyView: View { let build: () -> AnyView let screen: Screen init(_ build: @autoclosure @escaping () -> AnyView, screen: Screen) { self.build = build self.screen = screen } var body: AnyView { if navigationCache[screen.id] == nil { navigationCache[screen.id] = build() } return navigationCache[screen.id]! .onDisappear { navigationCache[screen.id] = nil }.asAny() } } struct Link: ViewModifier where T: View { private let destination: () -> T let screen: Screen init(destination: @autoclosure @escaping () -> T, screen: Screen) { self.destination = destination self.screen = screen } func body(content: Content) -> some View { NavigationLink(destination: NavigationLazyView(destination().asAny(), screen: screen)) { content } } } struct AdaptsToSoftwareKeyboard: ViewModifier { @State var currentHeight: CGFloat = 0 func body(content: Content) -> some View { content .padding(.bottom, currentHeight).animation(.easeOut(duration: 0.25)) .edgesIgnoringSafeArea(currentHeight == 0 ? Edge.Set() : .bottom) .onAppear(perform: subscribeToKeyboardChanges) } private let keyboardHeightOnOpening = Foundation.NotificationCenter.default .publisher(for: UIResponder.keyboardWillShowNotification) .map { $0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect } .map(\.height) private let keyboardHeightOnHiding = Foundation.NotificationCenter.default .publisher(for: UIResponder.keyboardWillHideNotification) .map { _ in CGFloat(0) } private func subscribeToKeyboardChanges() { _ = Publishers.Merge(keyboardHeightOnOpening, keyboardHeightOnHiding) .subscribe(on: DispatchQueue.main) .sink { height in if self.currentHeight == 0 || height == 0 { self.currentHeight = height } } } } struct ClearButton: ViewModifier { @Binding var text: String func body(content: Content) -> some View { HStack { content if !text.isEmpty { Button { self.text = "" } label: { Image(systemName: "delete.left") .foregroundColor(.gray) } } } } } extension View { func roundedBackground() -> some View { modifier(RoundedBackground()) } func buttonBackground() -> some View { modifier(RoundedBackground(color: .accentColor)) } func navigationLink(to screen: Screen, from view: V) -> some View { modifier(Link(destination: view.state.view(for: screen), screen: screen)) } func adaptsToSoftwareKeyboard() -> some View { modifier(AdaptsToSoftwareKeyboard()) } func modal(for screen: Screen?, from view: V) -> some View { onTapGesture { view.state.showModal(for: screen) } } func asAny() -> AnyView { .init(self) } }