From f13a71675dd21daf34d532e64ec313dabeca1340 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Thu, 18 Apr 2024 09:44:08 +0200 Subject: [PATCH] enhance tournament settings screen --- PadelClub.xcodeproj/project.pbxproj | 12 ++ PadelClub/Data/GroupStage.swift | 4 +- PadelClub/Data/Round.swift | 4 +- PadelClub/ViewModel/AgendaDestination.swift | 2 +- PadelClub/ViewModel/Selectable.swift | 31 +++- .../GenericDestinationPickerView.swift | 6 +- .../Views/GroupStage/GroupStagesView.swift | 2 +- .../TournamentClubSettingsView.swift | 68 ++++++++ .../TournamentGeneralSettingsView.swift | 78 +++++++++ .../TournamentMatchFormatsSettingsView.swift | 20 +++ .../Screen/TournamentCallView.swift | 6 +- .../Screen/TournamentCashierView.swift | 8 +- .../Screen/TournamentScheduleView.swift | 2 +- .../Screen/TournamentSettingsView.swift | 153 +++++++----------- 14 files changed, 280 insertions(+), 116 deletions(-) create mode 100644 PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift create mode 100644 PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift create mode 100644 PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index ac7b61c..cc5fbe0 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -39,6 +39,9 @@ FF025ADB2BD0C2D000A86CF8 /* MatchTeamDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */; }; FF025ADD2BD0C94300A86CF8 /* FooterButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */; }; FF025ADF2BD0CE0A00A86CF8 /* TeamWeightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADE2BD0CE0A00A86CF8 /* TeamWeightView.swift */; }; + FF025AE12BD0EB9000A86CF8 /* TournamentClubSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */; }; + FF025AE32BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */; }; + FF025AE52BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */; }; FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB32BB0020000F0AEC7 /* PlayerSexPickerView.swift */; }; FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */; }; FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */; }; @@ -321,6 +324,9 @@ FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchTeamDetailView.swift; sourceTree = ""; }; FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterButtonView.swift; sourceTree = ""; }; FF025ADE2BD0CE0A00A86CF8 /* TeamWeightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamWeightView.swift; sourceTree = ""; }; + FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentClubSettingsView.swift; sourceTree = ""; }; + FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentMatchFormatsSettingsView.swift; sourceTree = ""; }; + FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentGeneralSettingsView.swift; sourceTree = ""; }; FF089EB32BB0020000F0AEC7 /* PlayerSexPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSexPickerView.swift; sourceTree = ""; }; FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRowView.swift; sourceTree = ""; }; FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerPopoverView.swift; sourceTree = ""; }; @@ -1006,6 +1012,9 @@ FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */, FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */, FF5D0D772BB42C5B005CB568 /* InscriptionInfoView.swift */, + FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */, + FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */, + FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */, ); path = Components; sourceTree = ""; @@ -1374,6 +1383,7 @@ FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */, FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */, FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */, + FF025AE12BD0EB9000A86CF8 /* TournamentClubSettingsView.swift in Sources */, FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */, FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */, FF025ADF2BD0CE0A00A86CF8 /* TeamWeightView.swift in Sources */, @@ -1460,6 +1470,7 @@ FF6EC90B2B947AC000EA7F5A /* Array+Extensions.swift in Sources */, FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */, FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */, + FF025AE52BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift in Sources */, FFC2DCB22BBE75D40046DB9F /* LoserBracketView.swift in Sources */, FF9267FC2BCE84870080F940 /* PlayerPayView.swift in Sources */, FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */, @@ -1475,6 +1486,7 @@ FF5D0D8B2BB4D1E3005CB568 /* CalendarView.swift in Sources */, FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */, FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */, + FF025AE32BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift in Sources */, FF11628A2BD05247000C4809 /* DateUpdateManagerView.swift in Sources */, FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */, FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 1f3e144..7378361 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -281,7 +281,7 @@ extension GroupStage: Selectable { runningMatches().count } - func badgeImage() -> String? { - hasEnded() ? "checkmark.circle.fill" : nil + func badgeImage() -> Badge? { + hasEnded() ? .checkmark : nil } } diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 0380d30..823e143 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -419,7 +419,7 @@ extension Round: Selectable { } } - func badgeImage() -> String? { - hasEnded() ? "checkmark.circle.fill" : nil + func badgeImage() -> Badge? { + hasEnded() ? .checkmark : nil } } diff --git a/PadelClub/ViewModel/AgendaDestination.swift b/PadelClub/ViewModel/AgendaDestination.swift index 2f05f66..4070cc8 100644 --- a/PadelClub/ViewModel/AgendaDestination.swift +++ b/PadelClub/ViewModel/AgendaDestination.swift @@ -56,7 +56,7 @@ enum AgendaDestination: CaseIterable, Identifiable, Selectable { } } - func badgeImage() -> String? { + func badgeImage() -> Badge? { nil } } diff --git a/PadelClub/ViewModel/Selectable.swift b/PadelClub/ViewModel/Selectable.swift index 0f656cf..7158823 100644 --- a/PadelClub/ViewModel/Selectable.swift +++ b/PadelClub/ViewModel/Selectable.swift @@ -6,9 +6,38 @@ // import Foundation +import SwiftUI protocol Selectable { func selectionLabel() -> String func badgeValue() -> Int? - func badgeImage() -> String? + func badgeImage() -> Badge? +} + +enum Badge { + case checkmark + case xmark + case custom(systemName: String, color: Color) + + func systemName() -> String { + switch self { + case .checkmark: + return "checkmark.circle.fill" + case .xmark: + return "xmark.circle.fill" + case .custom(let systemName, _): + return systemName + } + } + + func color() -> Color { + switch self { + case .checkmark: + .green + case .xmark: + .red + case .custom(_, let color): + color + } + } } diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index 13c0adf..4531718 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -45,9 +45,9 @@ struct GenericDestinationPickerView: View { } .buttonStyle(.plain) .overlay(alignment: .bottomTrailing) { - if let image = destination.badgeImage() { - Image(systemName: image) - .foregroundColor(.green) + if let badge = destination.badgeImage() { + Image(systemName: badge.systemName()) + .foregroundColor(badge.color()) .imageScale(.medium) .background ( Color(.systemBackground) diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 7dd0d07..7cd5f0e 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -42,7 +42,7 @@ struct GroupStagesView: View { } } - func badgeImage() -> String? { + func badgeImage() -> Badge? { nil } } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift new file mode 100644 index 0000000..3ac8a52 --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift @@ -0,0 +1,68 @@ +// +// TournamentClubSettingsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import SwiftUI + +struct TournamentClubSettingsView: View { + @Environment(Tournament.self) private var tournament: Tournament + @EnvironmentObject var dataStore: DataStore + + var body: some View { + @Bindable var tournament = tournament + List { + let event = tournament.eventObject + let selectedClub = event?.clubObject + Section { + if let selectedClub { + NavigationLink { + ClubDetailView(club: selectedClub, displayContext: .edition) + } label: { + ClubRowView(club: selectedClub) + } + } else { + NavigationLink { + ClubsView() { club in + if let event { + event.club = club.id + try? dataStore.events.addOrUpdate(instance: event) + } else { + let event = Event(club: club.id) + tournament.event = event.id + try? dataStore.events.addOrUpdate(instance: event) + } + } + } label: { + Text("Choisir un club") + } + } + } header: { + Text("Lieu du tournoi") + } footer: { + if let event, selectedClub != nil { + HStack { + Spacer() + Button("modifier", role: .destructive) { + event.club = nil + try? dataStore.events.addOrUpdate(instance: event) + } + } + } + } + + Section { + TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount, max: 100) + } + } + .onDisappear { + try? dataStore.tournaments.addOrUpdate(instance: tournament) + } + } +} + +#Preview { + TournamentClubSettingsView() +} diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift new file mode 100644 index 0000000..094596a --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -0,0 +1,78 @@ +// +// TournamentGeneralSettingsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import SwiftUI + +struct TournamentGeneralSettingsView: View { + @Environment(Tournament.self) private var tournament: Tournament + @EnvironmentObject var dataStore: DataStore + + @State private var tournamentName: String = "" + @FocusState private var textFieldIsFocus: Bool + + var body: some View { + @Bindable var tournament = tournament + Form { + Section { + TournamentDatePickerView() + TournamentDurationManagerView() + } + + Section { + TournamentLevelPickerView() + } + + Section { + LabeledContent { + TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $tournament.entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR")) + .keyboardType(.decimalPad) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + } label: { + Text("Inscription") + } + } + + Section { + LabeledContent { + TextField("Nom", text: $tournamentName) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + .keyboardType(.alphabet) + .autocorrectionDisabled() + .onSubmit { + if tournamentName.trimmed.isEmpty { + tournament.name = nil + } else { + tournament.name = tournamentName + } + } + } label: { + Text("Nom du tournoi") + } + } + } + .focused($textFieldIsFocus) + .scrollDismissesKeyboard(.immediately) + .navigationTitle("Réglages") + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .keyboard) { + Button("Valider") { + textFieldIsFocus = false + } + } + } + .onDisappear { + try? dataStore.tournaments.addOrUpdate(instance: tournament) + } + } +} + +#Preview { + TournamentGeneralSettingsView() +} diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift new file mode 100644 index 0000000..c477c54 --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift @@ -0,0 +1,20 @@ +// +// TournamentMatchFormatsSettingsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import SwiftUI + +struct TournamentMatchFormatsSettingsView: View { + var body: some View { + List { + TournamentFormatSelectionView() + } + } +} + +#Preview { + TournamentMatchFormatsSettingsView() +} diff --git a/PadelClub/Views/Tournament/Screen/TournamentCallView.swift b/PadelClub/Views/Tournament/Screen/TournamentCallView.swift index af1e0d3..8a8dfa3 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCallView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCallView.swift @@ -40,14 +40,14 @@ enum CallDestination: Identifiable, Selectable { } } - func badgeImage() -> String? { + func badgeImage() -> Badge? { switch self { case .seeds(let tournament): let allSeedCalled = tournament.seeds().allSatisfy({ tournament.isStartDateIsDifferentThanCallDate($0) == false }) - return allSeedCalled ? "checkmark.circle.fill" : nil + return allSeedCalled ? .checkmark : nil case .groupStages(let tournament): let allSeedCalled = tournament.groupStageTeams().allSatisfy({ tournament.isStartDateIsDifferentThanCallDate($0) == false }) - return allSeedCalled ? "checkmark.circle.fill" : nil + return allSeedCalled ? .checkmark : nil } } diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index 1de11e8..4b1e91f 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -50,16 +50,16 @@ enum CashierDestination: Identifiable, Selectable { } } - func badgeImage() -> String? { + func badgeImage() -> Badge? { switch self { case .summary: return nil case .groupStage(let groupStage): - return groupStage.unsortedPlayers().allSatisfy({ $0.hasPaid() }) ? "checkmark.circle.fill" : nil + return groupStage.unsortedPlayers().allSatisfy({ $0.hasPaid() }) ? .checkmark : nil case .bracket(let round): - return round.seeds().flatMap { $0.unsortedPlayers() }.allSatisfy({ $0.hasPaid() }) ? "checkmark.circle.fill" : nil + return round.seeds().flatMap { $0.unsortedPlayers() }.allSatisfy({ $0.hasPaid() }) ? .checkmark : nil case .all(let tournament): - return tournament.selectedPlayers().allSatisfy({ $0.hasPaid() }) ? "checkmark.circle.fill" : nil + return tournament.selectedPlayers().allSatisfy({ $0.hasPaid() }) ? .checkmark : nil } } diff --git a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift index fccb5a2..82bc8cb 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift @@ -42,7 +42,7 @@ enum ScheduleDestination: String, Identifiable, Selectable { nil } - func badgeImage() -> String? { + func badgeImage() -> Badge? { nil } diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift index b05d84b..a984d14 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -7,112 +7,69 @@ import SwiftUI -struct TournamentSettingsView: View { - @Environment(Tournament.self) private var tournament: Tournament - @EnvironmentObject var dataStore: DataStore - - @State private var tournamentName: String = "" - @FocusState private var textFieldIsFocus: Bool - - var body: some View { - @Bindable var tournament = tournament - Form { - LabeledContent { - TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $tournament.entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR")) - .keyboardType(.decimalPad) - .multilineTextAlignment(.trailing) - .frame(maxWidth: .infinity) - } label: { - Text("Inscription") - } - - LabeledContent { - TextField("Nom", text: $tournamentName) - .multilineTextAlignment(.trailing) - .frame(maxWidth: .infinity) - .keyboardType(.alphabet) - .autocorrectionDisabled() - .onSubmit { - if tournamentName.trimmed.isEmpty { - tournament.name = nil - } else { - tournament.name = tournamentName - } - } - } label: { - Text("Nom du tournoi") - } - - TournamentLevelPickerView() - TournamentDurationManagerView() - TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount, max: 100) - TournamentDatePickerView() - +enum TournamentSettings: Identifiable, Selectable { + case general + case club(Tournament) + case matchFormats - let event = tournament.eventObject - let selectedClub = event?.clubObject - Section { - if let selectedClub { - NavigationLink { - ClubDetailView(club: selectedClub, displayContext: .edition) - } label: { - ClubRowView(club: selectedClub) - } - } else { - NavigationLink { - ClubsView() { club in - if let event { - event.club = club.id - try? dataStore.events.addOrUpdate(instance: event) - } else { - let event = Event(club: club.id) - tournament.event = event.id - try? dataStore.events.addOrUpdate(instance: event) - } - } - } label: { - Text("Choisir un club") - } - } - } header: { - Text("Lieu du tournoi") - } footer: { - if let event, selectedClub != nil { - HStack { - Spacer() - Button("modifier", role: .destructive) { - event.club = nil - try? dataStore.events.addOrUpdate(instance: event) - } - } - } - } - - TournamentFormatSelectionView() + var id: String { String(describing: self) } + func selectionLabel() -> String { + switch self { + case .matchFormats: + return "Formats de jeu" + case .general: + return "Général" + case .club: + return "Club" } - .focused($textFieldIsFocus) - .scrollDismissesKeyboard(.immediately) - .navigationTitle("Réglages") - .toolbarBackground(.visible, for: .navigationBar) - .toolbar { - ToolbarItem(placement: .keyboard) { - Button("Valider") { - textFieldIsFocus = false - } + } + + func badgeValue() -> Int? { + nil + } + + func badgeImage() -> Badge? { + switch self { + case .club(let tournament): + if tournament.club() != nil { + return .checkmark + } else { + return .xmark } + default: + return nil } - .onDisappear { - try? dataStore.tournaments.addOrUpdate(instance: tournament) + } +} + +struct TournamentSettingsView: View { + @State private var selectedDestination: TournamentSettings? = .general + @Environment(Tournament.self) var tournament: Tournament + + private func destinations() -> [TournamentSettings] { + [.general, .club(tournament), .matchFormats] + } + + var body: some View { + VStack(spacing: 0) { + GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: destinations(), nilDestinationIsValid: false) + switch selectedDestination! { + case .matchFormats: + TournamentMatchFormatsSettingsView() + case .general: + TournamentGeneralSettingsView() + case .club: + TournamentClubSettingsView() + } } + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + .navigationTitle("Réglages") } + } #Preview { - Group { - - TournamentSettingsView() - .environmentObject(DataStore.shared) - .environment(Tournament.mock()) - } + TournamentSettingsView() }