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. 35
      LeCountdown/Views/ContentView.swift
  4. 24
      LeCountdown/Views/Countdown/CountdownDialView.swift
  5. 24
      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,6 +20,7 @@ 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
@ -45,6 +46,10 @@ 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
ZStack(alignment: .bottom) {
ScrollView {
LazyVGrid( LazyVGrid(
columns: columns, columns: columns,
spacing: itemSpacing spacing: itemSpacing
@ -53,14 +58,31 @@ struct ContentView<T : AbstractTimer>: View {
ReorderableForEach(items: timersArray) { timer in ReorderableForEach(items: timersArray) { timer in
DialView(timer: timer, frameSize: width) .environment(\.managedObjectContext, viewContext) DialView(timer: timer, frameSize: width) .environment(\.managedObjectContext, viewContext)
.environmentObject(Conductor.maestro)
.environmentObject(boringContext) .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,9 +160,14 @@ 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 {
if timer is T {
print("performAction")
TimerRouter.performAction(timer: timer) { result in TimerRouter.performAction(timer: timer) { result in
switch result { switch result {
case .success: case .success:
@ -157,6 +183,8 @@ struct ContentView<T : AbstractTimer>: View {
} }
} }
}
// print("Start countdown: \(countdown.name ?? ""), \(countdown.duration)") // print("Start countdown: \(countdown.name ?? ""), \(countdown.duration)")
// self._launchCountdown(countdown) // self._launchCountdown(countdown)
} else { } else {
@ -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] {
if let dateInterval = Conductor.maestro.notificationDates[countdown.stringId] {
Text(dateInterval.end, style: .timer) Text(dateInterval.end, style: .timer)
Spacer()
HStack {
Spacer()
Button { Button {
CountdownScheduler.master.cancelCurrentNotifications(countdown: countdown) CountdownScheduler.master.cancelCurrentNotifications(countdown: countdown)
} label: { } label: {
Text("Cancel") 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,22 +12,25 @@ 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()
} }
HStack {
Spacer()
NavigationLink { NavigationLink {
self._editView(timer: timer, isPresented: $boringContext.isShowingNewData) self._editView(timer: timer, isPresented: $boringContext.isShowingNewData)
} label: { } label: {
@ -36,7 +39,7 @@ struct DialView: View {
.padding() .padding()
.foregroundColor(Color.white) .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