| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- 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: ViewModifier {
- let screen: Screen
- init(screen: Screen) {
- self.screen = screen
- }
- func body(content: Content) -> some View {
- NavigationLink(value: screen, label: { content })
- }
- }
- struct ScreenNavigation<T>: ViewModifier where T: View {
- private let destination: (Screen) -> T
- init(destination: @escaping (Screen) -> T) {
- self.destination = destination
- }
- func body(content: Content) -> some View {
- content.navigationDestination(
- for: Screen.self,
- destination: { screen in NavigationLazyView(destination(screen).asAny(), screen: screen) }
- )
- }
- }
- 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 _: V) -> some View {
- modifier(Link(screen: screen))
- }
- func screenNavigation<V: BaseView>(_ view: V) -> some View {
- modifier(ScreenNavigation { screen in
- view.state.view(for: 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) }
- var backport: Backport<Self> { Backport(content: self) }
- }
- struct Backport<Content: View> {
- let content: Content
- }
- extension Backport {
- @ViewBuilder func chartXSelection(value: Binding<Date?>) -> some View {
- if #available(iOS 17, *) {
- content.chartXSelection(value: value)
- } else {
- content
- }
- }
- @ViewBuilder func chartForegroundStyleScale(state: any StateModel) -> some View {
- if (state as? Bolus.StateModel)?.forecastDisplayType == ForecastDisplayType.lines ||
- (state as? Home.StateModel)?.forecastDisplayType == ForecastDisplayType.lines
- {
- let modifiedContent = content
- .chartForegroundStyleScale([
- "iob": .blue,
- "uam": Color.uam,
- "zt": Color.zt,
- "cob": .orange
- ])
- if state is Home.StateModel {
- modifiedContent
- .chartLegend(.hidden)
- } else {
- modifiedContent
- }
- } else {
- content
- }
- }
- }
|