Files
attendi/Apps/Attendi/Sources/Services/ActivityReporting.swift
T

147 lines
4.5 KiB
Swift

#if os(iOS)
@preconcurrency import ActivityKit
#endif
import Commanding
import Foundation
import Recording
/// The reporting service that surfaces the recording flow in a Live Activity on the Lock Screen and in the Dynamic Island.
///
/// The service implements the `Recording` feature's `Reporting` port over the Live Activity vocabulary shared with the widget
/// extension through the `Commanding` target: it starts a Live Activity when a recording starts, updates its content on every pause,
/// resumption, and processing transition, and ends it — dismissing it right away — when the flow ends. The activity is an auxiliary
/// surface of the recording flow, never a required one: a start that fails, or that the user disallowed in the system settings, is
/// silently ignored, and the platforms without Live Activities reduce the whole service to a no-op.
@MainActor
final class ActivityReporting: Reporting {
// MARK: Properties
#if os(iOS)
/// The Live Activity reflecting the ongoing recording flow, or `nil` while none is.
private var activity: Activity<RecordingActivityAttributes>?
#endif
// MARK: Initializers
/// Creates a reporting service with no Live Activity started yet.
init() {}
// MARK: Methods
/// Starts a Live Activity for a new recording, unless the user disallowed Live Activities or the system refuses to start one.
///
/// - Parameter anchor: The instant the elapsed recording time counts from.
func started(
at anchor: Date
) async {
#if os(iOS)
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
return
}
activity = try? Activity.request(
attributes: RecordingActivityAttributes(),
content: .init(
state: .init(
state: .recording,
anchor: anchor,
elapsed: .zero
),
staleDate: nil
)
)
#endif
}
/// Updates the Live Activity with the pause of the ongoing recording, freezing its timer at the elapsed recording time.
///
/// - Parameter elapsed: The number of seconds spent recording so far, excluding any time spent paused.
func paused(
elapsed: TimeInterval
) async {
#if os(iOS)
await update(
state: .paused,
anchor: nil,
elapsed: elapsed
)
#endif
}
/// Updates the Live Activity with the resumption of a paused recording, restarting its timer from the given anchor.
///
/// - Parameter anchor: The instant the elapsed recording time counts from, moved back by the time already spent recording.
func resumed(
anchor: Date
) async {
#if os(iOS)
await update(
state: .recording,
anchor: anchor,
elapsed: .zero
)
#endif
}
/// Updates the Live Activity with the processing of the recorded input, freezing its timer at the elapsed recording time.
///
/// - Parameter elapsed: The number of seconds spent recording, excluding any time spent paused.
func processing(
elapsed: TimeInterval
) async {
#if os(iOS)
await update(
state: .processing,
anchor: nil,
elapsed: elapsed
)
#endif
}
/// Ends the Live Activity of the recording flow, dismissing it right away.
func ended() async {
#if os(iOS)
await activity?.end(
activity?.content,
dismissalPolicy: .immediate
)
activity = nil
#endif
}
}
#if os(iOS)
// MARK: - Helpers
private extension ActivityReporting {
/// Updates the dynamic content of the Live Activity, when one is running.
///
/// - Parameters:
/// - state: The state of the recording flow to show.
/// - anchor: The instant the elapsed recording time counts from, or `nil` while the recording is not running.
/// - elapsed: The number of seconds spent recording up to the transition, excluding any time spent paused.
func update(
state: RecordingActivityState,
anchor: Date?,
elapsed: TimeInterval
) async {
await activity?.update(
.init(
state: .init(
state: state,
anchor: anchor,
elapsed: elapsed
),
staleDate: nil
)
)
}
}
#endif