Rendi affidabile pausa con slate e conferma i controlli laterali.

Evita buchi HLS/YouTube in pausa (path slate ricreata, reload player, no patch recording inutili) e richiede conferma su tasti laterali app/regia.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-20 12:44:23 +02:00
co-authored by Cursor
parent e09b3ffcf8
commit 0702cb699c
37 changed files with 1186 additions and 83 deletions
@@ -4,6 +4,15 @@ private let sideToolbarWidth: CGFloat = 44
private let iconButtonSize: CGFloat = 36
private let scoreButtonHeight: CGFloat = 32
private enum SideConfirmAction {
case shareLive
case shareRegia
case advancePeriod
case pauseOrResume
case toggleMute
case terminate
}
struct BroadcastControlsOverlay: View {
let controlsVisible: Bool
let onToggleControls: () -> Void
@@ -46,7 +55,7 @@ struct BroadcastControlsOverlay: View {
var sessionQualityPreset: String = "720p_30_2.5mbps"
var onSelectMinQuality: (String) -> Void = { _ in }
@State private var showTerminateConfirm = false
@State private var pendingConfirm: SideConfirmAction?
@State private var showMinQualityPicker = false
var body: some View {
@@ -118,11 +127,62 @@ struct BroadcastControlsOverlay: View {
)
}
}
.alert(L10n.t("broadcast.terminate.title"), isPresented: $showTerminateConfirm) {
Button(L10n.t("action.cancel"), role: .cancel) {}
Button(L10n.t("broadcast.terminate.confirm"), role: .destructive, action: onTerminate)
} message: {
Text(L10n.t("broadcast.terminate.message"))
.alert(
sideConfirmTitle,
isPresented: Binding(
get: { pendingConfirm != nil },
set: { if !$0 { pendingConfirm = nil } }
)
) {
Button(L10n.t("action.cancel"), role: .cancel) { pendingConfirm = nil }
if pendingConfirm == .terminate {
Button(L10n.t("broadcast.terminate.confirm"), role: .destructive, action: performPendingConfirm)
} else {
Button(L10n.t("action.confirm"), action: performPendingConfirm)
}
} message: {
Text(sideConfirmMessage)
}
}
private var sideConfirmTitle: String {
switch pendingConfirm {
case .shareLive: return L10n.t("broadcast.share.live.title")
case .shareRegia: return L10n.t("broadcast.share.regia.title")
case .advancePeriod: return L10n.t("broadcast.advance.period.title")
case .pauseOrResume:
return isPaused ? L10n.t("broadcast.resume.title") : L10n.t("broadcast.pause.title")
case .toggleMute:
return audioMuted ? L10n.t("broadcast.unmute.title") : L10n.t("broadcast.mute.title")
case .terminate: return L10n.t("broadcast.terminate.title")
case .none: return ""
}
}
private var sideConfirmMessage: String {
switch pendingConfirm {
case .shareLive: return L10n.t("broadcast.share.live.message")
case .shareRegia: return L10n.t("broadcast.share.regia.message")
case .advancePeriod: return L10n.t("broadcast.advance.period.message")
case .pauseOrResume:
return isPaused ? L10n.t("broadcast.resume.message") : L10n.t("broadcast.pause.message")
case .toggleMute:
return audioMuted ? L10n.t("broadcast.unmute.message") : L10n.t("broadcast.mute.message")
case .terminate: return L10n.t("broadcast.terminate.message")
case .none: return ""
}
}
private func performPendingConfirm() {
guard let action = pendingConfirm else { return }
pendingConfirm = nil
switch action {
case .shareLive: onShareLive()
case .shareRegia: onShareRegia()
case .advancePeriod: onAdvancePeriod?()
case .pauseOrResume: onPauseOrResume()
case .toggleMute: onToggleAudioMute()
case .terminate: onTerminate()
}
}
@@ -131,13 +191,13 @@ struct BroadcastControlsOverlay: View {
SideIconButton(
systemName: "square.and.arrow.up",
accessibilityLabel: L10n.t("broadcast.share.live.cd"),
action: onShareLive,
action: { pendingConfirm = .shareLive },
enabled: shareLiveEnabled
)
SideIconButton(
systemName: "video.fill",
accessibilityLabel: L10n.t("broadcast.share.regia.cd"),
action: onShareRegia
action: { pendingConfirm = .shareRegia }
)
SideIconButton(
systemName: "slider.horizontal.3",
@@ -151,11 +211,11 @@ struct BroadcastControlsOverlay: View {
action: onCloseSet
)
}
if let onAdvancePeriod {
if onAdvancePeriod != nil {
SideIconButton(
systemName: "forward.end.fill",
accessibilityLabel: L10n.t("broadcast.next.period.cd"),
action: onAdvancePeriod
action: { pendingConfirm = .advancePeriod }
)
}
}
@@ -168,19 +228,19 @@ struct BroadcastControlsOverlay: View {
SideIconButton(
systemName: isPaused ? "play.fill" : "pause.fill",
accessibilityLabel: isPaused ? L10n.t("broadcast.resume.cd") : L10n.t("broadcast.pause.cd"),
action: onPauseOrResume,
action: { pendingConfirm = .pauseOrResume },
highlighted: isPaused
)
SideIconButton(
systemName: audioMuted ? "speaker.slash.fill" : "speaker.wave.2.fill",
accessibilityLabel: audioMuted ? L10n.t("broadcast.unmute.cd") : L10n.t("broadcast.mute.cd"),
action: onToggleAudioMute,
action: { pendingConfirm = .toggleMute },
highlighted: audioMuted
)
SideIconButton(
systemName: "stop.fill",
accessibilityLabel: L10n.t("broadcast.terminate.cd"),
action: { showTerminateConfirm = true },
action: { pendingConfirm = .terminate },
danger: true
)
}
@@ -3,6 +3,7 @@ import SwiftUI
enum ScoreDialogKind {
case setWon
case closeSet
case closeSetAnyway
case matchWon
}
@@ -92,10 +93,10 @@ final class LiveScoreActions {
awayPoints: score.awayPoints,
currentSet: score.currentSet
)
if winner == nil {
let ok = await dialogHost.show(ScoreDialogState(kind: .closeSetAnyway))
if !ok { return }
}
let ok = await dialogHost.show(
ScoreDialogState(kind: winner == nil ? .closeSetAnyway : .closeSet)
)
if !ok { return }
guard await scoreController.closeSetAsync() else { return }
onScoreboardChanged()
await afterCloseSet()
@@ -149,6 +150,13 @@ struct ScoreDialogRouter: View {
primaryButton: .default(Text(L10n.t("score.action.close.set"))) { host.resolve(true) },
secondaryButton: .cancel(Text(L10n.t("score.action.continue.scoring"))) { host.resolve(false) }
)
case .closeSet:
return Alert(
title: Text(L10n.t("score.action.close.set")),
message: Text(L10n.t("broadcast.close.set.confirm.message")),
primaryButton: .default(Text(L10n.t("score.action.close.set"))) { host.resolve(true) },
secondaryButton: .cancel(Text(L10n.t("action.cancel"))) { host.resolve(false) }
)
case .closeSetAnyway:
return Alert(
title: Text(L10n.t("score.action.close.set")),