From 7db4aecfa4dec4bf18ff8a5477b2ab296d2b0089 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 3 Feb 2023 11:41:35 +0100 Subject: [PATCH] Adds button to stop the repeating sound --- LeCountdown/Conductor.swift | 5 +- LeCountdown/LeCountdownApp.swift | 43 ++++++++++- LeCountdown/Views/ContentView.swift | 77 +++++++++++++------ .../Views/Countdown/CountdownDialView.swift | 42 +++++----- LeCountdown/Views/DialView.swift | 40 ++++++---- 5 files changed, 144 insertions(+), 63 deletions(-) diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index 8b8e521..7fe589b 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -13,7 +13,7 @@ class Conductor : ObservableObject { static let maestro: Conductor = Conductor() - var soundPlayer: SoundPlayer? = nil + @Published var soundPlayer: SoundPlayer? = nil @UserDefault(Key.dates.rawValue, defaultValue: [:]) static var savedDates: [String : DateInterval] @@ -99,8 +99,9 @@ class Conductor : ObservableObject { } - func stopSoundIfNecessary() { + func stopSoundIfPossible() { self.soundPlayer?.stop() + self.soundPlayer = nil } // MARK: - Live Activity diff --git a/LeCountdown/LeCountdownApp.swift b/LeCountdown/LeCountdownApp.swift index 77d9380..3235fee 100644 --- a/LeCountdown/LeCountdownApp.swift +++ b/LeCountdown/LeCountdownApp.swift @@ -18,6 +18,8 @@ struct LeCountdownApp: App { let persistenceController = PersistenceController.shared + @State private var tabSelection = 1 + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate init() { @@ -26,24 +28,38 @@ struct LeCountdownApp: App { var body: some Scene { WindowGroup { - TabView { + + TabView(selection: $tabSelection) { ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) + .environmentObject(Conductor.maestro) .tabItem { Label("Countdown", systemImage: "timer") } + .tag(1) ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) + .environmentObject(Conductor.maestro) .tabItem { Label("Stopwatch", systemImage: "stopwatch") } + .tag(2) ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) + .environmentObject(Conductor.maestro) .tabItem { Label("Alarm", systemImage: "alarm") } + .tag(3) RecordsView().environment(\.managedObjectContext, persistenceController.container.viewContext) .tabItem { Label("Stats", systemImage: "chart.bar.fill") } + .tag(4) + } .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in self._willEnterForegroundNotification() - }.onAppear { + } + .onAppear { self._onAppear() } + .onOpenURL { url in + print("open URL = \(url)") + self._performActionIfPossible(url: url) + } } } @@ -87,4 +103,27 @@ struct LeCountdownApp: App { } + fileprivate func _performActionIfPossible(url: URL) { + + let context = persistenceController.container.viewContext + let urlString = url.absoluteString + if let timer = context.object(stringId: urlString) as? AbstractTimer { + + switch timer { + case is Countdown: + tabSelection = 1 + case is Stopwatch: + tabSelection = 2 + case is Alarm: + tabSelection = 3 + default: + print("url not managed, object is \(timer)") + break + } + } else { + print("url not managed: \(url)") + } + + } + } diff --git a/LeCountdown/Views/ContentView.swift b/LeCountdown/Views/ContentView.swift index c1be15e..56152a4 100644 --- a/LeCountdown/Views/ContentView.swift +++ b/LeCountdown/Views/ContentView.swift @@ -20,7 +20,8 @@ class BoringContext : ObservableObject { struct ContentView: View { @StateObject var boringContext: BoringContext = BoringContext() - + @EnvironmentObject var conductor: Conductor + @Environment(\.managedObjectContext) private var viewContext @FetchRequest( @@ -45,22 +46,43 @@ struct ContentView: View { GeometryReader { reader in let width: CGFloat = reader.size.width / 2 - 10.0 - LazyVGrid( - columns: columns, - spacing: itemSpacing - ) { + ZStack(alignment: .bottom) { - ReorderableForEach(items: timersArray) { timer in + ScrollView { - DialView(timer: timer, frameSize: width) .environment(\.managedObjectContext, viewContext) - .environmentObject(boringContext) + LazyVGrid( + columns: columns, + spacing: itemSpacing + ) { + + ReorderableForEach(items: timersArray) { timer in + + DialView(timer: timer, frameSize: width) .environment(\.managedObjectContext, viewContext) + .environmentObject(Conductor.maestro) + .environmentObject(boringContext) - } moveAction: { from, to in - self._reorder(from: from, to: to) + } moveAction: { from, to in + self._reorder(from: from, to: to) + } + } } + + if Conductor.maestro.soundPlayer != nil { + Button { + Conductor.maestro.stopSoundIfPossible() + } label: { + Text("STOP") + .frame(minWidth: 0.0, maxWidth: .infinity, minHeight: 75.0, maxHeight: 75.0) + .foregroundColor(.white) + .background(.red) + .cornerRadius(16.0) + }.padding() + } + } - }.padding(itemSpacing) + + }.padding(.horizontal, itemSpacing) .navigationTitle("\(String(describing: T.self))") .alert(boringContext.error?.localizedDescription ?? "missing error", isPresented: $boringContext.showDefaultAlert) { Button("OK", role: .cancel) { } @@ -96,7 +118,6 @@ struct ContentView: View { self._askPermissions() } .onOpenURL { url in - print("open URL = \(url)") self._performActionIfPossible(url: url) } } @@ -139,22 +160,29 @@ struct ContentView: View { fileprivate func _performActionIfPossible(url: URL) { + print("_performActionIfPossible") + let urlString = url.absoluteString if let timer = viewContext.object(stringId: urlString) as? 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 + if timer is T { + print("performAction") + + 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 + } } } + } // print("Start countdown: \(countdown.name ?? ""), \(countdown.duration)") @@ -222,6 +250,7 @@ fileprivate extension Countdown { struct ContentView_Previews: PreviewProvider { static var previews: some View { - ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) .environmentObject(Conductor.maestro) + } } diff --git a/LeCountdown/Views/Countdown/CountdownDialView.swift b/LeCountdown/Views/Countdown/CountdownDialView.swift index 44a69bb..bd44c7f 100644 --- a/LeCountdown/Views/Countdown/CountdownDialView.swift +++ b/LeCountdown/Views/Countdown/CountdownDialView.swift @@ -9,35 +9,37 @@ import SwiftUI struct CountdownDialView: View { + @EnvironmentObject var conductor: Conductor + @ObservedObject var countdown: Countdown var body: some View { - VStack { - - HStack { + + HStack { + VStack(alignment: .leading) { Text(countdown.activity?.name?.uppercased() ?? "") - Spacer() - } - - if let dateInterval = Conductor.maestro.notificationDates[countdown.stringId] { - Text(dateInterval.end, style: .timer) - Button { - CountdownScheduler.master.cancelCurrentNotifications(countdown: countdown) - } label: { - Text("Cancel") + // let dateInterval = DateInterval(start: Date(), end: Date()) + if let dateInterval = conductor.notificationDates[countdown.stringId] { + Text(dateInterval.end, style: .timer) + Spacer() + HStack { + Spacer() + Button { + CountdownScheduler.master.cancelCurrentNotifications(countdown: countdown) + } label: { + Text("Cancel".uppercased()) + } .buttonStyle(.bordered) - .tint(.red) - } - - } else { - HStack { + Spacer() + } + + } else { Text(countdown.duration.minuteSecond) - Spacer() } + Spacer() } Spacer() } - .padding(24.0) .monospaced() .font(Font.system(size: 16.0, weight: .semibold)) .foregroundColor(Color.white) @@ -47,6 +49,6 @@ struct CountdownDialView: View { struct CountdownLiveView_Previews: PreviewProvider { static var previews: some View { - CountdownDialView(countdown: Countdown.fake(context: PersistenceController.preview.container.viewContext)) + CountdownDialView(countdown: Countdown.fake(context: PersistenceController.preview.container.viewContext)).background(.cyan).environmentObject(Conductor.maestro) } } diff --git a/LeCountdown/Views/DialView.swift b/LeCountdown/Views/DialView.swift index 009384d..ff2406e 100644 --- a/LeCountdown/Views/DialView.swift +++ b/LeCountdown/Views/DialView.swift @@ -12,31 +12,34 @@ struct DialView: View { @Environment(\.managedObjectContext) private var viewContext @EnvironmentObject var boringContext: BoringContext - + @EnvironmentObject var conductor: Conductor + @State var timer: AbstractTimer var frameSize: CGFloat var body: some View { - ZStack(alignment: .topTrailing) { + ZStack(alignment: .topLeading) { Image(timer.imageName).resizable() Button { self._launchTimer(timer) } label: { - self._dialView(timer: timer) + self._dialView(timer: timer).padding() } - NavigationLink { - self._editView(timer: timer, isPresented: $boringContext.isShowingNewData) - } label: { - Image(systemName: "gearshape.fill") - .font(.system(size: 24, weight: .light)) - .padding() - .foregroundColor(Color.white) + HStack { + Spacer() + 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) @@ -46,11 +49,11 @@ struct DialView: View { fileprivate func _dialView(timer: AbstractTimer) -> some View { switch timer { case let countdown as Countdown: - CountdownDialView(countdown: countdown) + CountdownDialView(countdown: countdown) .environmentObject(Conductor.maestro) case let alarm as Alarm: - AlarmDialView(alarm: alarm) + AlarmDialView(alarm: alarm) .environmentObject(Conductor.maestro) case let stopwatch as Stopwatch: - StopwatchDialView(stopwatch: stopwatch) + StopwatchDialView(stopwatch: stopwatch) .environmentObject(Conductor.maestro) default: Text("missing dial view") } @@ -93,6 +96,13 @@ struct DialView: View { struct DialView_Previews: PreviewProvider { static var previews: some View { - DialView(timer: Countdown.fake(context: PersistenceController.preview.container.viewContext), frameSize: 150.0) + + DialView( + timer: Countdown.fake(context: PersistenceController.preview.container.viewContext), + frameSize: 150.0) + .environmentObject(Conductor.maestro) + .environmentObject(BoringContext()) + + } }