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>
146 lines
5.7 KiB
Swift
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 }
|
|
}
|