multistore
Razmig Sarkissian 1 year ago
parent b961b8919b
commit 27fa26f86e
  1. 8
      PadelClub.xcodeproj/project.pbxproj
  2. 8
      PadelClub/Data/Tournament.swift
  3. 2
      PadelClub/Utils/Tips.swift
  4. 5
      PadelClub/Utils/URLs.swift
  5. 20
      PadelClub/Views/Tournament/Screen/BroadcastView.swift
  6. 35
      PadelClub/Views/Tournament/Shared/TournamentBroadcastRowView.swift
  7. 45
      PadelClub/Views/Tournament/TournamentBuildView.swift
  8. 20
      PadelClub/Views/Tournament/TournamentInitView.swift
  9. 28
      PadelClub/Views/Tournament/TournamentInscriptionView.swift
  10. 14
      PadelClub/Views/Tournament/TournamentView.swift
  11. 82
      PadelClub/Views/User/LoginView.swift

@ -202,6 +202,7 @@
FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */; };
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; };
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; };
FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */; };
FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; };
FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; };
FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; };
@ -525,6 +526,7 @@
FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = "<group>"; };
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = "<group>"; };
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentBroadcastRowView.swift; sourceTree = "<group>"; };
FFB9C8702BBADDE200A0EF4F /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = "<group>"; };
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = "<group>"; };
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamView.swift; sourceTree = "<group>"; };
@ -952,6 +954,7 @@
FF7091672B90F79F00AB08DA /* TournamentCellView.swift */,
FF7091692B90F95E00AB08DA /* DateBoxView.swift */,
FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */,
FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */,
);
path = Shared;
sourceTree = "<group>";
@ -1483,6 +1486,7 @@
FF025AE12BD0EB9000A86CF8 /* TournamentClubSettingsView.swift in Sources */,
FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */,
FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */,
FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */,
FF025ADF2BD0CE0A00A86CF8 /* TeamWeightView.swift in Sources */,
FF9268012BCE94920080F940 /* SeedsCallingView.swift in Sources */,
FF9268092BCEDC2C0080F940 /* CallView.swift in Sources */,
@ -1855,7 +1859,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 39;
CURRENT_PROJECT_VERSION = 40;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1893,7 +1897,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 39;
CURRENT_PROJECT_VERSION = 40;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -446,8 +446,12 @@ class Tournament : ModelObject, Storable {
}
func shareURL(_ pageLink: PageLink = .matches) -> URL? {
if pageLink == .broadcast, let club = club(), let broadcastCode = club.broadcastCode {
return URLs.main.url.appending(path: "c/\(broadcastCode)")
if pageLink == .clubBroadcast {
if let club = club(), let broadcastCode = club.broadcastCode {
return URLs.main.url.appending(path: "c/\(broadcastCode)")
} else {
return nil
}
}
return URLs.main.url.appending(path: "tournament/\(id)").appending(path: pageLink.path)
}

@ -418,7 +418,7 @@ struct CreateAccountTip: Tip {
}
var message: Text? {
let message = "Un compte est nécessaire pour publier le tournoi sur [Padel Club](\(URLs.main.rawValue)) et profiter de toutes du site, comme le mode TV pour transformer l'expérience de vos tournois !"
let message = "Un compte est nécessaire pour publier le tournoi sur [Padel Club](\(URLs.main.rawValue)) et profiter de toutes les pages du site, comme le mode TV pour transformer l'expérience de vos tournois !"
return Text(.init(message))
}

@ -28,7 +28,8 @@ enum PageLink: String, Identifiable, CaseIterable {
case groupStages = "Poules"
case matches = "Matchs"
case rankings = "Classement"
case broadcast = "Broadcast"
case broadcast = "Mode TV (Tournoi)"
case clubBroadcast = "Mode TV (Club)"
var id: String { self.rawValue }
@ -50,6 +51,8 @@ enum PageLink: String, Identifiable, CaseIterable {
return "group-stages"
case .broadcast:
return "broadcast"
case .clubBroadcast:
return ""
}
}
}

@ -63,7 +63,7 @@ struct BroadcastView: View {
}
if tournament.isPrivate == false {
if let url = tournament.shareURL(.broadcast) {
if let url = tournament.shareURL(.clubBroadcast) {
Section {
Link(destination: url) {
Text(url.absoluteString)
@ -251,14 +251,20 @@ struct BroadcastView: View {
Text("Padel Club")
}
if let club = tournament.club(), let clubURL = club.shareURL() {
LabeledContent {
let club = tournament.club()
LabeledContent {
if let clubURL = club?.shareURL() {
actionForURL(clubURL)
} label: {
Text("Club")
}
} label: {
Text("Club")
if let club {
Text(club.clubTitle())
} else {
Text("Aucun club indiqué pour ce tournoi")
}
}
if let url = tournament.shareURL(pageLink) {
LabeledContent {
actionForURL(url)
@ -268,7 +274,7 @@ struct BroadcastView: View {
}
}
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings]
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast]
Picker(selection: $pageLink) {
ForEach(links) { pageLink in
Text(pageLink.localizedLabel()).tag(pageLink)

@ -0,0 +1,35 @@
//
// TournamentBroadcastRowView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 05/06/2024.
//
import SwiftUI
import LeStorage
struct TournamentBroadcastRowView: View {
var tournament: Tournament
var body: some View {
NavigationLink(value: Screen.broadcast) {
LabeledContent {
if Store.main.userId == nil {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.logoRed)
} else {
if tournament.isPrivate {
Text("tournoi privé").foregroundStyle(.logoRed)
} else {
Text("Automatique")
}
}
} label: {
Text("Publication")
if Store.main.userId == nil {
Text("Un compte Padel Club est nécessaire")
}
}
}
}
}

@ -17,6 +17,8 @@ struct TournamentBuildView: View {
@ViewBuilder
var body: some View {
let state = tournament.state()
if tournament.hasEnded() {
Section {
NavigationLink(value: Screen.rankings) {
@ -70,9 +72,13 @@ struct TournamentBuildView: View {
}
Section {
if tournament.state() != .finished {
NavigationLink(value: Screen.schedule) {
let tournamentStatus = scheduleStatus
if state == .running || state == .finished {
TournamentBroadcastRowView(tournament: tournament)
}
if state == .running || state == .finished {
NavigationLink(value: Screen.cashier) {
let tournamentStatus = cashierStatus
LabeledContent {
if let tournamentStatus {
Text(tournamentStatus.completion)
@ -80,7 +86,7 @@ struct TournamentBuildView: View {
ProgressView()
}
} label: {
Text("Horaires")
Text("Encaissement")
if let tournamentStatus {
Text(tournamentStatus.label).lineLimit(1)
} else {
@ -89,11 +95,13 @@ struct TournamentBuildView: View {
}
}
.task {
scheduleStatus = await tournament.scheduleStatus()
cashierStatus = await tournament.cashierStatus()
}
NavigationLink(value: Screen.call) {
let tournamentStatus = callStatus
}
if state != .finished {
NavigationLink(value: Screen.schedule) {
let tournamentStatus = scheduleStatus
LabeledContent {
if let tournamentStatus {
Text(tournamentStatus.completion)
@ -101,7 +109,7 @@ struct TournamentBuildView: View {
ProgressView()
}
} label: {
Text("Convocations")
Text("Horaires")
if let tournamentStatus {
Text(tournamentStatus.label).lineLimit(1)
} else {
@ -110,13 +118,11 @@ struct TournamentBuildView: View {
}
}
.task {
callStatus = await tournament.callStatus()
scheduleStatus = await tournament.scheduleStatus()
}
}
if tournament.state() == .running || tournament.state() == .finished {
NavigationLink(value: Screen.cashier) {
let tournamentStatus = cashierStatus
NavigationLink(value: Screen.call) {
let tournamentStatus = callStatus
LabeledContent {
if let tournamentStatus {
Text(tournamentStatus.completion)
@ -124,7 +130,7 @@ struct TournamentBuildView: View {
ProgressView()
}
} label: {
Text("Encaissement")
Text("Convocations")
if let tournamentStatus {
Text(tournamentStatus.label).lineLimit(1)
} else {
@ -133,9 +139,14 @@ struct TournamentBuildView: View {
}
}
.task {
cashierStatus = await tournament.cashierStatus()
callStatus = await tournament.callStatus()
}
}
if state == .running || state == .finished {
TournamentInscriptionView(tournament: tournament)
}
}
}
}

@ -42,25 +42,7 @@ struct TournamentInitView: View {
}
}
NavigationLink(value: Screen.broadcast) {
LabeledContent {
if Store.main.userId == nil {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.logoRed)
} else {
if tournament.isPrivate {
Text("tournoi privé").foregroundStyle(.logoRed)
} else {
Text("Automatique")
}
}
} label: {
Text("Publication")
if Store.main.userId == nil {
Text("Un compte Padel Club est nécessaire")
}
}
}
TournamentBroadcastRowView(tournament: tournament)
NavigationLink(value: Screen.print) {
Text("Imprimer")

@ -14,23 +14,21 @@ struct TournamentInscriptionView: View {
@ViewBuilder
var body: some View {
Section {
NavigationLink(value: Screen.inscription) {
LabeledContent {
Text(tournament.unsortedTeamsWithoutWO().count.formatted() + "/" + tournament.teamCount.formatted())
} label: {
Text("Gestion des inscriptions")
if let closedRegistrationDate = tournament.closedRegistrationDate {
Text("clôturé le " + closedRegistrationDate.formatted(date: .abbreviated, time: .shortened))
}
NavigationLink(value: Screen.inscription) {
LabeledContent {
Text(tournament.unsortedTeamsWithoutWO().count.formatted() + "/" + tournament.teamCount.formatted())
} label: {
Text("Gestion des inscriptions")
if let closedRegistrationDate = tournament.closedRegistrationDate {
Text("clôturé le " + closedRegistrationDate.formatted(date: .abbreviated, time: .shortened))
}
}
if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false {
LabeledContent {
Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened))
} label: {
Text("Date limite")
}
}
if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false {
LabeledContent {
Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened))
} label: {
Text("Date limite")
}
}
}

@ -56,14 +56,20 @@ struct TournamentView: View {
}
}
case .initial:
TournamentInscriptionView(tournament: tournament)
Section {
TournamentInscriptionView(tournament: tournament)
}
TournamentInitView(tournament: tournament)
case .build:
TournamentInscriptionView(tournament: tournament)
Section {
TournamentInscriptionView(tournament: tournament)
}
TournamentInitView(tournament: tournament)
TournamentBuildView(tournament: tournament)
case .running, .finished:
TournamentInscriptionView(tournament: tournament)
case .running:
TournamentRunningView(tournament: tournament)
TournamentBuildView(tournament: tournament)
case .finished:
TournamentBuildView(tournament: tournament)
TournamentRunningView(tournament: tournament)
}

@ -11,6 +11,7 @@ import LeStorage
struct LoginView: View {
@EnvironmentObject var dataStore: DataStore
@EnvironmentObject var networkMonitor: NetworkMonitor
@State var username: String = ""
@State var password: String = ""
@ -20,6 +21,18 @@ struct LoginView: View {
@State var errorText: String? = nil
@State var showSubscriptionView: Bool = false
var loginFailed: Binding<Bool> {
Binding {
errorText != nil
} set: { newValue in
if newValue == false {
errorText = nil
}
}
}
var showEmailValidationMessage: Bool = false
var handler: (User) -> ()
@ -41,26 +54,12 @@ struct LoginView: View {
}
Section {
Button(action: {
self._login()
}, label: {
if self.isLoading {
HStack {
Spacer()
ProgressView()
Spacer()
}
} else {
Text("Connexion")
.buttonStyle(.borderedProminent)
.tint(.orange)
.listRowBackground(Color.clear)
.frame(maxWidth: .infinity)
}
})
if let error = self.errorText {
Text(error).font(.callout).foregroundStyle(.red)
RowButtonView("Connexion") {
await self._login()
}
// if let error = self.errorText {
// Text(error).font(.callout).foregroundStyle(.red)
// }
}
if !self.showEmailValidationMessage {
@ -86,6 +85,13 @@ struct LoginView: View {
}
}
.alert("Un problème est survenu", isPresented: loginFailed) {
Button("OK") {
}
} message: {
let message = [networkMonitor.connected == false ? "L'appareil n'est pas connecté à internet." as String? : nil, errorText].compacted().joined(separator: "\n")
Text(message)
}
.navigationTitle("Connexion")
.onAppear {
#if DEBUG
@ -99,29 +105,27 @@ struct LoginView: View {
}
}
fileprivate func _login() {
fileprivate func _login() async {
self.errorText = nil // reset error
self.isLoading = true
Task {
do {
let service = try Store.main.service()
let user: User = try await service.login(
username: self.username,
password: self.password)
self.dataStore.user = user
self.isLoading = false
self.handler(user)
} catch {
self.isLoading = false
switch error {
case ServiceError.responseError(let reason):
self.errorText = reason
default:
self.errorText = error.localizedDescription
}
Logger.error(error)
do {
let service = try Store.main.service()
let user: User = try await service.login(
username: self.username,
password: self.password)
self.dataStore.user = user
self.isLoading = false
self.handler(user)
} catch {
self.isLoading = false
switch error {
case ServiceError.responseError(let reason):
self.errorText = reason
default:
self.errorText = error.localizedDescription
}
Logger.error(error)
}
}

Loading…
Cancel
Save