polish iOS study flow

This commit is contained in:
2026-04-09 15:51:03 +02:00
parent 5c0aa9542a
commit 0cfd847d62
16 changed files with 508 additions and 94 deletions
@@ -1,13 +1,28 @@
import Combine
import Foundation
protocol AnalyticsTracking {
func track(_ event: String, attributes: [String: String])
}
final class HermesAnalyticsClient: AnalyticsTracking {
struct HermesTrackedEvent: Identifiable, Equatable {
let id = UUID()
let event: String
let attributes: [String: String]
let timestamp: Date
}
@MainActor
final class HermesAnalyticsClient: ObservableObject, AnalyticsTracking {
@Published private(set) var trackedEvents: [HermesTrackedEvent] = []
func track(_ event: String, attributes: [String: String]) {
// Scaffold implementation.
_ = event
_ = attributes
trackedEvents.append(
HermesTrackedEvent(
event: event,
attributes: attributes,
timestamp: Date()
)
)
}
}
+3 -2
View File
@@ -115,6 +115,7 @@ struct HermesMetricPill: View {
struct HermesCountdownBadge: View {
let label: String
let value: String
var warning: Bool = false
var body: some View {
VStack(alignment: .leading, spacing: 4) {
@@ -123,11 +124,11 @@ struct HermesCountdownBadge: View {
.foregroundStyle(HermesTheme.textTertiary)
Text(value)
.font(.title3.weight(.bold))
.foregroundStyle(HermesTheme.accent)
.foregroundStyle(warning ? HermesTheme.warning : HermesTheme.accent)
}
.padding(.horizontal, 14)
.padding(.vertical, 12)
.background(HermesTheme.accentSoft)
.background((warning ? HermesTheme.warning : HermesTheme.accent).opacity(0.16))
.clipShape(RoundedRectangle(cornerRadius: HermesTheme.insetRadius, style: .continuous))
}
}
@@ -1,7 +1,37 @@
import AVFoundation
import Combine
import Foundation
@MainActor
final class PlayerCoordinator: ObservableObject {
let player: AVPlayer
@Published var isPlaying = false
@Published var playbackPositionMs: Int = 0
init(previewURL: URL = URL(string: "https://cdn.example.com/hermes/sample-event/master.m3u8")!) {
self.player = AVPlayer(url: previewURL)
self.player.actionAtItemEnd = .pause
}
func prepareForPreview() {
player.seek(to: .zero)
player.play()
isPlaying = true
}
func play() {
player.play()
isPlaying = true
}
func pause() {
player.pause()
isPlaying = false
}
func restart() {
player.seek(to: .zero)
play()
}
}
@@ -0,0 +1,18 @@
import AVKit
import SwiftUI
struct StudyVideoPlayerView: View {
@ObservedObject var coordinator: PlayerCoordinator
var body: some View {
VideoPlayer(player: coordinator.player)
.frame(maxWidth: .infinity)
.frame(height: 224)
.clipShape(RoundedRectangle(cornerRadius: HermesTheme.cornerRadius, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: HermesTheme.cornerRadius, style: .continuous)
.stroke(HermesTheme.accent.opacity(0.12), lineWidth: 1)
)
.background(HermesTheme.surfaceElevated)
}
}
@@ -42,8 +42,8 @@ struct HermesEventMedia: Codable {
var id: UUID
var eventId: UUID
var mediaType: String
var hlsMasterURL: URL
var posterURL: URL?
var hlsMasterUrl: URL
var posterUrl: URL?
var durationMs: Int
var previewStartMs: Int
var previewEndMs: Int