| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- 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<Screen.ID, AnyView>(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<T>: 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<V: BaseView>(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<V: BaseView>(for screen: Screen?, from view: V) -> some View {
- onTapGesture {
- view.state.showModal(for: screen)
- }
- }
- func asAny() -> AnyView { .init(self) }
- }
|