diff --git a/LaunchWidget/LaunchWidget.swift b/LaunchWidget/LaunchWidget.swift index f28f9df..89788de 100644 --- a/LaunchWidget/LaunchWidget.swift +++ b/LaunchWidget/LaunchWidget.swift @@ -118,7 +118,6 @@ struct LaunchWidgetEntryView : View { } default: MultiCountdownView(countdowns: entry.countdowns) - } } diff --git a/LaunchWidget/LaunchWidgetLiveActivity.swift b/LaunchWidget/LaunchWidgetLiveActivity.swift index dc85417..839bf22 100644 --- a/LaunchWidget/LaunchWidgetLiveActivity.swift +++ b/LaunchWidget/LaunchWidgetLiveActivity.swift @@ -12,20 +12,50 @@ import SwiftUI struct LaunchWidgetAttributes: ActivityAttributes { public struct ContentState: Codable, Hashable { // Dynamic stateful properties about your activity go here! - var value: Int + var ended: Bool } // Fixed non-changing properties about your activity go here! + var id: String var name: String + var endDate: Date + +} + +struct LiveActivityView: View { + + var name: String + var endDate: Date + + var body: some View { + HStack { + Text(name) + Spacer() + Text(endDate, style: .timer) + .monospaced() + }.padding() + .font(.title) + + } + } struct LaunchWidgetLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: LaunchWidgetAttributes.self) { context in + // Lock screen/banner UI goes here - VStack { - Text("Hello") - } + HStack { + Text(context.attributes.name) + Spacer() + if !context.state.ended { + Text(context.attributes.endDate, style: .timer) + .monospaced() + } else { + Text("It's time!") + } + }.padding() + .font(.title) .activityBackgroundTint(Color.cyan) .activitySystemActionForegroundColor(Color.black) @@ -34,14 +64,18 @@ struct LaunchWidgetLiveActivity: Widget { // Expanded UI goes here. Compose the expanded UI through // various regions, like leading/trailing/center/bottom DynamicIslandExpandedRegion(.leading) { - Text("Leading") + Text(context.attributes.name) } DynamicIslandExpandedRegion(.trailing) { - Text("Trailing") + Text(context.attributes.endDate, style: .timer) + .monospaced() } DynamicIslandExpandedRegion(.bottom) { - Text("Bottom") - // more content + Button { + self._stop() + } label: { + Text("Stop") + } } } compactLeading: { Text("L") @@ -50,15 +84,24 @@ struct LaunchWidgetLiveActivity: Widget { } minimal: { Text("Min") } - .widgetURL(URL(string: "http://www.apple.com")) + .widgetURL(URL(string: context.attributes.id)) .keylineTint(Color.red) } } + + fileprivate func _stop() { + + } } struct LaunchWidgetLiveActivity_Previews: PreviewProvider { - static let attributes = LaunchWidgetAttributes(name: "Me") - static let contentState = LaunchWidgetAttributes.ContentState(value: 3) + + static let attributes = LaunchWidgetAttributes( + id: "", + name: "Tea", + endDate: Date().addingTimeInterval(3600.0)) + + static let contentState = LaunchWidgetAttributes.ContentState(ended: false) static var previews: some View { attributes diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 13d38d1..1bbfcc3 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ C4742B59298411E800D5D950 /* CountdownFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B58298411E800D5D950 /* CountdownFormView.swift */; }; C4742B5B298414B000D5D950 /* ImageSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B5A298414B000D5D950 /* ImageSelectionView.swift */; }; C4742B5F2984205000D5D950 /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B5E2984205000D5D950 /* ViewModifiers.swift */; }; + C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -572,6 +573,7 @@ C438C7C12980228B00BF3EF9 /* CountdownScheduler.swift in Sources */, C445FA8F2987B83B0054D761 /* SoundPlayer.swift in Sources */, C438C7C929803CA000BF3EF9 /* AppDelegate.swift in Sources */, + C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */, C4060DF5297AE9A7003FAB80 /* TimeInterval+Extensions.swift in Sources */, C4060DF7297AFEF2003FAB80 /* NewCountdownView.swift in Sources */, C4060DCC297AE73D003FAB80 /* LeCountdown.xcdatamodeld in Sources */, diff --git a/LeCountdown/AppDelegate.swift b/LeCountdown/AppDelegate.swift index 20f1031..b7b4531 100644 --- a/LeCountdown/AppDelegate.swift +++ b/LeCountdown/AppDelegate.swift @@ -23,6 +23,8 @@ extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { + print("didReceive notification") + AppEnvironment.sun.stopSoundIfNecessary() } diff --git a/LeCountdown/CountdownScheduler.swift b/LeCountdown/CountdownScheduler.swift index 0e651cf..c17092b 100644 --- a/LeCountdown/CountdownScheduler.swift +++ b/LeCountdown/CountdownScheduler.swift @@ -7,6 +7,7 @@ import Foundation import UserNotifications +import ActivityKit class CountdownScheduler { @@ -88,6 +89,8 @@ class AppEnvironment : ObservableObject { DispatchQueue.main.async { let dateInterval = DateInterval(start: Date(), end: date) self.notificationDates[countdown.stringId] = dateInterval + + self._launchLiveActivity(countdown: countdown, endDate: date) } } @@ -102,6 +105,9 @@ class AppEnvironment : ObservableObject { self._recordActivity(countdownId: countdownId) } self.notificationDates.removeValue(forKey: countdownId) + +// self._updateLiveActivity(countdownId: countdownId, endDate: <#T##Date#>) + self._endLiveActivity(countdownId: countdownId) } } @@ -149,4 +155,53 @@ class AppEnvironment : ObservableObject { self.soundPlayer?.stop() } + // MARK: - Live Activity + + fileprivate func _launchLiveActivity(countdown: Countdown, endDate: Date) { + + if ActivityAuthorizationInfo().areActivitiesEnabled { + + let contentState = LaunchWidgetAttributes.ContentState(ended: false) + let attributes = LaunchWidgetAttributes(id: countdown.stringId, name: countdown.displayName, endDate: endDate) + let activityContent = ActivityContent(state: contentState, staleDate: endDate.addingTimeInterval(30.0)) + + do { + let liveActivity = try ActivityKit.Activity.request(attributes: attributes, content: activityContent) + print("Requested a countdown Live Activity \(String(describing: liveActivity.id)).") + } catch (let error) { + print("Error requesting countdown Live Activity \(error.localizedDescription).") + } + + } + } + + fileprivate func _liveActivity(countdownId: String) -> ActivityKit.Activity? { + return ActivityKit.Activity.activities.first(where: { $0.attributes.id == countdownId } ) + } + +// fileprivate func _updateLiveActivity(countdownId: String, endDate: Date) { +// if let activity = self._liveActivity(countdownId: countdownId) { +// Task { +// let state = LaunchWidgetAttributes.ContentState(ended: true) +// let content = ActivityContent(state: state, staleDate: endDate) +// await activity.update(content) +// print("Ending the Live Activity: \(activity.id)") +// } +// } +// } + + fileprivate func _endLiveActivity(countdownId: String) { + + print("Trt to end the Live Activity: \(countdownId)") + + if let activity = self._liveActivity(countdownId: countdownId) { + Task { + let state = LaunchWidgetAttributes.ContentState(ended: true) + let content = ActivityContent(state: state, staleDate: Date()) + await activity.end(content, dismissalPolicy: .immediate) + print("Ending the Live Activity: \(activity.id)") + } + } + } + } diff --git a/LeCountdown/Info.plist b/LeCountdown/Info.plist index 3847eec..d5fa61c 100644 --- a/LeCountdown/Info.plist +++ b/LeCountdown/Info.plist @@ -2,6 +2,8 @@ + NSSupportsLiveActivities + NSUserActivityTypes SelectCountdownIntent diff --git a/LeCountdown/Model/Model+Extensions.swift b/LeCountdown/Model/Model+Extensions.swift index 21e1d13..83dcb3e 100644 --- a/LeCountdown/Model/Model+Extensions.swift +++ b/LeCountdown/Model/Model+Extensions.swift @@ -11,6 +11,10 @@ import CoreData extension Countdown { + var displayName: String { + return self.name ?? self.coolpic.emoji + } + var name: String? { return self.activity?.name } @@ -23,11 +27,15 @@ extension Countdown { } } - var imageName: String { - if let image { - return image + var coolpic: CoolPic { + if let image, let coolpic = CoolPic(rawValue: image) { + return coolpic } - return CoolPic.allCases[0].rawValue + return CoolPic.allCases[0] + } + + var imageName: String { + return self.coolpic.rawValue } var soundFile: SoundFile { diff --git a/LeCountdown/Utils/CoolPic.swift b/LeCountdown/Utils/CoolPic.swift index 42483e0..3da4f68 100644 --- a/LeCountdown/Utils/CoolPic.swift +++ b/LeCountdown/Utils/CoolPic.swift @@ -20,4 +20,9 @@ enum CoolPic : String, CaseIterable, Identifiable { case pic7 case pic8 case pic9 + + var emoji: String { + return "☕️" + } + }