Improvement and fixes

release
Laurent 3 years ago
parent a71c534f27
commit b9fde1b0c2
  1. 29
      LaunchWidget/LaunchWidgetLiveActivity.swift
  2. 6
      LeCountdown.xcodeproj/project.pbxproj
  3. 2
      LeCountdown/AppDelegate.swift
  4. 100
      LeCountdown/Conductor.swift
  5. 2
      LeCountdown/LeCountdownApp.swift
  6. 8
      LeCountdown/Model/Model+Extensions.swift
  7. 2
      LeCountdown/Sound/Sound.swift
  8. 16
      LeCountdown/Sound/SoundPlayer.swift
  9. 17
      LeCountdown/TimerRouter.swift
  10. 23
      LeCountdown/Views/Components/GreenCheckmarkView.swift
  11. 6
      LeCountdown/Views/ContentView.swift
  12. 12
      LeCountdown/Views/LiveTimerListView.swift

@ -18,7 +18,7 @@ struct LaunchWidgetAttributes: ActivityAttributes {
// Fixed non-changing properties about your activity go here! // Fixed non-changing properties about your activity go here!
var id: String var id: String
var name: String var name: String
var endDate: Date var date: Date
} }
@ -45,13 +45,20 @@ struct LaunchWidgetLiveActivity: Widget {
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
ActivityConfiguration(for: LaunchWidgetAttributes.self) { context in ActivityConfiguration(for: LaunchWidgetAttributes.self) { context in
let range = Date()...context.attributes.endDate // let range = Date()...context.attributes.date
// Lock screen/banner UI goes here // Lock screen/banner UI goes here
HStack { HStack {
Text(context.attributes.name) Text(context.attributes.name.uppercased())
Spacer() Spacer()
Text(timerInterval: range, pauseTime: range.lowerBound) Text(context.attributes.date, style: .timer)
.font(.title)
// if Date() < context.attributes.date {
// Text(context.attributes.date, style: .timer)
// } else {
// GreenCheckmarkView()
// }
// if context.attributes.endDate > self.model.now { // if context.attributes.endDate > self.model.now {
// Text(context.attributes.endDate, style: .timer) // Text(context.attributes.endDate, style: .timer)
@ -60,19 +67,21 @@ struct LaunchWidgetLiveActivity: Widget {
// Text("It's time!") // Text("It's time!")
// } // }
}.padding() }.padding()
.font(.title) .monospaced()
.activityBackgroundTint(Color.cyan) .foregroundColor(.white)
.activitySystemActionForegroundColor(Color.black) .activityBackgroundTint(Color(white: 0.2))
.activitySystemActionForegroundColor(.white)
} dynamicIsland: { context in } dynamicIsland: { context in
DynamicIsland { DynamicIsland {
// Expanded UI goes here. Compose the expanded UI through // Expanded UI goes here. Compose the expanded UI through
// various regions, like leading/trailing/center/bottom // various regions, like leading/trailing/center/bottom
DynamicIslandExpandedRegion(.leading) { DynamicIslandExpandedRegion(.leading) {
Text(context.attributes.name) Text(context.attributes.name.uppercased())
.monospaced()
} }
DynamicIslandExpandedRegion(.trailing) { DynamicIslandExpandedRegion(.trailing) {
Text(context.attributes.endDate, style: .timer) Text(context.attributes.date, style: .timer)
.monospaced() .monospaced()
} }
DynamicIslandExpandedRegion(.bottom) { DynamicIslandExpandedRegion(.bottom) {
@ -104,7 +113,7 @@ struct LaunchWidgetLiveActivity_Previews: PreviewProvider {
static let attributes = LaunchWidgetAttributes( static let attributes = LaunchWidgetAttributes(
id: "", id: "",
name: "Tea", name: "Tea",
endDate: Date().addingTimeInterval(3600.0)) date: Date().addingTimeInterval(3600.0))
static let contentState = LaunchWidgetAttributes.ContentState(ended: false) static let contentState = LaunchWidgetAttributes.ContentState(ended: false)

@ -65,6 +65,8 @@
C498E59F298D4DEA00E90DE0 /* LiveTimerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */; }; C498E59F298D4DEA00E90DE0 /* LiveTimerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */; };
C498E5A1298D543900E90DE0 /* LiveTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A0298D543900E90DE0 /* LiveTimer.swift */; }; C498E5A1298D543900E90DE0 /* LiveTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A0298D543900E90DE0 /* LiveTimer.swift */; };
C498E5A3298D720600E90DE0 /* TestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A2298D720600E90DE0 /* TestView.swift */; }; C498E5A3298D720600E90DE0 /* TestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A2298D720600E90DE0 /* TestView.swift */; };
C498E5A5299152B400E90DE0 /* GreenCheckmarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A4299152B400E90DE0 /* GreenCheckmarkView.swift */; };
C498E5A6299152C600E90DE0 /* GreenCheckmarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A4299152B400E90DE0 /* GreenCheckmarkView.swift */; };
C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */; }; C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */; };
C4F8B15729891271005C86A5 /* Conductor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B15629891271005C86A5 /* Conductor.swift */; }; C4F8B15729891271005C86A5 /* Conductor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B15629891271005C86A5 /* Conductor.swift */; };
C4F8B15929891528005C86A5 /* forest_stream.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C4F8B15829891528005C86A5 /* forest_stream.mp3 */; }; C4F8B15929891528005C86A5 /* forest_stream.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C4F8B15829891528005C86A5 /* forest_stream.mp3 */; };
@ -226,6 +228,7 @@
C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimerListView.swift; sourceTree = "<group>"; }; C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimerListView.swift; sourceTree = "<group>"; };
C498E5A0298D543900E90DE0 /* LiveTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimer.swift; sourceTree = "<group>"; }; C498E5A0298D543900E90DE0 /* LiveTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimer.swift; sourceTree = "<group>"; };
C498E5A2298D720600E90DE0 /* TestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = "<group>"; }; C498E5A2298D720600E90DE0 /* TestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = "<group>"; };
C498E5A4299152B400E90DE0 /* GreenCheckmarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GreenCheckmarkView.swift; sourceTree = "<group>"; };
C4F8B15629891271005C86A5 /* Conductor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conductor.swift; sourceTree = "<group>"; }; C4F8B15629891271005C86A5 /* Conductor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conductor.swift; sourceTree = "<group>"; };
C4F8B15829891528005C86A5 /* forest_stream.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = forest_stream.mp3; sourceTree = "<group>"; }; C4F8B15829891528005C86A5 /* forest_stream.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = forest_stream.mp3; sourceTree = "<group>"; };
C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = "<group>"; }; C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = "<group>"; };
@ -530,6 +533,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C4F8B1D1298BF646005C86A5 /* PermissionAlertView.swift */, C4F8B1D1298BF646005C86A5 /* PermissionAlertView.swift */,
C498E5A4299152B400E90DE0 /* GreenCheckmarkView.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@ -745,6 +749,7 @@
C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */, C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */,
C4F8B183298AC234005C86A5 /* Stopwatch+CoreDataProperties.swift in Sources */, C4F8B183298AC234005C86A5 /* Stopwatch+CoreDataProperties.swift in Sources */,
C4F8B1A8298AC2FC005C86A5 /* AbstractSoundTimer+CoreDataProperties.swift in Sources */, C4F8B1A8298AC2FC005C86A5 /* AbstractSoundTimer+CoreDataProperties.swift in Sources */,
C498E5A5299152B400E90DE0 /* GreenCheckmarkView.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 */, C4F8B1D0298BF2E2005C86A5 /* DialView.swift in Sources */,
@ -805,6 +810,7 @@
files = ( files = (
C4F8B1AF298AC451005C86A5 /* Countdown+CoreDataProperties.swift in Sources */, C4F8B1AF298AC451005C86A5 /* Countdown+CoreDataProperties.swift in Sources */,
C445FA932987CF280054D761 /* Sound.swift in Sources */, C445FA932987CF280054D761 /* Sound.swift in Sources */,
C498E5A6299152C600E90DE0 /* GreenCheckmarkView.swift in Sources */,
C438C7EB2981266F00BF3EF9 /* SingleCountdownView.swift in Sources */, C438C7EB2981266F00BF3EF9 /* SingleCountdownView.swift in Sources */,
C438C7D62981216200BF3EF9 /* LaunchWidgetBundle.swift in Sources */, C438C7D62981216200BF3EF9 /* LaunchWidgetBundle.swift in Sources */,
C4F8B18B298AC288005C86A5 /* Record+CoreDataClass.swift in Sources */, C4F8B18B298AC288005C86A5 /* Record+CoreDataClass.swift in Sources */,

@ -13,7 +13,7 @@ class AppDelegate : NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self UNUserNotificationCenter.current().delegate = self
Conductor.maestro.cleanup() Conductor.maestro.cleanupCountdowns()
return true return true
} }

@ -52,6 +52,7 @@ class Conductor: ObservableObject {
fileprivate var _cleanupTimers: [String : Timer] = [:] fileprivate var _cleanupTimers: [String : Timer] = [:]
func removeLiveTimer(id: String) { func removeLiveTimer(id: String) {
self.stopSoundIfPossible()
self.liveTimers.removeAll(where: { $0.id == id }) self.liveTimers.removeAll(where: { $0.id == id })
} }
@ -90,21 +91,36 @@ class Conductor: ObservableObject {
} }
func notifyUser(countdownId: String) {
self._playSound(timerId: countdownId)
self._endCountdown(countdownId: countdownId, cancel: false)
}
fileprivate func _recordActivity(countdownId: String) {
let context = PersistenceController.shared.container.viewContext
if let countdown = context.object(stringId: countdownId) as? Countdown,
let dateInterval = self.currentCountdowns[countdownId] {
do {
try CoreDataRequests.recordActivity(timer: countdown, dateInterval: dateInterval)
} catch {
print("Could not record activity = \(error)")
// TODO: show error to user
}
}
}
// MARK: - Countdown
func startCountdown(_ date: Date, countdown: Countdown) { func startCountdown(_ date: Date, countdown: Countdown) {
DispatchQueue.main.async { DispatchQueue.main.async {
let dateInterval = DateInterval(start: Date(), end: date) let dateInterval = DateInterval(start: Date(), end: date)
self.currentCountdowns[countdown.stringId] = dateInterval self.currentCountdowns[countdown.stringId] = dateInterval
self._launchLiveActivity(countdown: countdown, endDate: date) // self._launchLiveActivity(countdown: countdown, endDate: date)
self._cleanupTimers.removeValue(forKey: countdown.stringId) self._cleanupTimers.removeValue(forKey: countdown.stringId)
} }
} }
func notifyUser(countdownId: String) {
self._playSound(countdownId: countdownId)
self._endCountdown(countdownId: countdownId, cancel: false)
}
func cancelCountdown(id: String) { func cancelCountdown(id: String) {
CountdownScheduler.master.cancelCurrentNotifications(countdownId: id) CountdownScheduler.master.cancelCurrentNotifications(countdownId: id)
self._endCountdown(countdownId: id, cancel: true) self._endCountdown(countdownId: id, cancel: true)
@ -115,16 +131,17 @@ class Conductor: ObservableObject {
if !cancel { if !cancel {
self._recordActivity(countdownId: countdownId) self._recordActivity(countdownId: countdownId)
} }
self.currentCountdowns.removeValue(forKey: countdownId)
if self.currentCountdowns.removeValue(forKey: countdownId) != nil { // if self.currentCountdowns.removeValue(forKey: countdownId) != nil {
self._endLiveActivity(countdownId: countdownId) // self._endLiveActivity(countdownId: countdownId)
} // }
self.removeLiveTimer(id: countdownId) self.removeLiveTimer(id: countdownId)
} }
} }
func cleanup() { func cleanupCountdowns() {
let now = Date() let now = Date()
for (key, value) in self.currentCountdowns { for (key, value) in self.currentCountdowns {
if value.end < now { if value.end < now {
@ -133,37 +150,55 @@ class Conductor: ObservableObject {
} }
} }
fileprivate func _recordActivity(countdownId: String) { // MARK: - Stopwatch
let context = PersistenceController.shared.container.viewContext
if let countdown = context.object(stringId: countdownId) as? Countdown, func startStopwatch(_ stopwatch: Stopwatch) {
let dateInterval = self.currentCountdowns[countdownId] { DispatchQueue.main.async {
let now = Date()
Conductor.maestro.currentStopwatches[stopwatch.stringId] = now
self._launchLiveActivity(stopwatch: stopwatch, start: now)
}
}
func stopStopwatch(_ stopwatch: Stopwatch, end: Date? = nil) {
if let start = Conductor.maestro.currentStopwatches[stopwatch.stringId] {
Conductor.maestro.currentStopwatches.removeValue(forKey: stopwatch.stringId)
do { do {
try CoreDataRequests.recordActivity(timer: countdown, dateInterval: dateInterval) try CoreDataRequests.recordActivity(timer: stopwatch, dateInterval: DateInterval(start: start, end: end ?? Date()))
} catch { } catch {
print("Could not record activity = \(error)") print("could not record")
// TODO: show error to user
} }
self._endLiveActivity(timerId: stopwatch.stringId)
} }
} }
// MARK: - Sound // MARK: - Sound
fileprivate func _playSound(countdownId: String) { fileprivate func _playSound(timerId: String) {
let countdown = PersistenceController.shared.container.viewContext.object(stringId: countdownId) as? Countdown let context = PersistenceController.shared.container.viewContext
let coolSound = countdown?.coolSound ?? Sound.allCases[0] var coolSound: Sound? = nil
switch context.object(stringId: timerId) {
case let cd as Countdown:
coolSound = cd.coolSound
case let sw as Stopwatch:
coolSound = sw.coolSound
default:
break
}
if let coolSound {
do { do {
let soundFile = try coolSound.soundFile() let soundFile = try coolSound.soundFile()
let soundPlayer = SoundPlayer() let soundPlayer = SoundPlayer()
self.soundPlayer = soundPlayer self.soundPlayer = soundPlayer
try soundPlayer.playSound(soundFile: soundFile, repeats: countdown?.repeats ?? true) try soundPlayer.playSound(soundFile: soundFile, repeats: false)
} catch { } catch {
print("error = \(error)") print("error = \(error)")
// TODO: manage error // TODO: manage error
} }
}
} }
func stopSoundIfPossible() { func stopSoundIfPossible() {
@ -173,13 +208,13 @@ class Conductor: ObservableObject {
// MARK: - Live Activity // MARK: - Live Activity
fileprivate func _launchLiveActivity(countdown: Countdown, endDate: Date) { fileprivate func _launchLiveActivity(stopwatch: Stopwatch, start: Date) {
if ActivityAuthorizationInfo().areActivitiesEnabled { if ActivityAuthorizationInfo().areActivitiesEnabled {
let contentState = LaunchWidgetAttributes.ContentState(ended: false) let contentState = LaunchWidgetAttributes.ContentState(ended: false)
let attributes = LaunchWidgetAttributes(id: countdown.stringId, name: countdown.displayName, endDate: endDate) let attributes = LaunchWidgetAttributes(id: stopwatch.stringId, name: stopwatch.displayName, date: start)
let activityContent = ActivityContent(state: contentState, staleDate: endDate.addingTimeInterval(30.0)) let activityContent = ActivityContent(state: contentState, staleDate: nil)
do { do {
let liveActivity = try ActivityKit.Activity.request(attributes: attributes, content: activityContent) let liveActivity = try ActivityKit.Activity.request(attributes: attributes, content: activityContent)
@ -188,7 +223,7 @@ class Conductor: ObservableObject {
print("Error requesting countdown Live Activity \(error.localizedDescription).") print("Error requesting countdown Live Activity \(error.localizedDescription).")
} }
self._scheduleAppRefresh(countdown: countdown) // self._scheduleAppRefresh(countdown: countdown)
} }
} }
@ -196,7 +231,6 @@ class Conductor: ObservableObject {
fileprivate func _scheduleAppRefresh(countdown: Countdown) { fileprivate func _scheduleAppRefresh(countdown: Countdown) {
let request = BGAppRefreshTaskRequest(identifier: BGTaskIdentifier.refresh.rawValue) let request = BGAppRefreshTaskRequest(identifier: BGTaskIdentifier.refresh.rawValue)
request.earliestBeginDate = Date(timeIntervalSinceNow: countdown.duration) request.earliestBeginDate = Date(timeIntervalSinceNow: countdown.duration)
do { do {
try BGTaskScheduler.shared.submit(request) try BGTaskScheduler.shared.submit(request)
print("request submitted with date: \(String(describing: request.earliestBeginDate))") print("request submitted with date: \(String(describing: request.earliestBeginDate))")
@ -205,8 +239,8 @@ class Conductor: ObservableObject {
} }
} }
fileprivate func _liveActivity(countdownId: String) -> ActivityKit.Activity<LaunchWidgetAttributes>? { fileprivate func _liveActivity(timerId: String) -> ActivityKit.Activity<LaunchWidgetAttributes>? {
return ActivityKit.Activity<LaunchWidgetAttributes>.activities.first(where: { $0.attributes.id == countdownId } ) return ActivityKit.Activity<LaunchWidgetAttributes>.activities.first(where: { $0.attributes.id == timerId } )
} }
func updateLiveActivities() { func updateLiveActivities() {
@ -215,7 +249,7 @@ class Conductor: ObservableObject {
for (countdownId, interval) in self.currentCountdowns { for (countdownId, interval) in self.currentCountdowns {
if interval.end < Date() { if interval.end < Date() {
self._endLiveActivity(countdownId: countdownId) self._endLiveActivity(timerId: countdownId)
} }
@ -237,11 +271,11 @@ class Conductor: ObservableObject {
} }
fileprivate func _endLiveActivity(countdownId: String) { fileprivate func _endLiveActivity(timerId: String) {
print("Try to end the Live Activity: \(countdownId)") print("Try to end the Live Activity: \(timerId)")
if let activity = self._liveActivity(countdownId: countdownId) { if let activity = self._liveActivity(timerId: timerId) {
Task { Task {
let state = LaunchWidgetAttributes.ContentState(ended: true) let state = LaunchWidgetAttributes.ContentState(ended: true)
let content = ActivityContent(state: state, staleDate: Date()) let content = ActivityContent(state: state, staleDate: Date())

@ -64,7 +64,7 @@ struct LeCountdownApp: App {
} }
fileprivate func _willEnterForegroundNotification() { fileprivate func _willEnterForegroundNotification() {
Conductor.maestro.cleanup() Conductor.maestro.cleanupCountdowns()
} }
fileprivate func _onAppear() { fileprivate func _onAppear() {

@ -47,11 +47,11 @@ extension AbstractTimer {
extension AbstractSoundTimer { extension AbstractSoundTimer {
var coolSound: Sound { var coolSound: Sound {
return Sound.allCases[Int(self.sound)] return Sound(rawValue: Int(self.sound)) ?? Sound.allCases[0]
} }
var soundName: String { var soundName: String {
coolSound.soundName return self.coolSound.soundName
} }
} }
@ -84,6 +84,10 @@ extension Alarm {
extension Stopwatch { extension Stopwatch {
var coolSound: Sound? {
return Sound(rawValue: Int(self.sound)) ?? nil
}
static func fake(context: NSManagedObjectContext) -> Stopwatch { static func fake(context: NSManagedObjectContext) -> Stopwatch {
let stopwatch = Stopwatch(context: context) let stopwatch = Stopwatch(context: context)
let activity = Activity(context: context) let activity = Activity(context: context)

@ -13,7 +13,7 @@ enum Sound : Int, CaseIterable, Identifiable {
var id: Int { return self.rawValue } var id: Int { return self.rawValue }
case trainhorn // default case trainhorn = 1 // default
case forestStream case forestStream
var localizedString: String { var localizedString: String {

@ -33,7 +33,7 @@ enum SoundPlayerError : Error {
case badFileName(name: String) case badFileName(name: String)
} }
class SoundPlayer { @objc class SoundPlayer: NSObject, AVAudioPlayerDelegate {
fileprivate var _player: AVAudioPlayer? fileprivate var _player: AVAudioPlayer?
@ -42,15 +42,16 @@ class SoundPlayer {
throw SoundPlayerError.missingResourceError(file: soundFile) throw SoundPlayerError.missingResourceError(file: soundFile)
} }
// let audioSession: AVAudioSession = AVAudioSession.sharedInstance() let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
// try audioSession.setCategory(.playback) try audioSession.setCategory(.playback)
// try audioSession.setActive(true) try audioSession.setActive(true)
_player = try AVAudioPlayer(contentsOf: url) _player = try AVAudioPlayer(contentsOf: url)
_player?.prepareToPlay() _player?.prepareToPlay()
// let loopCount = repeats ? Int.max : 0 // let loopCount = repeats ? Int.max : 0
_player?.numberOfLoops = 0 //loopCount _player?.numberOfLoops = 0 //loopCount
_player?.volume = 1.0 _player?.volume = 1.0
_player?.delegate = self
// do { // do {
// try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth, .defaultToSpeaker]) // try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth, .defaultToSpeaker])
@ -60,10 +61,17 @@ class SoundPlayer {
_player?.play() _player?.play()
} }
func stop() { func stop() {
self._player?.stop() self._player?.stop()
} }
// MARK: - Delegate
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
}
} }

@ -29,7 +29,7 @@ class TimerRouter {
case let countdown as Countdown: case let countdown as Countdown:
Conductor.maestro.cancelCountdown(id: countdown.stringId) Conductor.maestro.cancelCountdown(id: countdown.stringId)
case let stopwatch as Stopwatch: case let stopwatch as Stopwatch:
self.stopStopwatch(stopwatch) self._stopStopwatch(stopwatch)
default: default:
print("missing launcher for \(self)") print("missing launcher for \(self)")
} }
@ -60,20 +60,11 @@ class TimerRouter {
} }
fileprivate static func _startStopwatch(_ stopwatch: Stopwatch, handler: @escaping (Result<Bool, Error>) -> Void) { fileprivate static func _startStopwatch(_ stopwatch: Stopwatch, handler: @escaping (Result<Bool, Error>) -> Void) {
Conductor.maestro.startStopwatch(stopwatch)
Conductor.maestro.currentStopwatches[stopwatch.stringId] = Date()
} }
static func stopStopwatch(_ stopwatch: Stopwatch, end: Date? = nil) { fileprivate static func _stopStopwatch(_ stopwatch: Stopwatch, end: Date? = nil) {
if let start = Conductor.maestro.currentStopwatches[stopwatch.stringId] { Conductor.maestro.stopStopwatch(stopwatch)
Conductor.maestro.currentStopwatches.removeValue(forKey: stopwatch.stringId)
do {
try CoreDataRequests.recordActivity(timer: stopwatch, dateInterval: DateInterval(start: start, end: end ?? Date()))
} catch {
print("could not record")
}
}
} }
} }

@ -0,0 +1,23 @@
//
// GreenCheckmarkView.swift
// LeCountdown
//
// Created by Laurent Morvillier on 06/02/2023.
//
import SwiftUI
struct GreenCheckmarkView: View {
var body: some View {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
.font(.title)
.frame(minWidth: 0.0, maxWidth: 60.0, minHeight: 0.0, maxHeight: .infinity)
}
}
struct GreenCheckmarkView_Previews: PreviewProvider {
static var previews: some View {
GreenCheckmarkView()
}
}

@ -88,12 +88,6 @@ struct ContentView<T : AbstractTimer>: View {
self._newView(isPresented: $boringContext.isShowingNewData) self._newView(isPresented: $boringContext.isShowingNewData)
.environment(\.managedObjectContext, viewContext) .environment(\.managedObjectContext, viewContext)
}) })
// .sheet(isPresented: $boringContext.isShowingNewData, content: {
// NewStopwatchView(isPresented: $boringContext.isShowingNewData) .environment(\.managedObjectContext, viewContext)
// })
// .sheet(isPresented: $boringContext.isShowingNewData, content: {
// NewAlarmView(isPresented: $boringContext.isShowingNewData) .environment(\.managedObjectContext, viewContext)
// })
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button { Button {

@ -7,15 +7,6 @@
import SwiftUI import SwiftUI
struct GreenCheckmarkView: View {
var body: some View {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
.font(.title)
.frame(minWidth: 0.0, maxWidth: 60.0, minHeight: 0.0, maxHeight: .infinity)
}
}
class LiveStopwatchModel: ObservableObject { class LiveStopwatchModel: ObservableObject {
@Published var endDate: Date? = nil @Published var endDate: Date? = nil
@ -25,7 +16,8 @@ class LiveStopwatchModel: ObservableObject {
let now = Date() let now = Date()
self.endDate = now self.endDate = now
TimerRouter.stopStopwatch(stopwatch, end: now) Conductor.maestro.stopStopwatch(stopwatch)
} }
} }

Loading…
Cancel
Save