import SwiftUI struct FeedView: View { @EnvironmentObject private var localization: LocalizationStore @EnvironmentObject private var analytics: HermesAnalyticsClient @EnvironmentObject private var repository: HermesRepository let onWatchPreview: () -> Void = {} let onRetry: () -> Void var body: some View { TimelineView(.periodic(from: Date(), by: 1)) { _ in let round = repository.currentRound let now = repository.serverNow() let bannerMessage = hermesUserFacingErrorMessage( localization: localization, localeCode: localization.localeCode, error: repository.errorCause ) VStack(alignment: .leading, spacing: HermesTheme.sectionSpacing) { HermesSectionHeader( title: localization.string(for: "feed.title"), subtitle: localization.string(for: "feed.subtitle") ) if round == nil { if let bannerMessage { feedErrorState( message: bannerMessage, retryText: localization.string(for: "common.retry"), onRetry: onRetry ) } else { feedLoadingState( title: localization.string(for: "common.loading"), subtitle: localization.string(for: "feed.subtitle") ) } } else { if let bannerMessage { feedBanner(message: bannerMessage) } heroCard(round: round) HStack(spacing: 12) { HermesMetricPill( label: localization.string(for: "feed.lock_label"), value: round.map { Self.countdownText(for: $0.event.lockAt.timeIntervalSince(now)) } ?? "--:--" ) HermesMetricPill( label: localization.string(for: "feed.odds_label"), value: round.map { Self.formatOdds($0) } ?? "--" ) } Button { analytics.track("next_round_requested", attributes: ["screen_name": "feed"]) analytics.track("cta_pressed", attributes: ["screen_name": "feed", "action": "watch_preview"]) onWatchPreview() } label: { Text(localization.string(for: "feed.cta")) } .buttonStyle(HermesPrimaryButtonStyle()) .disabled(round == nil) } } .onAppear { analytics.track("feed_viewed", attributes: ["screen_name": "feed"]) analytics.track("screen_viewed", attributes: ["screen_name": "feed"]) } } .hermesCard(elevated: true) } @ViewBuilder private func heroCard(round: HermesRound?) -> some View { ZStack(alignment: .bottomLeading) { RoundedRectangle(cornerRadius: HermesTheme.cornerRadius, style: .continuous) .fill( LinearGradient( colors: [HermesTheme.surfaceElevated, HermesTheme.background], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(height: 220) VStack(alignment: .leading, spacing: 8) { Text(round.map { localizedEventTitle($0) } ?? localization.string(for: "feed.hero_title")) .font(.title2.weight(.bold)) .foregroundStyle(HermesTheme.textPrimary) Text(localization.string(for: "feed.hero_subtitle")) .font(.callout) .foregroundStyle(HermesTheme.textSecondary) .frame(maxWidth: 260, alignment: .leading) } .padding(HermesTheme.contentPadding) } } @ViewBuilder private func feedLoadingState(title: String, subtitle: String) -> some View { VStack(alignment: .leading, spacing: 8) { Text(title) .font(.title2.weight(.bold)) .foregroundStyle(HermesTheme.textPrimary) Text(subtitle) .font(.callout) .foregroundStyle(HermesTheme.textSecondary) } .padding(HermesTheme.contentPadding) .frame(maxWidth: .infinity, minHeight: 220, alignment: .center) .background( RoundedRectangle(cornerRadius: HermesTheme.cornerRadius, style: .continuous) .fill( LinearGradient( colors: [HermesTheme.surfaceElevated, HermesTheme.background], startPoint: .topLeading, endPoint: .bottomTrailing ) ) ) } @ViewBuilder private func feedErrorState(message: String, retryText: String, onRetry: @escaping () -> Void) -> some View { VStack(alignment: .leading, spacing: 12) { Text(message) .font(.callout) .foregroundStyle(HermesTheme.warning) Button { onRetry() } label: { Text(retryText) } .buttonStyle(HermesSecondaryButtonStyle()) } } @ViewBuilder private func feedBanner(message: String) -> some View { Text(message) .font(.callout) .foregroundStyle(HermesTheme.warning) .padding(.horizontal, 12) .padding(.vertical, 10) .frame(maxWidth: .infinity, alignment: .leading) .background(HermesTheme.warning.opacity(0.12)) .clipShape(RoundedRectangle(cornerRadius: HermesTheme.insetRadius, style: .continuous)) } private func localizedEventTitle(_ round: HermesRound) -> String { localization.localeCode == "sv" ? round.event.titleSv : round.event.titleEn } private static func countdownText(for remaining: TimeInterval) -> String { let totalSeconds = max(Int(remaining.rounded(.down)), 0) let minutes = totalSeconds / 60 let seconds = totalSeconds % 60 return String(format: "%02d:%02d", minutes, seconds) } private static func formatOdds(_ round: HermesRound) -> String { let oddsByOutcomeId = Dictionary(uniqueKeysWithValues: round.oddsVersion.odds.map { ($0.outcomeId, $0) }) return round.market.outcomes .sorted(by: { $0.sortOrder < $1.sortOrder }) .compactMap { oddsByOutcomeId[$0.id]?.decimalOdds } .map { String(format: "%.2f", $0) } .joined(separator: " / ") } }