Files
MatchLiveTv/native/ios/MatchLiveTv/Data/Repository/MatchRepository.swift
Emiliano Frascaro 585332e32e Aggiunge app iOS nativa con parità multi-sport e test unitari.
Porting SwiftUI da Android (auth, hub, wizard, broadcast RTMP, overlay, scoring board-aware), reload hub al ritorno da diretta, decodifica score tollerante e documentazione allineata.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 21:37:32 +02:00

146 lines
5.7 KiB
Swift

import Foundation
import UIKit
@MainActor
final class MatchRepository {
private let api: MatchLiveAPI
private let tokenStore: TokenStore
init(api: MatchLiveAPI, tokenStore: TokenStore) {
self.api = api
self.tokenStore = tokenStore
}
func fetchSports() async throws -> [SportOption] {
try await api.sports().map { $0.toDomain() }
}
func fetchTeams() async throws -> [Team] {
try await api.teams().map { $0.toDomain() }
}
func fetchTeam(teamId: String) async -> Team? {
if let dto = try? await api.team(id: teamId) { return dto.toDomain() }
let teams = (try? await fetchTeams()) ?? []
return teams.first { $0.id == teamId }
}
func resolveActiveTeam(teams: [Team]) -> Team? {
guard !teams.isEmpty else { return nil }
let selected = teams.first { $0.id == tokenStore.activeTeamId } ?? teams[0]
tokenStore.saveActiveTeamId(selected.id)
return selected
}
func fetchMatch(matchId: String) async throws -> Match {
try await api.match(id: matchId).toDomain()
}
func fetchMatchesForTeam(teamId: String) async throws -> [Match] {
let matches = try await api.matches(teamId: teamId).map { $0.toDomain() }
return MatchHubFilter.sortMatches(matches.filter { MatchHubFilter.coachHubVisible($0) })
}
func fetchMatches() async throws -> [Match] {
let teams = try await fetchTeams()
guard let team = resolveActiveTeam(teams: teams) else { return [] }
return try await fetchMatchesForTeam(teamId: team.id)
}
func selectTeam(teamId: String) {
tokenStore.saveActiveTeamId(teamId)
}
func createQuickMatch(teamId: String) async throws -> Match {
try await api.createMatch(
teamId: teamId,
body: CreateMatchRequest(match: CreateMatchBody(opponentName: "Avversario", sport: "volleyball", setsToWin: 3, scheduledAt: nil, location: nil))
).toDomain()
}
func createScheduledMatch(teamId: String, opponentName: String, scheduledAt: Date, location: String?) async throws -> Match {
let iso = ISO8601DateFormatter().string(from: scheduledAt)
return try await api.createMatch(
teamId: teamId,
body: CreateMatchRequest(
match: CreateMatchBody(
opponentName: opponentName,
sport: "volleyball",
setsToWin: 3,
scheduledAt: iso,
location: location?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty
)
)
).toDomain()
}
func updateTeamBranding(teamId: String, primaryColor: String, secondaryColor: String?, logoImage: UIImage?) async throws -> Team {
if let logoImage, let data = logoImage.pngData() {
return try await api.updateTeamMultipart(
id: teamId,
primaryColor: primaryColor,
secondaryColor: secondaryColor,
logoData: data,
logoFilename: "logo.png"
).toDomain()
}
return try await api.updateTeam(
id: teamId,
body: UpdateTeamRequest(team: UpdateTeamBody(primaryColor: primaryColor, secondaryColor: secondaryColor))
).toDomain()
}
func updateMatch(
matchId: String,
opponentName: String,
location: String?,
scheduledAt: String?,
setsToWin: Int,
category: String?,
opponentPrimaryColor: String?,
scoringRules: ScoringRules?,
sportKey: String? = nil,
overlayKind: String? = nil,
opponentLogoImage: UIImage? = nil
) async throws -> Match {
let rulesMap = scoringRules?.toMap(setsToWin: setsToWin)
let body = UpdateMatchBodyFull(
opponentName: opponentName,
location: location?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty,
scheduledAt: scheduledAt,
setsToWin: setsToWin,
sportKey: sportKey,
overlayKind: overlayKind,
category: category?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty,
opponentPrimaryColor: opponentPrimaryColor,
scoringRules: rulesMap
)
if let opponentLogoImage, let data = opponentLogoImage.pngData() {
var fields: [MultipartField] = [
.text("match[opponent_name]", opponentName),
.text("match[sets_to_win]", "\(setsToWin)"),
]
if let location = body.location { fields.append(.text("match[location]", location)) }
if let scheduledAt { fields.append(.text("match[scheduled_at]", scheduledAt)) }
if let category = body.category { fields.append(.text("match[category]", category)) }
if let color = opponentPrimaryColor { fields.append(.text("match[opponent_primary_color]", color)) }
if let rules = scoringRules {
fields.append(.text("match[scoring_rules][points_per_set]", "\(rules.pointsPerSet)"))
fields.append(.text("match[scoring_rules][points_deciding_set]", "\(rules.pointsDecidingSet)"))
fields.append(.text("match[scoring_rules][min_point_lead]", "\(rules.minPointLead)"))
}
fields.append(.file("opponent_logo_file", data, filename: "opponent.png", mime: "image/png"))
return try await api.updateMatchMultipart(id: matchId, fields: fields).toDomain()
}
return try await api.updateMatch(id: matchId, body: UpdateMatchRequestFull(match: body)).toDomain()
}
func deleteMatch(matchId: String) async throws {
try await api.deleteMatch(id: matchId)
}
}
private extension String {
var nilIfEmpty: String? { isEmpty ? nil : self }
}