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>
This commit is contained in:
co-authored by
Cursor
parent
b798e0ea06
commit
585332e32e
@@ -0,0 +1,125 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LoginScreen: View {
|
||||
@ObservedObject var container: AppContainer
|
||||
let onLoggedIn: () -> Void
|
||||
|
||||
@State private var email = ""
|
||||
@State private var password = ""
|
||||
@State private var error: String?
|
||||
@State private var loading = false
|
||||
@State private var passwordVisible = false
|
||||
|
||||
var body: some View {
|
||||
MatchScreenScaffold {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
MatchLiveWordmark(showSlogan: true)
|
||||
.padding(.top, 32)
|
||||
Text("ACCEDI")
|
||||
.font(MatchTypography.headlineMedium)
|
||||
.padding(.top, 48)
|
||||
Text("Gestisci le dirette della tua squadra")
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.top, 8)
|
||||
VStack(spacing: 16) {
|
||||
MatchTextField(title: "Email", text: $email, placeholder: "coach@squadra.it", keyboard: .emailAddress)
|
||||
MatchSecureField(title: "Password", text: $password, visible: $passwordVisible)
|
||||
}
|
||||
.padding(.top, 32)
|
||||
if let error {
|
||||
Text(error)
|
||||
.foregroundStyle(MatchColors.primaryRed)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.top, 12)
|
||||
}
|
||||
MatchPrimaryButton(
|
||||
label: "ACCEDI",
|
||||
action: submitLogin,
|
||||
enabled: !email.isEmpty && !password.isEmpty,
|
||||
loading: loading
|
||||
)
|
||||
.padding(.top, 32)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func submitLogin() {
|
||||
guard !loading, !email.isEmpty, !password.isEmpty else { return }
|
||||
loading = true
|
||||
error = nil
|
||||
Task {
|
||||
do {
|
||||
_ = try await container.authRepository.login(email: email.trimmingCharacters(in: .whitespaces), password: password)
|
||||
onLoggedIn()
|
||||
} catch {
|
||||
let message = error.localizedDescription
|
||||
if message.contains("401") {
|
||||
self.error = "Email o password non corretti"
|
||||
} else if message.localizedCaseInsensitiveContains("timeout") {
|
||||
self.error = "Server non raggiungibile. Verifica la connessione."
|
||||
} else {
|
||||
self.error = message
|
||||
}
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MatchTextField: View {
|
||||
let title: String
|
||||
@Binding var text: String
|
||||
var placeholder: String = ""
|
||||
var keyboard: UIKeyboardType = .default
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(title)
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
TextField(placeholder, text: $text)
|
||||
.textInputAutocapitalization(.never)
|
||||
.keyboardType(keyboard)
|
||||
.autocorrectionDisabled()
|
||||
.padding(12)
|
||||
.overlay(RoundedRectangle(cornerRadius: 8).stroke(MatchColors.outline))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MatchSecureField: View {
|
||||
let title: String
|
||||
@Binding var text: String
|
||||
@Binding var visible: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(title)
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
HStack {
|
||||
Group {
|
||||
if visible {
|
||||
TextField("", text: $text)
|
||||
} else {
|
||||
SecureField("", text: $text)
|
||||
}
|
||||
}
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
Button(action: { visible.toggle() }) {
|
||||
Image(systemName: visible ? "eye.slash" : "eye")
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
}
|
||||
}
|
||||
.padding(12)
|
||||
.overlay(RoundedRectangle(cornerRadius: 8).stroke(MatchColors.outline))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user