- Mini player shows as soon as generation starts (not just when audio ready) - Idempotency: tapping headphones again during generation is a no-op - Playback resets to position 0 on completion (standard player behavior) - Stop button in full player visible during generation - PodcastLibraryView lists all generated podcasts - Sparkles menu gains Podcasts entry - Fix AnalyticsService Sendable error (serialize before Task.detached boundary)
29 lines
1.3 KiB
Swift
29 lines
1.3 KiB
Swift
import Foundation
|
|
|
|
enum Analytics {
|
|
static func track(_ event: String, _ properties: [String: Any] = [:]) {
|
|
// Serialize before crossing the task boundary — Data is Sendable, [String: Any] is not
|
|
var body: [String: Any] = ["event": event]
|
|
if !properties.isEmpty { body["properties"] = properties }
|
|
guard let payload = try? JSONSerialization.data(withJSONObject: body) else { return }
|
|
Task.detached {
|
|
do {
|
|
let token = try await MarksAuth.validToken()
|
|
guard let url = URL(string: MarksAuth.baseURL + "/v1/marks/events") else { return }
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "POST"
|
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.httpBody = payload
|
|
let (_, response) = try await URLSession.shared.data(for: request)
|
|
let status = (response as? HTTPURLResponse)?.statusCode ?? 0
|
|
if status < 200 || status > 299 {
|
|
print("[Analytics] \(event) → HTTP \(status)")
|
|
}
|
|
} catch {
|
|
print("[Analytics] \(event) failed: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
}
|
|
}
|