// // ContentView.swift // LeCountdown // // Created by Laurent Morvillier on 20/01/2023. // import SwiftUI import CoreData import Combine class BoringContext : ObservableObject { @Published var isShowingNewData = false @Published var error: Error? @Published var showDefaultAlert: Bool = false @Published var showPermissionAlert: Bool = false } class TimerSpot : Identifiable, Equatable { var id: Int16 { return self.order } var order: Int16 var timer: AbstractTimer? init(order: Int16, timer: AbstractTimer? = nil) { self.order = order self.timer = timer } func setOrder(order: Int16) { self.order = order self.timer?.order = order } static func == (lhs: TimerSpot, rhs: TimerSpot) -> Bool { return lhs.order == rhs.order && lhs.timer?.stringId == rhs.timer?.stringId } } struct ContentView: View { @StateObject var boringContext: BoringContext = BoringContext() @EnvironmentObject var conductor: Conductor @Environment(\.managedObjectContext) private var viewContext @FetchRequest( sortDescriptors: [NSSortDescriptor(keyPath: \T.order, ascending: true)], animation: .default) private var timers: FetchedResults @State private var isEditing: Bool = false @State private var tipsShown: Bool = false fileprivate let itemSpacing: CGFloat = 10.0 var body: some View { let columns: [GridItem] = self._columns() GeometryReader { reader in let width: CGFloat = reader.size.width / CGFloat(columns.count) - 15.0 VStack { ScrollView { LazyVGrid( columns: columns, spacing: itemSpacing ) { ReorderableForEach(items: Array(timers)) { timer in DialView(timer: timer, isEditingBinding: self.$isEditing, frameSize: width) .environment(\.managedObjectContext, viewContext) .environmentObject(Conductor.maestro) .environmentObject(boringContext) } moveAction: { from, to in self._reorder(from: from, to: to) } } }.padding(.horizontal, itemSpacing) if !self.tipsShown, let tip = Preferences.tipToShow { TipView(tip: tip) { self._hideTip(tip) }.padding() } if !conductor.liveTimers.isEmpty { LiveTimerListView() .environment(\.managedObjectContext, viewContext) .environmentObject(conductor) .foregroundColor(.white) .background(Color(white: 0.1)) .cornerRadius(32.0, corners: [.topRight, .topLeft]) } } } .navigationTitle("Home") .alert(boringContext.error?.localizedDescription ?? "missing error", isPresented: $boringContext.showDefaultAlert) { Button("OK", role: .cancel) { } } .alert("You need to accept notifications, please check your settings", isPresented: $boringContext.showPermissionAlert) { PermissionAlertView() } .sheet(isPresented: $boringContext.isShowingNewData, content: { // self._newView(isPresented: $boringContext.isShowingNewData) // .environment(\.managedObjectContext, viewContext) }) .toolbar { // ToolbarItem(placement: .navigationBarTrailing) { // Button { // self.boringContext.isShowingNewData = true // } label: { // HStack { // Image(systemName: "plus") // } // } // } ToolbarItem(placement: .navigationBarLeading) { Button { withAnimation { self.isEditing.toggle() } } label: { Text(self.isEditing ? "Done" : "Edit") } } } .onAppear { self._askPermissions() // self.showLiveTimersSheet = !conductor.liveTimers.isEmpty } .onOpenURL { url in self._performActionIfPossible(url: url) } } fileprivate func _columnCount() -> Int { #if os(iOS) if UIDevice.isPhoneIdiom { return 2 } else { return 3 } #else return 3 #endif } fileprivate func _columns() -> [GridItem] { return (0.. success = \(success), error = \(String(describing: error))") } } fileprivate func _performActionIfPossible(url: URL) { // hide new window if launching a timer self.boringContext.isShowingNewData = false print("_performActionIfPossible") let urlString = url.absoluteString if let timer = viewContext.object(stringId: urlString) as? AbstractTimer { if timer is T { print("performAction") TimerRouter.performAction(timer: timer) { result in switch result { case .success: break case .failure(let failure): switch failure { case TimerRouterError.notificationAuthorizationMissing: self.boringContext.showPermissionAlert = true default: self.boringContext.error = failure self.boringContext.showDefaultAlert = true } } } } // print("Start countdown: \(countdown.name ?? ""), \(countdown.duration)") // self._launchCountdown(countdown) } else { print("timer not found with id = \(urlString)") } } } struct MainToolbarView: View { var isShowingNewData: Binding var body: some View { Button { self.isShowingNewData.wrappedValue = true } label: { HStack { Image(systemName: "timer") Text("countdown") } } Button { self.isShowingNewData.wrappedValue = true } label: { HStack { Image(systemName: "stopwatch") Text("stopwatch") } } Button { self.isShowingNewData.wrappedValue = true } label: { HStack { Image(systemName: "alarm") Text("alarm") } } } } fileprivate extension Countdown { var endDate: Date? { return Conductor.maestro.currentCountdowns[self.stringId]?.end } var isLive: Bool { return endDate != nil } // var colorForStatus: Color { // if isLive { // return Color(red: 0.9, green: 1.0, blue: 0.95) // } else { // return Color(red: 0.9, green: 0.95, blue: 1.0) // } // } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) .environmentObject(Conductor.maestro) } }