Adds button to stop the repeating sound

release
Laurent 3 years ago
parent 4c20c83745
commit 7db4aecfa4
  1. 5
      LeCountdown/Conductor.swift
  2. 43
      LeCountdown/LeCountdownApp.swift
  3. 77
      LeCountdown/Views/ContentView.swift
  4. 42
      LeCountdown/Views/Countdown/CountdownDialView.swift
  5. 40
      LeCountdown/Views/DialView.swift

@ -13,7 +13,7 @@ class Conductor : ObservableObject {
static let maestro: Conductor = Conductor() static let maestro: Conductor = Conductor()
var soundPlayer: SoundPlayer? = nil @Published var soundPlayer: SoundPlayer? = nil
@UserDefault(Key.dates.rawValue, defaultValue: [:]) static var savedDates: [String : DateInterval] @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?.stop()
self.soundPlayer = nil
} }
// MARK: - Live Activity // MARK: - Live Activity

@ -18,6 +18,8 @@ struct LeCountdownApp: App {
let persistenceController = PersistenceController.shared let persistenceController = PersistenceController.shared
@State private var tabSelection = 1
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
init() { init() {
@ -26,24 +28,38 @@ struct LeCountdownApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
TabView {
TabView(selection: $tabSelection) {
ContentView<Countdown>() ContentView<Countdown>()
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(Conductor.maestro)
.tabItem { Label("Countdown", systemImage: "timer") } .tabItem { Label("Countdown", systemImage: "timer") }
.tag(1)
ContentView<Stopwatch>() ContentView<Stopwatch>()
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(Conductor.maestro)
.tabItem { Label("Stopwatch", systemImage: "stopwatch") } .tabItem { Label("Stopwatch", systemImage: "stopwatch") }
.tag(2)
ContentView<Alarm>() ContentView<Alarm>()
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(Conductor.maestro)
.tabItem { Label("Alarm", systemImage: "alarm") } .tabItem { Label("Alarm", systemImage: "alarm") }
.tag(3)
RecordsView().environment(\.managedObjectContext, persistenceController.container.viewContext) RecordsView().environment(\.managedObjectContext, persistenceController.container.viewContext)
.tabItem { Label("Stats", systemImage: "chart.bar.fill") } .tabItem { Label("Stats", systemImage: "chart.bar.fill") }
.tag(4)
} }
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
self._willEnterForegroundNotification() self._willEnterForegroundNotification()
}.onAppear { }
.onAppear {
self._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)")
}
}
} }

@ -20,7 +20,8 @@ class BoringContext : ObservableObject {
struct ContentView<T : AbstractTimer>: View { struct ContentView<T : AbstractTimer>: View {
@StateObject var boringContext: BoringContext = BoringContext() @StateObject var boringContext: BoringContext = BoringContext()
@EnvironmentObject var conductor: Conductor
@Environment(\.managedObjectContext) private var viewContext @Environment(\.managedObjectContext) private var viewContext
@FetchRequest( @FetchRequest(
@ -45,22 +46,43 @@ struct ContentView<T : AbstractTimer>: View {
GeometryReader { reader in GeometryReader { reader in
let width: CGFloat = reader.size.width / 2 - 10.0 let width: CGFloat = reader.size.width / 2 - 10.0
LazyVGrid( ZStack(alignment: .bottom) {
columns: columns,
spacing: itemSpacing
) {
ReorderableForEach(items: timersArray) { timer in ScrollView {
DialView(timer: timer, frameSize: width) .environment(\.managedObjectContext, viewContext) LazyVGrid(
.environmentObject(boringContext) 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 } moveAction: { from, to in
self._reorder(from: from, to: to) 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))") .navigationTitle("\(String(describing: T.self))")
.alert(boringContext.error?.localizedDescription ?? "missing error", isPresented: $boringContext.showDefaultAlert) { .alert(boringContext.error?.localizedDescription ?? "missing error", isPresented: $boringContext.showDefaultAlert) {
Button("OK", role: .cancel) { } Button("OK", role: .cancel) { }
@ -96,7 +118,6 @@ struct ContentView<T : AbstractTimer>: View {
self._askPermissions() self._askPermissions()
} }
.onOpenURL { url in .onOpenURL { url in
print("open URL = \(url)")
self._performActionIfPossible(url: url) self._performActionIfPossible(url: url)
} }
} }
@ -139,22 +160,29 @@ struct ContentView<T : AbstractTimer>: View {
fileprivate func _performActionIfPossible(url: URL) { fileprivate func _performActionIfPossible(url: URL) {
print("_performActionIfPossible")
let urlString = url.absoluteString let urlString = url.absoluteString
if let timer = viewContext.object(stringId: urlString) as? AbstractTimer { if let timer = viewContext.object(stringId: urlString) as? AbstractTimer {
TimerRouter.performAction(timer: timer) { result in if timer is T {
switch result { print("performAction")
case .success:
break TimerRouter.performAction(timer: timer) { result in
case .failure(let failure): switch result {
switch failure { case .success:
case TimerError.notificationAuthorizationMissing: break
self.boringContext.showPermissionAlert = true case .failure(let failure):
default: switch failure {
self.boringContext.error = failure case TimerError.notificationAuthorizationMissing:
self.boringContext.showDefaultAlert = true self.boringContext.showPermissionAlert = true
default:
self.boringContext.error = failure
self.boringContext.showDefaultAlert = true
}
} }
} }
} }
// print("Start countdown: \(countdown.name ?? ""), \(countdown.duration)") // print("Start countdown: \(countdown.name ?? ""), \(countdown.duration)")
@ -222,6 +250,7 @@ fileprivate extension Countdown {
struct ContentView_Previews: PreviewProvider { struct ContentView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ContentView<Countdown>().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) ContentView<Countdown>().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) .environmentObject(Conductor.maestro)
} }
} }

@ -9,35 +9,37 @@ import SwiftUI
struct CountdownDialView: View { struct CountdownDialView: View {
@EnvironmentObject var conductor: Conductor
@ObservedObject var countdown: Countdown @ObservedObject var countdown: Countdown
var body: some View { var body: some View {
VStack {
HStack {
HStack { VStack(alignment: .leading) {
Text(countdown.activity?.name?.uppercased() ?? "") Text(countdown.activity?.name?.uppercased() ?? "")
Spacer() // let dateInterval = DateInterval(start: Date(), end: Date())
} if let dateInterval = conductor.notificationDates[countdown.stringId] {
Text(dateInterval.end, style: .timer)
if let dateInterval = Conductor.maestro.notificationDates[countdown.stringId] { Spacer()
Text(dateInterval.end, style: .timer) HStack {
Button { Spacer()
CountdownScheduler.master.cancelCurrentNotifications(countdown: countdown) Button {
} label: { CountdownScheduler.master.cancelCurrentNotifications(countdown: countdown)
Text("Cancel") } label: {
Text("Cancel".uppercased())
}
.buttonStyle(.bordered) .buttonStyle(.bordered)
.tint(.red) Spacer()
} }
} else { } else {
HStack {
Text(countdown.duration.minuteSecond) Text(countdown.duration.minuteSecond)
Spacer()
} }
Spacer()
} }
Spacer() Spacer()
} }
.padding(24.0)
.monospaced() .monospaced()
.font(Font.system(size: 16.0, weight: .semibold)) .font(Font.system(size: 16.0, weight: .semibold))
.foregroundColor(Color.white) .foregroundColor(Color.white)
@ -47,6 +49,6 @@ struct CountdownDialView: View {
struct CountdownLiveView_Previews: PreviewProvider { struct CountdownLiveView_Previews: PreviewProvider {
static var previews: some View { 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)
} }
} }

@ -12,31 +12,34 @@ struct DialView: View {
@Environment(\.managedObjectContext) private var viewContext @Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var boringContext: BoringContext @EnvironmentObject var boringContext: BoringContext
@EnvironmentObject var conductor: Conductor
@State var timer: AbstractTimer @State var timer: AbstractTimer
var frameSize: CGFloat var frameSize: CGFloat
var body: some View { var body: some View {
ZStack(alignment: .topTrailing) { ZStack(alignment: .topLeading) {
Image(timer.imageName).resizable() Image(timer.imageName).resizable()
Button { Button {
self._launchTimer(timer) self._launchTimer(timer)
} label: { } label: {
self._dialView(timer: timer) self._dialView(timer: timer).padding()
} }
NavigationLink { HStack {
self._editView(timer: timer, isPresented: $boringContext.isShowingNewData) Spacer()
} label: { NavigationLink {
Image(systemName: "gearshape.fill") self._editView(timer: timer, isPresented: $boringContext.isShowingNewData)
.font(.system(size: 24, weight: .light)) } label: {
.padding() Image(systemName: "gearshape.fill")
.foregroundColor(Color.white) .font(.system(size: 24, weight: .light))
.padding()
.foregroundColor(Color.white)
}
} }
} }
.frame(width: frameSize, height: frameSize) .frame(width: frameSize, height: frameSize)
.cornerRadius(40.0) .cornerRadius(40.0)
@ -46,11 +49,11 @@ struct DialView: View {
fileprivate func _dialView(timer: AbstractTimer) -> some View { fileprivate func _dialView(timer: AbstractTimer) -> some View {
switch timer { switch timer {
case let countdown as Countdown: case let countdown as Countdown:
CountdownDialView(countdown: countdown) CountdownDialView(countdown: countdown) .environmentObject(Conductor.maestro)
case let alarm as Alarm: case let alarm as Alarm:
AlarmDialView(alarm: alarm) AlarmDialView(alarm: alarm) .environmentObject(Conductor.maestro)
case let stopwatch as Stopwatch: case let stopwatch as Stopwatch:
StopwatchDialView(stopwatch: stopwatch) StopwatchDialView(stopwatch: stopwatch) .environmentObject(Conductor.maestro)
default: default:
Text("missing dial view") Text("missing dial view")
} }
@ -93,6 +96,13 @@ struct DialView: View {
struct DialView_Previews: PreviewProvider { struct DialView_Previews: PreviewProvider {
static var previews: some View { 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())
} }
} }

Loading…
Cancel
Save