release
Laurent 3 years ago
parent 676c813bab
commit 1a8bcf6a4d
  1. 20
      LeCountdown.xcodeproj/project.pbxproj
  2. 3
      LeCountdown/LeCountdownApp.swift
  3. 15
      LeCountdown/Model/Model+Extensions.swift
  4. 56
      LeCountdown/TimerRouter.swift
  5. 31
      LeCountdown/Views/Components/PermissionAlertView.swift
  6. 201
      LeCountdown/Views/ContentView.swift
  7. 98
      LeCountdown/Views/DialView.swift

@ -120,6 +120,9 @@
C4F8B1C0298ACA61005C86A5 /* Model+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C806298195E600BF3EF9 /* Model+Extensions.swift */; }; C4F8B1C0298ACA61005C86A5 /* Model+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C806298195E600BF3EF9 /* Model+Extensions.swift */; };
C4F8B1C3298ACBDB005C86A5 /* Sound.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA912987CC8A0054D761 /* Sound.swift */; }; C4F8B1C3298ACBDB005C86A5 /* Sound.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA912987CC8A0054D761 /* Sound.swift */; };
C4F8B1C6298ACC1F005C86A5 /* SoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA8E2987B83B0054D761 /* SoundPlayer.swift */; }; C4F8B1C6298ACC1F005C86A5 /* SoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA8E2987B83B0054D761 /* SoundPlayer.swift */; };
C4F8B1D0298BF2E2005C86A5 /* DialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B1CF298BF2E2005C86A5 /* DialView.swift */; };
C4F8B1D2298BF646005C86A5 /* PermissionAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B1D1298BF646005C86A5 /* PermissionAlertView.swift */; };
C4F8B1D8298C0727005C86A5 /* TimerRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B1D7298C0727005C86A5 /* TimerRouter.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -243,6 +246,9 @@
C4F8B1B7298AC81D005C86A5 /* CountdownDialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownDialView.swift; sourceTree = "<group>"; }; C4F8B1B7298AC81D005C86A5 /* CountdownDialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownDialView.swift; sourceTree = "<group>"; };
C4F8B1BC298AC8DE005C86A5 /* AlarmDialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmDialView.swift; sourceTree = "<group>"; }; C4F8B1BC298AC8DE005C86A5 /* AlarmDialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmDialView.swift; sourceTree = "<group>"; };
C4F8B1BE298ACA0B005C86A5 /* StopwatchDialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopwatchDialView.swift; sourceTree = "<group>"; }; C4F8B1BE298ACA0B005C86A5 /* StopwatchDialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopwatchDialView.swift; sourceTree = "<group>"; };
C4F8B1CF298BF2E2005C86A5 /* DialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialView.swift; sourceTree = "<group>"; };
C4F8B1D1298BF646005C86A5 /* PermissionAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionAlertView.swift; sourceTree = "<group>"; };
C4F8B1D7298C0727005C86A5 /* TimerRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerRouter.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -319,6 +325,7 @@
C4060DBF297AE73B003FAB80 /* LeCountdownApp.swift */, C4060DBF297AE73B003FAB80 /* LeCountdownApp.swift */,
C438C7C02980228B00BF3EF9 /* CountdownScheduler.swift */, C438C7C02980228B00BF3EF9 /* CountdownScheduler.swift */,
C4F8B15629891271005C86A5 /* Conductor.swift */, C4F8B15629891271005C86A5 /* Conductor.swift */,
C4F8B1D7298C0727005C86A5 /* TimerRouter.swift */,
C438C80B2981DE2E00BF3EF9 /* Views */, C438C80B2981DE2E00BF3EF9 /* Views */,
C438C8092981DDF800BF3EF9 /* Model */, C438C8092981DDF800BF3EF9 /* Model */,
C445FA8D2987B82E0054D761 /* Sound */, C445FA8D2987B82E0054D761 /* Sound */,
@ -430,7 +437,9 @@
C4F8B1BA298AC83F005C86A5 /* Alarm */, C4F8B1BA298AC83F005C86A5 /* Alarm */,
C4F8B1B9298AC830005C86A5 /* Countdown */, C4F8B1B9298AC830005C86A5 /* Countdown */,
C4F8B1BB298AC848005C86A5 /* Stopwatch */, C4F8B1BB298AC848005C86A5 /* Stopwatch */,
C4F8B1D3298BF686005C86A5 /* Components */,
C4060DC1297AE73B003FAB80 /* ContentView.swift */, C4060DC1297AE73B003FAB80 /* ContentView.swift */,
C4F8B1CF298BF2E2005C86A5 /* DialView.swift */,
C438C80E29828B8600BF3EF9 /* RecordsView.swift */, C438C80E29828B8600BF3EF9 /* RecordsView.swift */,
C4742B5A298414B000D5D950 /* ImageSelectionView.swift */, C4742B5A298414B000D5D950 /* ImageSelectionView.swift */,
C4F8B165298A9ABB005C86A5 /* SoundImageFormView.swift */, C4F8B165298A9ABB005C86A5 /* SoundImageFormView.swift */,
@ -508,6 +517,14 @@
path = Stopwatch; path = Stopwatch;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
C4F8B1D3298BF686005C86A5 /* Components */ = {
isa = PBXGroup;
children = (
C4F8B1D1298BF646005C86A5 /* PermissionAlertView.swift */,
);
path = Components;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -701,12 +718,14 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C4F8B1D2298BF646005C86A5 /* PermissionAlertView.swift in Sources */,
C4060DC9297AE73D003FAB80 /* Persistence.swift in Sources */, C4060DC9297AE73D003FAB80 /* Persistence.swift in Sources */,
C4F8B1AB298AC3A0005C86A5 /* Countdown+CoreDataProperties.swift in Sources */, C4F8B1AB298AC3A0005C86A5 /* Countdown+CoreDataProperties.swift in Sources */,
C438C80F29828B8600BF3EF9 /* RecordsView.swift in Sources */, C438C80F29828B8600BF3EF9 /* RecordsView.swift in Sources */,
C4F8B187298AC234005C86A5 /* Activity+CoreDataProperties.swift in Sources */, C4F8B187298AC234005C86A5 /* Activity+CoreDataProperties.swift in Sources */,
C438C80D2982847300BF3EF9 /* CoreDataRequests.swift in Sources */, C438C80D2982847300BF3EF9 /* CoreDataRequests.swift in Sources */,
C4742B5F2984205000D5D950 /* ViewModifiers.swift in Sources */, C4742B5F2984205000D5D950 /* ViewModifiers.swift in Sources */,
C4F8B1D8298C0727005C86A5 /* TimerRouter.swift in Sources */,
C4F8B186298AC234005C86A5 /* Activity+CoreDataClass.swift in Sources */, C4F8B186298AC234005C86A5 /* Activity+CoreDataClass.swift in Sources */,
C4F8B15729891271005C86A5 /* Conductor.swift in Sources */, C4F8B15729891271005C86A5 /* Conductor.swift in Sources */,
C4F8B17E298AC234005C86A5 /* Alarm+CoreDataClass.swift in Sources */, C4F8B17E298AC234005C86A5 /* Alarm+CoreDataClass.swift in Sources */,
@ -718,6 +737,7 @@
C4F8B1A8298AC2FC005C86A5 /* AbstractSoundTimer+CoreDataProperties.swift in Sources */, C4F8B1A8298AC2FC005C86A5 /* AbstractSoundTimer+CoreDataProperties.swift in Sources */,
C4F8B1B8298AC81D005C86A5 /* CountdownDialView.swift in Sources */, C4F8B1B8298AC81D005C86A5 /* CountdownDialView.swift in Sources */,
C445FA922987CC8A0054D761 /* Sound.swift in Sources */, C445FA922987CC8A0054D761 /* Sound.swift in Sources */,
C4F8B1D0298BF2E2005C86A5 /* DialView.swift in Sources */,
C4742B59298411E800D5D950 /* CountdownFormView.swift in Sources */, C4742B59298411E800D5D950 /* CountdownFormView.swift in Sources */,
C4060DC2297AE73B003FAB80 /* ContentView.swift in Sources */, C4060DC2297AE73B003FAB80 /* ContentView.swift in Sources */,
C438C7C12980228B00BF3EF9 /* CountdownScheduler.swift in Sources */, C438C7C12980228B00BF3EF9 /* CountdownScheduler.swift in Sources */,

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import CoreData
import BackgroundTasks import BackgroundTasks
enum BGTaskIdentifier : String { enum BGTaskIdentifier : String {
@ -22,7 +23,7 @@ struct LeCountdownApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView() ContentView()
.environmentObject(Conductor.maestro) .environmentObject(BoringContext())
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
self._willEnterForegroundNotification() self._willEnterForegroundNotification()

@ -9,6 +9,10 @@ import Foundation
import SwiftUI import SwiftUI
import CoreData import CoreData
enum TimerError: Error {
case notificationAuthorizationMissing
}
extension AbstractTimer { extension AbstractTimer {
var displayName: String { var displayName: String {
@ -23,7 +27,7 @@ extension AbstractTimer {
if let url = URL(string: self.stringId) { if let url = URL(string: self.stringId) {
return url return url
} else { } else {
return URL(fileURLWithPath: self.stringId) // stupid fallthrough fatalError("Can't produce url with \(self.stringId)")
} }
} }
@ -54,15 +58,6 @@ extension AbstractSoundTimer {
extension Countdown { extension Countdown {
// override func view() -> any View {
// return CountdownLiveView(countdown: self)
// .environmentObject(Conductor.maestro)
// }
//
// override func editView() -> any View {
//
// }
static func fake(context: NSManagedObjectContext) -> Countdown { static func fake(context: NSManagedObjectContext) -> Countdown {
let cd = Countdown(context: context) let cd = Countdown(context: context)
cd.duration = 4 * 60.0 cd.duration = 4 * 60.0

@ -0,0 +1,56 @@
//
// TimerRouter.swift
// LeCountdown
//
// Created by Laurent Morvillier on 02/02/2023.
//
import Foundation
import NotificationCenter
class TimerRouter {
static func performAction(timer: AbstractTimer, handler: @escaping (Result<Bool, Error>) -> Void) {
switch timer {
case let countdown as Countdown:
self._launchCountdown(countdown, handler: handler)
case let alarm as Alarm:
self._scheduleAlarm(alarm, handler: handler)
case let stopwatch as Stopwatch:
self._startStopwatch(stopwatch, handler: handler)
default:
print("missing launcher for \(self)")
}
}
fileprivate static func _launchCountdown(_ countdown: Countdown, handler: @escaping (Result<Bool, Error>) -> Void) {
UNUserNotificationCenter.current().getNotificationSettings { settings in
switch settings.authorizationStatus {
case .notDetermined, .denied:
handler(.failure(TimerError.notificationAuthorizationMissing))
default:
CountdownScheduler.master.scheduleIfPossible(countdown: countdown) { result in
switch result {
case .success(_):
handler(.success(true))
case .failure(let failure):
handler(.failure(failure))
}
}
}
}
}
fileprivate static func _scheduleAlarm(_ alarm: Alarm, handler: @escaping (Result<Bool, Error>) -> Void) {
}
fileprivate static func _startStopwatch(_ stopwatch: Stopwatch, handler: @escaping (Result<Bool, Error>) -> Void) {
}
}

@ -0,0 +1,31 @@
//
// PermissionAlertView.swift
// LeCountdown
//
// Created by Laurent Morvillier on 02/02/2023.
//
import SwiftUI
struct PermissionAlertView: View {
var body: some View {
Button("Show permissions") {
self._showPermissionSettings()
}
Button("OK", role: .cancel) { }
}
fileprivate func _showPermissionSettings() {
if let url = URL(string: UIApplication.openNotificationSettingsURLString) {
UIApplication.shared.open(url)
}
}
}
struct PermissionAlertView_Previews: PreviewProvider {
static var previews: some View {
PermissionAlertView()
}
}

@ -8,9 +8,18 @@
import SwiftUI import SwiftUI
import CoreData import CoreData
class BoringContext : ObservableObject {
@Published var isShowingNewData = false
@Published var error: Error?
@Published var showDefaultAlert: Bool = false
@Published var showPermissionAlert: Bool = false
}
struct ContentView: View { struct ContentView: View {
@EnvironmentObject var environment: Conductor @EnvironmentObject var boringContext: BoringContext
@Environment(\.managedObjectContext) private var viewContext @Environment(\.managedObjectContext) private var viewContext
@ -21,14 +30,6 @@ struct ContentView: View {
fileprivate let itemSpacing: CGFloat = 10.0 fileprivate let itemSpacing: CGFloat = 10.0
@State private var isShowingNewData = false
// @State private var isShowingNewStopwatch = false
// @State private var isShowingNewAlarm = false
@State var error: Error?
@State var showDefaultAlert: Bool = false
@State var showPermissionAlert: Bool = false
private let columns: [GridItem] = [ private let columns: [GridItem] = [
GridItem(spacing: 10.0), GridItem(spacing: 10.0),
GridItem(spacing: 10.0), GridItem(spacing: 10.0),
@ -51,29 +52,8 @@ struct ContentView: View {
ReorderableForEach(items: timersArray) { timer in ReorderableForEach(items: timersArray) { timer in
ZStack(alignment: .topTrailing) { DialView(timer: timer, frameSize: width) .environment(\.managedObjectContext, viewContext)
.environmentObject(boringContext)
Image(timer.imageName).resizable()
Button {
self._launchTimer(timer)
} label: {
self._dialView(timer: timer)
}
NavigationLink {
self._editView(timer: timer, isPresented: $isShowingNewData)
} label: {
Image(systemName: "gearshape.fill")
.font(.system(size: 24, weight: .light))
.padding()
.foregroundColor(Color.white)
}
}
.frame(width: width, height: width)
.cornerRadius(40.0)
} moveAction: { from, to in } moveAction: { from, to in
self._reorder(from: from, to: to) self._reorder(from: from, to: to)
@ -82,52 +62,24 @@ struct ContentView: View {
}.padding(itemSpacing) }.padding(itemSpacing)
.navigationTitle("Youpi") .navigationTitle("Youpi")
.alert(error?.localizedDescription ?? "missing error", isPresented: $showDefaultAlert) { .alert(boringContext.error?.localizedDescription ?? "missing error", isPresented: $boringContext.showDefaultAlert) {
Button("OK", role: .cancel) { } Button("OK", role: .cancel) { }
} }
.alert("You need to accept notifications, please check your settings", isPresented: $showPermissionAlert, actions: { .alert("You need to accept notifications, please check your settings", isPresented: $boringContext.showPermissionAlert) {
Button("Show permissions") { PermissionAlertView()
self._showPermissionSettings()
} }
Button("OK", role: .cancel) { } .sheet(isPresented: $boringContext.isShowingNewData, content: {
}) NewCountdownView(isPresented: $boringContext.isShowingNewData) .environment(\.managedObjectContext, viewContext)
.sheet(isPresented: self.$isShowingNewData, content: {
NewCountdownView(isPresented: $isShowingNewData) .environment(\.managedObjectContext, viewContext)
}) })
.sheet(isPresented: self.$isShowingNewData, content: { .sheet(isPresented: $boringContext.isShowingNewData, content: {
NewStopwatchView(isPresented: $isShowingNewData) .environment(\.managedObjectContext, viewContext) NewStopwatchView(isPresented: $boringContext.isShowingNewData) .environment(\.managedObjectContext, viewContext)
}) })
.sheet(isPresented: self.$isShowingNewData, content: { .sheet(isPresented: $boringContext.isShowingNewData, content: {
NewAlarmView(isPresented: $isShowingNewData) .environment(\.managedObjectContext, viewContext) NewAlarmView(isPresented: $boringContext.isShowingNewData) .environment(\.managedObjectContext, viewContext)
}) })
.toolbar { .toolbar {
ToolbarItemGroup(placement: .bottomBar) { ToolbarItemGroup(placement: .bottomBar) {
Button { MainToolbarView(isShowingNewData: $boringContext.isShowingNewData)
self.isShowingNewData = true
} label: {
HStack {
Image(systemName: "timer")
Text("countdown")
}
}
Button {
self.isShowingNewStopwatch = true
} label: {
HStack {
Image(systemName: "stopwatch")
Text("stopwatch")
}
}
Button {
self.isShowingNewAlarm = true
} label: {
HStack {
Image(systemName: "alarm")
Text("alarm")
}
}
} }
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink { NavigationLink {
@ -139,47 +91,18 @@ struct ContentView: View {
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
EditButton() EditButton()
} }
} }
.onAppear { .onAppear {
self._askPermissions() self._askPermissions()
} }
.onOpenURL { url in .onOpenURL { url in
print("open URL = \(url)") print("open URL = \(url)")
self._startCountdownIfPossible(url: url) self._performActionIfPossible(url: url)
} }
} }
} }
@ViewBuilder
fileprivate func _dialView(timer: AbstractTimer) -> some View {
switch timer {
case let countdown as Countdown:
CountdownDialView(countdown: countdown)
case let alarm as Alarm:
AlarmDialView(alarm: alarm)
case let stopwatch as Stopwatch:
StopwatchDialView(stopwatch: stopwatch)
default:
Text("missing dial view")
}
}
@ViewBuilder
fileprivate func _editView(timer: AbstractTimer, isPresented: Binding<Bool>) -> some View {
switch timer {
case let countdown as Countdown:
CountdownEditView(countdown: countdown, isPresented: isPresented)
case let alarm as Alarm:
AlarmEditView(alarm: alarm, isPresented: isPresented)
case let stopwatch as Stopwatch:
StopwatchEditView(stopwatch: stopwatch, isPresented: isPresented)
default:
Text("missing edit view")
}
}
fileprivate func _reorder(from: IndexSet, to: Int) { fileprivate func _reorder(from: IndexSet, to: Int) {
var timers: [AbstractTimer] = self.timersArray var timers: [AbstractTimer] = self.timersArray
timers.move(fromOffsets: from, toOffset: to) timers.move(fromOffsets: from, toOffset: to)
@ -189,63 +112,79 @@ struct ContentView: View {
do { do {
try viewContext.save() try viewContext.save()
} catch { } catch {
self.error = error boringContext.error = error
}
}
fileprivate func _askPermissions() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { success, error in
print("requestAuthorization > success = \(success), error = \(String(describing: error))")
} }
} }
fileprivate func _startCountdownIfPossible(url: URL) { fileprivate func _performActionIfPossible(url: URL) {
let urlString = url.absoluteString let urlString = url.absoluteString
if let countdown = viewContext.object(stringId: urlString) as? Countdown { if let timer = viewContext.object(stringId: urlString) as? AbstractTimer {
print("Start countdown: \(countdown.name ?? ""), \(countdown.duration)") TimerRouter.performAction(timer: timer) { result in
switch result {
case .success:
break
case .failure(let failure):
switch failure {
case TimerError.notificationAuthorizationMissing:
self.boringContext.showPermissionAlert = true
default:
self.boringContext.error = failure
self.boringContext.showDefaultAlert = true
}
}
}
self._launchCountdown(countdown) // print("Start countdown: \(countdown.name ?? ""), \(countdown.duration)")
// self._launchCountdown(countdown)
} else { } else {
print("countdown not found with id = \(urlString)") print("timer not found with id = \(urlString)")
} }
} }
fileprivate func _launchTimer(_ timer: AbstractTimer) {
} }
fileprivate func _launchCountdown(_ countdown: Countdown) { struct MainToolbarView: View {
UNUserNotificationCenter.current().getNotificationSettings { settings in var isShowingNewData: Binding<Bool>
switch settings.authorizationStatus { var body: some View {
case .notDetermined, .denied: Button {
self.showPermissionAlert = true self.isShowingNewData.wrappedValue = true
default: } label: {
CountdownScheduler.master.scheduleIfPossible(countdown: countdown) { result in HStack {
switch result { Image(systemName: "timer")
case .success(_): Text("countdown")
break
case .failure(let failure):
self.error = failure
self.showDefaultAlert = true
}
} }
} }
Button {
self.isShowingNewData.wrappedValue = true
} label: {
HStack {
Image(systemName: "stopwatch")
Text("stopwatch")
} }
} }
Button {
fileprivate func _askPermissions() { self.isShowingNewData.wrappedValue = true
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { success, error in } label: {
print("requestAuthorization > success = \(success), error = \(String(describing: error))") HStack {
Image(systemName: "alarm")
Text("alarm")
} }
} }
fileprivate func _showPermissionSettings() {
if let url = URL(string: UIApplication.openNotificationSettingsURLString) {
UIApplication.shared.open(url)
} }
} }
}
fileprivate extension Countdown { fileprivate extension Countdown {
var endDate: Date? { var endDate: Date? {

@ -0,0 +1,98 @@
//
// DialView.swift
// LeCountdown
//
// Created by Laurent Morvillier on 02/02/2023.
//
import SwiftUI
struct DialView: View {
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var boringContext: BoringContext
@State var timer: AbstractTimer
var frameSize: CGFloat
var body: some View {
ZStack(alignment: .topTrailing) {
Image(timer.imageName).resizable()
Button {
self._launchTimer(timer)
} label: {
self._dialView(timer: timer)
}
NavigationLink {
self._editView(timer: timer, isPresented: $boringContext.isShowingNewData)
} label: {
Image(systemName: "gearshape.fill")
.font(.system(size: 24, weight: .light))
.padding()
.foregroundColor(Color.white)
}
}
.frame(width: frameSize, height: frameSize)
.cornerRadius(40.0)
}
@ViewBuilder
fileprivate func _dialView(timer: AbstractTimer) -> some View {
switch timer {
case let countdown as Countdown:
CountdownDialView(countdown: countdown)
case let alarm as Alarm:
AlarmDialView(alarm: alarm)
case let stopwatch as Stopwatch:
StopwatchDialView(stopwatch: stopwatch)
default:
Text("missing dial view")
}
}
@ViewBuilder
fileprivate func _editView(timer: AbstractTimer, isPresented: Binding<Bool>) -> some View {
switch timer {
case let countdown as Countdown:
CountdownEditView(countdown: countdown, isPresented: isPresented)
case let alarm as Alarm:
AlarmEditView(alarm: alarm, isPresented: isPresented)
case let stopwatch as Stopwatch:
StopwatchEditView(stopwatch: stopwatch, isPresented: isPresented)
default:
Text("missing edit view")
}
}
fileprivate func _launchTimer(_ timer: AbstractTimer) {
TimerRouter.performAction(timer: timer) { result in
switch result {
case .success:
break
case .failure(let failure):
switch failure {
case TimerError.notificationAuthorizationMissing:
self.boringContext.showPermissionAlert = true
default:
self.boringContext.error = failure
self.boringContext.showDefaultAlert = true
}
}
}
}
}
struct DialView_Previews: PreviewProvider {
static var previews: some View {
DialView(timer: Countdown.fake(context: PersistenceController.preview.container.viewContext), frameSize: 150.0)
}
}
Loading…
Cancel
Save