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.
316 lines
9.8 KiB
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))
|
|
}
|
|
|
|
}
|
|
}
|
|
|