You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
LeCountdown/LeCountdown/Views/ContentView.swift

316 lines
9.8 KiB

//
// 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
@Published var siriTimer: AbstractTimer? = nil
}
//enum TimerType {
// case timer
// case stopwatch
//}
struct ContentView<T : AbstractTimer>: View {
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var conductor: Conductor
@StateObject var boringContext: BoringContext = BoringContext()
@State private var isEditing: Bool = false
@State private var tipsShown: Bool = false
@State private var siriTipShown: Bool = false
@State private var showSettingsSheet: Bool = false
@State private var showStatsSheet: Bool = false
// @State private var showAddSheet: Bool = false
// @State private var showTimerSheet: Bool = false
// @State private var showStopwatchSheet: Bool = false
@State private var showSubscriptionSheet: Bool = false
var body: some View {
VStack {
if !AppGuard.main.isSubscriber {
SubscriptionButtonView()
}
TimersView(isEditing: self.$isEditing,
showSubscriptionSheet: self.$showSubscriptionSheet,
siriHandler: { timer in
self._handleSiriTips(timer: timer)
})
.environment(\.managedObjectContext, viewContext)
.environmentObject(self.boringContext)
if !conductor.liveTimers.isEmpty {
Spacer()
SiriVolumeView(timer: self.boringContext.siriTimer, siriTipShown: self.$siriTipShown)
LiveTimerListView()
.environment(\.managedObjectContext, viewContext)
.environmentObject(conductor)
.padding(.horizontal, 12.0)
.foregroundColor(.black)
.background(Color(white: 0.9))
.cornerRadius(32.0, corners: [.topRight, .topLeft])
}
}
.navigationTitle(Bundle.main.applicationName)
.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: self.$boringContext.isShowingNewData, content: {
})
// .sheet(isPresented: self.$showAddSheet, content: {
// NewCountdownView(isPresented: $showAddSheet, tabSelection: .constant(0))
// .environment(\.managedObjectContext, viewContext)
// })
.toolbar {
MainToolbarView(isEditing: self.$isEditing)
}
.onAppear {
self._askPermissions()
}
.onOpenURL { url in
self._performActionIfPossible(url: url)
}
}
fileprivate func _handleSiriTips(timer: AbstractTimer) {
let timerId = timer.stringId
if !Preferences.timerSiriTips.contains(timerId) {
self.boringContext.siriTimer = timer
Preferences.timerSiriTips.insert(timerId)
self.siriTipShown = true
} else {
self.boringContext.siriTimer = nil
self.siriTipShown = false
}
}
// MARK: - Business
fileprivate func _hideTip(_ tip: Tip) {
Preferences.lastShownTip = tip.rawValue
self.tipsShown = true
}
fileprivate func _askPermissions() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { success, error in
print("requestAuthorization > 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 = self.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: ToolbarContent {
@Environment(\.managedObjectContext) private var viewContext
@Binding var isEditing: Bool
@State var showSettingsSheet: Bool = false
@State var showStatsSheet: Bool = false
@State var showAddSheet: Bool = false
@State var showTimerSheet: Bool = false
@State var showStopwatchSheet: Bool = false
@State var showLogsSheet: Bool = false
var body: some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
self.isEditing.toggle()
}
} label: {
Text(self.isEditing ? "Done" : "Edit")
}
}
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
withAnimation {
self.showLogsSheet.toggle()
}
} label: {
Image(systemName: "list.dash")
}
.sheet(isPresented: self.$showLogsSheet, content: {
NavigationStack {
LogsView().navigationTitle("Logs")
}
})
if self.haveRecords() {
Button {
withAnimation {
self.showStatsSheet.toggle()
}
} label: {
Image(systemName: "chart.bar.doc.horizontal")
}
.sheet(isPresented: self.$showStatsSheet, content: {
NavigationStack {
ActivitiesView()
}
})
}
Button {
withAnimation {
self.showSettingsSheet.toggle()
}
} label: {
Image(systemName: "gearshape.fill")
}
.sheet(isPresented: self.$showSettingsSheet, content: {
SettingsView()
.presentationDetents([.height(240.0)])
})
Button {
withAnimation {
self.showAddSheet.toggle()
}
} label: {
Image(systemName: "plus")
}
.confirmationDialog("Please select", isPresented: self.$showAddSheet) {
Button("Timer") {
self.showAddSheet = false
self.showTimerSheet.toggle()
}
Button("Stopwatch") {
self.showAddSheet = false
self.showStopwatchSheet.toggle()
}
}
.sheet(isPresented: self.$showStopwatchSheet, content: {
NewStopwatchView(isPresented: $showStopwatchSheet, tabSelection: .constant(0))
})
.sheet(isPresented: self.$showTimerSheet, content: {
NewCountdownView(isPresented: $showTimerSheet, tabSelection: .constant(0))
})
}
}
func haveRecords() -> Bool {
return self.viewContext.count(entityName: "Record") > 0
}
}
fileprivate extension Countdown {
var endDate: Date? {
return Conductor.maestro.currentCountdowns[self.stringId]?.end
}
var isLive: Bool {
return endDate != nil
}
}
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_Previews: PreviewProvider {
// static var previews: some View {
// ContentView<Countdown>()
// .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
// .environmentObject(Conductor.maestro)
//
// }
//}
struct Toolbar_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
Text("Hello")
}
.navigationTitle("Title")
.toolbar {
MainToolbarView(isEditing: .constant(false))
}
}
}