diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index cc5fbe0..a5b55a7 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4002B6D249D002A7B48 /* PadelClubApp.swift */; }; - C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4022B6D249D002A7B48 /* ContentView.swift */; }; C425D4052B6D249E002A7B48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4042B6D249E002A7B48 /* Assets.xcassets */; }; C425D4082B6D249E002A7B48 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4072B6D249E002A7B48 /* Preview Assets.xcassets */; }; C425D4122B6D249E002A7B48 /* PadelClubTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4112B6D249E002A7B48 /* PadelClubTests.swift */; }; @@ -20,7 +19,6 @@ C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D592B6D383C00ADC637 /* Tournament.swift */; }; C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */; }; C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D622B6D3D6500ADC637 /* Club.swift */; }; - C4A47D742B72881F00ADC637 /* ClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D732B72881F00ADC637 /* ClubView.swift */; }; C4A47D772B73789100ADC637 /* TournamentV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D762B73789100ADC637 /* TournamentV1.swift */; }; C4A47D7B2B73C0F900ADC637 /* TournamentV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D7A2B73C0F900ADC637 /* TournamentV2.swift */; }; C4A47D7D2B73CDC300ADC637 /* ClubV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D7C2B73CDC300ADC637 /* ClubV1.swift */; }; @@ -42,6 +40,11 @@ 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 */; }; + FF025AE72BD1111000A86CF8 /* GlobalSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */; }; + FF025AE92BD1307F00A86CF8 /* MonthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE82BD1307E00A86CF8 /* MonthData.swift */; }; + FF025AED2BD1513700A86CF8 /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AEC2BD1513700A86CF8 /* AppScreen.swift */; }; + FF025AEF2BD1AE9400A86CF8 /* DurationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */; }; + FF025AF12BD1AEBD00A86CF8 /* MatchFormatStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.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 */; }; @@ -290,7 +293,6 @@ /* Begin PBXFileReference section */ C425D3FD2B6D249D002A7B48 /* PadelClub.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PadelClub.app; sourceTree = BUILT_PRODUCTS_DIR; }; C425D4002B6D249D002A7B48 /* PadelClubApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubApp.swift; sourceTree = ""; }; - C425D4022B6D249D002A7B48 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; C425D4042B6D249E002A7B48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C425D4072B6D249E002A7B48 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; C425D40D2B6D249E002A7B48 /* PadelClubTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PadelClubTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -305,7 +307,6 @@ C4A47D592B6D383C00ADC637 /* Tournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tournament.swift; sourceTree = ""; }; C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = ""; }; C4A47D622B6D3D6500ADC637 /* Club.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Club.swift; sourceTree = ""; }; - C4A47D732B72881F00ADC637 /* ClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubView.swift; sourceTree = ""; }; C4A47D762B73789100ADC637 /* TournamentV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentV1.swift; sourceTree = ""; }; C4A47D7A2B73C0F900ADC637 /* TournamentV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentV2.swift; sourceTree = ""; }; C4A47D7C2B73CDC300ADC637 /* ClubV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubV1.swift; sourceTree = ""; }; @@ -327,6 +328,11 @@ 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 = ""; }; + FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSettingsView.swift; sourceTree = ""; }; + FF025AE82BD1307E00A86CF8 /* MonthData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthData.swift; sourceTree = ""; }; + FF025AEC2BD1513700A86CF8 /* AppScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppScreen.swift; sourceTree = ""; }; + FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DurationSettingsView.swift; sourceTree = ""; }; + FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchFormatStorageView.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 = ""; }; @@ -650,6 +656,7 @@ FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */, C4A47D622B6D3D6500ADC637 /* Club.swift */, FF8F263E2BAD7D5C00650388 /* Event.swift */, + FF025AE82BD1307E00A86CF8 /* MonthData.swift */, FF1DC5522BAB354A00FD8220 /* MockData.swift */, FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */, FF6EC9012B94799200EA7F5A /* Coredata */, @@ -661,8 +668,6 @@ C4A47D722B72881500ADC637 /* Views */ = { isa = PBXGroup; children = ( - C425D4022B6D249D002A7B48 /* ContentView.swift */, - C4A47D732B72881F00ADC637 /* ClubView.swift */, FF39719B2B8DE04B004C4E75 /* Navigation */, FF8F26392BAD526A00650388 /* Event */, FF1DC54D2BAB34FA00FD8220 /* Club */, @@ -920,6 +925,9 @@ children = ( FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */, FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */, + FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */, + FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */, + FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */, ); path = Toolbox; sourceTree = ""; @@ -936,6 +944,7 @@ isa = PBXGroup; children = ( FF7091652B90F0B000AB08DA /* TabDestination.swift */, + FF025AEC2BD1513700A86CF8 /* AppScreen.swift */, FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */, FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */, FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */, @@ -1414,6 +1423,8 @@ FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */, FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */, FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */, + FF025AEF2BD1AE9400A86CF8 /* DurationSettingsView.swift in Sources */, + FF025AED2BD1513700A86CF8 /* AppScreen.swift in Sources */, FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */, FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */, FF92680B2BCEE3E10080F940 /* ContactManager.swift in Sources */, @@ -1426,7 +1437,6 @@ C44B79112BBDA63A00906534 /* Locale+Extensions.swift in Sources */, FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */, FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */, - C4A47D742B72881F00ADC637 /* ClubView.swift in Sources */, C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */, FF11627F2BCF9432000C4809 /* PlayerListView.swift in Sources */, FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */, @@ -1489,11 +1499,11 @@ FF025AE32BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift in Sources */, FF11628A2BD05247000C4809 /* DateUpdateManagerView.swift in Sources */, FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */, + FF025AE92BD1307F00A86CF8 /* MonthData.swift in Sources */, FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */, FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */, FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */, FF9268072BCE94D90080F940 /* TournamentCallView.swift in Sources */, - C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */, FFC2DCB42BBE9ECD0046DB9F /* LoserRoundsView.swift in Sources */, FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */, FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */, @@ -1506,6 +1516,7 @@ FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */, FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */, FF0E0B6D2BC254C6005F00A9 /* TournamentScheduleView.swift in Sources */, + FF025AF12BD1AEBD00A86CF8 /* MatchFormatStorageView.swift in Sources */, FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */, C4A47D772B73789100ADC637 /* TournamentV1.swift in Sources */, C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */, @@ -1548,6 +1559,7 @@ FF5D0D722BB3EFA5005CB568 /* LearnMoreSheetView.swift in Sources */, FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */, FF0EC5222BB173E70056B6D1 /* UpdateSourceRankDateView.swift in Sources */, + FF025AE72BD1111000A86CF8 /* GlobalSettingsView.swift in Sources */, C4A47D912B7BBBEC00ADC637 /* Guard.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/PadelClub/Data/AppSettings.swift b/PadelClub/Data/AppSettings.swift index 864ea23..48909e9 100644 --- a/PadelClub/Data/AppSettings.swift +++ b/PadelClub/Data/AppSettings.swift @@ -7,14 +7,48 @@ import Foundation import LeStorage +import SwiftUI +@Observable class AppSettings: MicroStorable { static var fileName: String { "appsettings.json" } + + var lastDataSource: String? = nil + var callMessageBody : String? = nil + var callMessageSignature: String? = nil + var callDisplayFormat: Bool = false + var callDisplayEntryFee: Bool = false + var callUseFullCustomMessage: Bool = false + var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil + var bracketMatchFormatPreference: Int? + var groupStageMatchFormatPreference: Int? + var loserBracketMatchFormatPreference: Int? required init() { } -// var id: String = Store.randomId() + func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) { + if estimatedDuration == matchFormat.defaultEstimatedDuration { + matchFormatsDefaultDuration?.removeValue(forKey: matchFormat) + } else { + matchFormatsDefaultDuration = matchFormatsDefaultDuration ?? [MatchFormat: Int]() + matchFormatsDefaultDuration?[matchFormat] = estimatedDuration + } + } + + enum CodingKeys: String, CodingKey { + case _lastDataSource = "lastDataSource" + case _callMessageBody = "callMessageBody" + case _callMessageSignature = "callMessageSignature" + case _callDisplayFormat = "callDisplayFormat" + case _callDisplayEntryFee = "callDisplayEntryFee" + case _callUseFullCustomMessage = "callUseFullCustomMessage" + case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration" + case _bracketMatchFormatPreference = "bracketMatchFormatPreference" + case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" + case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" + + } } diff --git a/PadelClub/Data/Club.swift b/PadelClub/Data/Club.swift index 7596ec8..aca7233 100644 --- a/PadelClub/Data/Club.swift +++ b/PadelClub/Data/Club.swift @@ -32,7 +32,9 @@ class Club : ModelObject, Storable, Hashable { var zipCode: String? var latitude: Double? var longitude: Double? - + var courtCount: Int? + var courtNames: [String]? = nil + internal init(name: String, acronym: String? = nil, phone: String? = nil, code: String? = nil, address: String? = nil, city: String? = nil, zipCode: String? = nil, latitude: Double? = nil, longitude: Double? = nil) { self.name = name self.acronym = acronym ?? name.acronym() @@ -45,12 +47,7 @@ class Club : ModelObject, Storable, Hashable { self.longitude = longitude } - var tournaments: [Tournament] { - return [] - } - override func deleteDependencies() throws { - try Store.main.deleteDependencies(items: self.tournaments) } enum CodingKeys: String, CodingKey { @@ -64,6 +61,8 @@ class Club : ModelObject, Storable, Hashable { case _zipCode = "zipCode" case _latitude = "latitude" case _longitude = "longitude" + case _courtCount = "courtCount" + case _courtNames = "courtNames" } } diff --git a/PadelClub/Data/DataStore.swift b/PadelClub/Data/DataStore.swift index f89dd00..f917d67 100644 --- a/PadelClub/Data/DataStore.swift +++ b/PadelClub/Data/DataStore.swift @@ -24,8 +24,29 @@ class DataStore: ObservableObject { fileprivate(set) var playerRegistrations: StoredCollection fileprivate(set) var rounds: StoredCollection fileprivate(set) var teamScores: StoredCollection + fileprivate(set) var monthData: StoredCollection fileprivate var _userStorage: OptionalStorage = OptionalStorage(fileName: "user.json") + fileprivate var _appSettingsStorage: MicroStorage = MicroStorage() + + var appSettings: AppSettings { + _appSettingsStorage.item + } + + func updateSettings() { + _appSettingsStorage.update { settings in + settings.lastDataSource = appSettings.lastDataSource + settings.callMessageBody = appSettings.callMessageBody + settings.callDisplayFormat = appSettings.callDisplayFormat + settings.callMessageSignature = appSettings.callMessageSignature + settings.callDisplayEntryFee = appSettings.callDisplayEntryFee + settings.callUseFullCustomMessage = appSettings.callUseFullCustomMessage + settings.matchFormatsDefaultDuration = appSettings.matchFormatsDefaultDuration + settings.bracketMatchFormatPreference = appSettings.bracketMatchFormatPreference + settings.groupStageMatchFormatPreference = appSettings.groupStageMatchFormatPreference + settings.loserBracketMatchFormatPreference = appSettings.loserBracketMatchFormatPreference + } + } var user: User? { return self._userStorage.item @@ -44,16 +65,18 @@ class DataStore: ObservableObject { // store.addMigration(Migration(version: 2)) // store.addMigration(Migration(version: 3)) - self.clubs = store.registerCollection(synchronized: false, indexed: true) - self.tournaments = store.registerCollection(synchronized: false, indexed: true) - self.events = store.registerCollection(synchronized: false, indexed: true) - self.groupStages = store.registerCollection(synchronized: false, indexed: true) - self.teamScores = store.registerCollection(synchronized: false, indexed: true) - self.teamRegistrations = store.registerCollection(synchronized: false, indexed: true) - self.playerRegistrations = store.registerCollection(synchronized: false, indexed: true) - self.rounds = store.registerCollection(synchronized: false, indexed: true) - self.matches = store.registerCollection(synchronized: false, indexed: true) - + let indexed : Bool = false + self.clubs = store.registerCollection(synchronized: false, indexed: indexed) + self.tournaments = store.registerCollection(synchronized: false, indexed: indexed) + self.events = store.registerCollection(synchronized: false, indexed: indexed) + self.groupStages = store.registerCollection(synchronized: false, indexed: indexed) + self.teamScores = store.registerCollection(synchronized: false, indexed: indexed) + self.teamRegistrations = store.registerCollection(synchronized: false, indexed: indexed) + self.playerRegistrations = store.registerCollection(synchronized: false, indexed: indexed) + self.rounds = store.registerCollection(synchronized: false, indexed: indexed) + self.matches = store.registerCollection(synchronized: false, indexed: indexed) + self.monthData = store.registerCollection(synchronized: false, indexed: indexed) + NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidLoad, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidChange, object: nil) } diff --git a/PadelClub/Data/Event.swift b/PadelClub/Data/Event.swift index 07cee7a..c3d7a23 100644 --- a/PadelClub/Data/Event.swift +++ b/PadelClub/Data/Event.swift @@ -22,6 +22,7 @@ class Event: ModelObject, Storable { var groupStageFormat: Int? var roundFormat: Int? var loserRoundFormat: Int? + //var timeslots ? internal init(club: String? = nil, name: String? = nil, courtCount: Int? = nil, tenupId: String? = nil, groupStageFormat: Int? = nil, roundFormat: Int? = nil, loserRoundFormat: Int? = nil) { self.club = club diff --git a/PadelClub/Data/Federal/FederalTournament.swift b/PadelClub/Data/Federal/FederalTournament.swift index 7e29cd4..c9391ec 100644 --- a/PadelClub/Data/Federal/FederalTournament.swift +++ b/PadelClub/Data/Federal/FederalTournament.swift @@ -203,6 +203,7 @@ enum TypePratique: String, Codable { case beach = "BEACH" case padel = "PADEL" case tennis = "TENNIS" + case pickle = "PICKLE" } // MARK: - CategorieTournoi diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 7378361..e17e819 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -19,6 +19,7 @@ class GroupStage: ModelObject, Storable { var size: Int var format: Int? var startDate: Date? + var name: String? var matchFormat: MatchFormat { get { @@ -255,6 +256,15 @@ class GroupStage: ModelObject, Storable { return unsortedTeams().sorted(by: \TeamRegistration.groupStagePosition!) } } + + func updateMatchFormat(_ matchFormat: MatchFormat) { + self.matchFormat = matchFormat + let playedMatches = playedMatches() + playedMatches.forEach { match in + match.matchFormat = matchFormat + } + try? DataStore.shared.matches.addOrUpdate(contentOfs: playedMatches) + } override func deleteDependencies() throws { try Store.main.deleteDependencies(items: self._matches()) @@ -269,6 +279,7 @@ extension GroupStage { case _size = "size" case _format = "format" case _startDate = "startDate" + case _name = "name" } } diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index ccd4c18..f70800a 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -27,6 +27,7 @@ class Match: ModelObject, Storable { var name: String? var order: Int var disabled: Bool = false + var courtIndex: Int? internal init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, matchFormat: MatchFormat? = nil, court: String? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil, broadcasted: Bool = false, name: String? = nil, order: Int = 0) { self.round = round @@ -100,8 +101,8 @@ class Match: ModelObject, Storable { return index * 2 + teamPosition.rawValue == bracketPosition } - func estimatedEndDate() -> Date? { - let minutesToAdd = Double(matchFormat.estimatedDuration) + func estimatedEndDate(_ additionalEstimationDuration: Int) -> Date? { + let minutesToAdd = Double(matchFormat.getEstimatedDuration(additionalEstimationDuration)) return startDate?.addingTimeInterval(minutesToAdd * 60.0) } @@ -315,7 +316,7 @@ class Match: ModelObject, Storable { } } - func courtIndex() -> Int? { + func getCourtIndex() -> Int? { guard let court else { return nil } if let courtIndex = Int(court) { return courtIndex - 1 } return nil @@ -536,6 +537,7 @@ class Match: ModelObject, Storable { case _index = "index" case _format = "format" case _court = "court" + case _courtIndex = "courtIndex" case _servingTeamId = "servingTeamId" case _winningTeamId = "winningTeamId" case _losingTeamId = "losingTeamId" diff --git a/PadelClub/Data/MockData.swift b/PadelClub/Data/MockData.swift index 10bcd26..064e781 100644 --- a/PadelClub/Data/MockData.swift +++ b/PadelClub/Data/MockData.swift @@ -35,17 +35,12 @@ extension Tournament { } static func newEmptyInstance() -> Tournament { - let lastDataSource: String? = UserDefaults.standard.string(forKey: "lastDataSource") - let lastDataSourceMaleUnranked: Int = UserDefaults.standard.integer(forKey: "lastDataSourceMaleUnranked") - let lastDataSourceFemaleUnranked: Int = UserDefaults.standard.integer(forKey: "lastDataSourceFemaleUnranked") - + let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource var _mostRecentDateAvailable: Date? { guard let lastDataSource else { return nil } return URL.importDateFormatter.date(from: lastDataSource) } - let maleUnrankedValue : Int? = lastDataSourceMaleUnranked == 0 ? nil : lastDataSourceMaleUnranked - let femaleUnrankedValue : Int? = lastDataSourceFemaleUnranked == 0 ? nil : lastDataSourceMaleUnranked let rankSourceDate = _mostRecentDateAvailable //todo @@ -55,7 +50,7 @@ extension Tournament { tournament.federalTournamentAge = FederalTournamentAge.mostUsed(tournaments: tournaments) */ - return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior, maleUnrankedValue: maleUnrankedValue, femaleUnrankedValue: femaleUnrankedValue) + return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior) } } diff --git a/PadelClub/Data/MonthData.swift b/PadelClub/Data/MonthData.swift new file mode 100644 index 0000000..0bb8d46 --- /dev/null +++ b/PadelClub/Data/MonthData.swift @@ -0,0 +1,53 @@ +// +// MonthData.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import Foundation +import SwiftUI +import LeStorage + +@Observable +class MonthData : ModelObject, Storable { + + static func resourceName() -> String { return "month-data" } + + private(set) var id: String = Store.randomId() + private(set) var monthKey: String + private(set) var creationDate: Date + + var maleUnrankedValue: Int? = nil + var femaleUnrankedValue: Int? = nil + + init(monthKey: String) { + self.monthKey = monthKey + self.creationDate = Date() + } + + static func calculateCurrentUnrankedValues(mostRecentDateAvailable: Date) async { + let lastDataSourceMaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: mostRecentDateAvailable, man: true) + let lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: mostRecentDateAvailable, man: false) + + await MainActor.run { + if let lastDataSource = DataStore.shared.appSettings.lastDataSource { + let currentMonthData : MonthData = Store.main.filter(isIncluded: { $0.monthKey == lastDataSource }).first ?? MonthData(monthKey: lastDataSource) + currentMonthData.maleUnrankedValue = lastDataSourceMaleUnranked + currentMonthData.femaleUnrankedValue = lastDataSourceFemaleUnranked + try? DataStore.shared.monthData.addOrUpdate(instance: currentMonthData) + } + } + } + + override func deleteDependencies() throws { + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _monthKey = "monthKey" + case _creationDate = "creationDate" + case _maleUnrankedValue = "maleUnrankedValue" + case _femaleUnrankedValue = "femaleUnrankedValue" + } +} diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 823e143..4577bd4 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -387,6 +387,15 @@ class Round: ModelObject, Storable { return Store.main.findById(parentRound) } + func updateMatchFormat(_ matchFormat: MatchFormat) { + self.matchFormat = matchFormat + let playedMatches = _matches() + playedMatches.forEach { match in + match.matchFormat = matchFormat + } + try? DataStore.shared.matches.addOrUpdate(contentOfs: playedMatches) + } + override func deleteDependencies() throws { try Store.main.deleteDependencies(items: _matches()) try Store.main.deleteDependencies(items: loserRoundsAndChildren()) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 95791bc..bd44b9b 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -41,14 +41,13 @@ class Tournament : ModelObject, Storable { var qualifiedPerGroupStage: Int var teamsPerGroupStage: Int var entryFee: Double? - var maleUnrankedValue: Int? - var femaleUnrankedValue: Int? var payment: TournamentPayment = .free + var additionalEstimationDuration: Int = 0 @ObservationIgnored var navigationPath: [Screen] = [] - internal init(event: String? = nil, creator: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = true, groupStageFormat: Int? = nil, roundFormat: Int? = nil, loserRoundFormat: Int? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, groupStageCourtCount: Int? = nil, seedCount: Int = 8, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, maleUnrankedValue: Int? = nil, femaleUnrankedValue: Int? = nil) { + internal init(event: String? = nil, creator: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = true, groupStageFormat: Int? = nil, roundFormat: Int? = nil, loserRoundFormat: Int? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, groupStageCourtCount: Int? = nil, seedCount: Int = 8, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil) { self.event = event self.creator = creator self.name = name @@ -77,8 +76,6 @@ class Tournament : ModelObject, Storable { self.qualifiedPerGroupStage = qualifiedPerGroupStage self.teamsPerGroupStage = teamsPerGroupStage self.entryFee = entryFee - self.maleUnrankedValue = maleUnrankedValue - self.femaleUnrankedValue = femaleUnrankedValue self.teamSorting = teamSorting ?? federalLevelCategory.defaultTeamSortingType } @@ -386,7 +383,7 @@ class Tournament : ModelObject, Storable { _sortedTeams = bracketTeams.sorted(using: _currentSelectionSorting, order: .ascending) + groupStageTeams.sorted(using: _currentSelectionSorting, order: .ascending) } - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + //let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) //print("func selectedSortedTeams", id, tournamentTitle(), duration.formatted(.units(allowed: [.seconds, .milliseconds]))) return _sortedTeams } @@ -608,22 +605,27 @@ class Tournament : ModelObject, Storable { func updateRank(to newDate: Date?) async throws { guard let newDate else { return } rankSourceDate = newDate - - let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate) - let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate) - + if currentMonthData() == nil { + let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate) + let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate) + await MainActor.run { + let monthData = MonthData(monthKey: URL.importDateFormatter.string(from: newDate)) + monthData.maleUnrankedValue = lastRankMan + monthData.femaleUnrankedValue = lastRankWoman + try? DataStore.shared.monthData.addOrUpdate(instance: monthData) + } + } + + let lastRankMan = currentMonthData()?.maleUnrankedValue + let lastRankWoman = currentMonthData()?.femaleUnrankedValue + try await unsortedPlayers().concurrentForEach { player in let dataURLs = SourceFileManager.shared.allFiles.filter({ $0.dateFromPath == newDate }) let sources = dataURLs.map { CSVParser(url: $0) } try await player.updateRank(from: sources, lastRank: (player.sex == 0 ? lastRankWoman : lastRankMan) ?? 0) } - - await MainActor.run { - self.maleUnrankedValue = lastRankMan - self.femaleUnrankedValue = lastRankWoman - } } func missingUnrankedValue() -> Bool { @@ -1035,6 +1037,21 @@ class Tournament : ModelObject, Storable { try Store.main.deleteDependencies(items: self.groupStages()) try Store.main.deleteDependencies(items: self.rounds()) } + + func currentMonthData() -> MonthData? { + guard let rankSourceDate else { return nil } + let dateString = URL.importDateFormatter.string(from: rankSourceDate) + return Store.main.filter(isIncluded: { $0.monthKey == dateString }).first + } + + var maleUnrankedValue: Int? { + currentMonthData()?.maleUnrankedValue + } + + var femaleUnrankedValue: Int? { + currentMonthData()?.femaleUnrankedValue + } + } extension Tournament { @@ -1068,8 +1085,7 @@ extension Tournament { case _qualifiedPerGroupStage = "qualifiedPerGroupStage" case _teamsPerGroupStage = "teamsPerGroupStage" case _entryFee = "entryFee" - case _maleUnrankedValue = "maleUnrankedValue" - case _femaleUnrankedValue = "femaleUnrankedValue" + case _additionalEstimationDuration = "additionalEstimationDuration" } } diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift index 63a3ac4..1626594 100644 --- a/PadelClub/Data/User.swift +++ b/PadelClub/Data/User.swift @@ -27,12 +27,7 @@ class User: UserBase { var lastName: String var phone: String? var country: String? - var callMessageBody : String? = nil - var callMessageSignature: String? = nil - var callDisplayFormat: Bool = false - var callDisplayEntryFee: Bool = false - var callUseFullCustomMessage: Bool = false - + init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?) { self.username = username self.firstName = firstName @@ -69,12 +64,6 @@ class User: UserBase { case _lastName = "lastName" case _phone = "phone" case _country = "country" - case _callMessageBody = "callMessageBody" - case _callMessageSignature = "callMessageSignature" - case _callDisplayFormat = "callDisplayFormat" - case _callDisplayEntryFee = "callDisplayEntryFee" - case _callUseFullCustomMessage = "callUseFullCustomMessage" - } } diff --git a/PadelClub/Manager/ContactManager.swift b/PadelClub/Manager/ContactManager.swift index 5c381cd..005e736 100644 --- a/PadelClub/Manager/ContactManager.swift +++ b/PadelClub/Manager/ContactManager.swift @@ -34,7 +34,7 @@ extension ContactType { static let defaultSignature = "" static func callingGroupStageCustomMessage(tournament: Tournament?, startDate: Date?, roundLabel: String) -> String { - let tournamentCustomMessage = DataStore.shared.user?.callMessageBody ?? defaultCustomMessage + let tournamentCustomMessage = DataStore.shared.appSettings.callMessageBody ?? defaultCustomMessage let clubName = tournament?.clubName ?? "" var text = tournamentCustomMessage @@ -49,7 +49,7 @@ extension ContactType { text = text.replacingOccurrences(of: "#jour", with: "\(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))") text = text.replacingOccurrences(of: "#horaire", with: "\(date.formatted(Date.FormatStyle().hour().minute()))") - let signature = DataStore.shared.user?.callMessageSignature ?? defaultSignature + let signature = DataStore.shared.appSettings.callMessageSignature ?? defaultSignature text = text.replacingOccurrences(of: "#signature", with: signature) return text @@ -57,7 +57,7 @@ extension ContactType { static func callingGroupStageMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?) -> String { - let useFullCustomMessage = DataStore.shared.user?.callUseFullCustomMessage ?? false + let useFullCustomMessage = DataStore.shared.appSettings.callUseFullCustomMessage ?? false if useFullCustomMessage { return callingGroupStageCustomMessage(tournament: tournament, startDate: startDate, roundLabel: roundLabel) @@ -66,17 +66,17 @@ extension ContactType { let date = startDate ?? tournament?.startDate ?? Date() let clubName = tournament?.clubName ?? "" - let message = DataStore.shared.user?.callMessageBody ?? defaultCustomMessage - let signature = DataStore.shared.user?.callMessageSignature ?? defaultSignature + let message = DataStore.shared.appSettings.callMessageBody ?? defaultCustomMessage + let signature = DataStore.shared.appSettings.callMessageSignature ?? defaultSignature let localizedCalled = "convoqué" + (tournament?.tournamentCategory == .women ? "e" : "") + "s" var formatMessage: String? { - (DataStore.shared.user?.callDisplayFormat ?? false) ? matchFormat?.computedLongLabel.appending(".") : nil + (DataStore.shared.appSettings.callDisplayFormat ?? false) ? matchFormat?.computedLongLabel.appending(".") : nil } var entryFeeMessage: String? { - (DataStore.shared.user?.callDisplayEntryFee ?? false) ? tournament?.entryFeeMessage : nil + (DataStore.shared.appSettings.callDisplayEntryFee ?? false) ? tournament?.entryFeeMessage : nil } var computedMessage: String { diff --git a/PadelClub/Manager/PadelRule.swift b/PadelClub/Manager/PadelRule.swift index 71a5c96..47e5cc1 100644 --- a/PadelClub/Manager/PadelRule.swift +++ b/PadelClub/Manager/PadelRule.swift @@ -1046,16 +1046,16 @@ enum MatchFormat: Int, Hashable, Codable, CaseIterable { } } - var estimatedDuration: Int { - if UserDefaults.standard.object(forKey: format) != nil { - return UserDefaults.standard.integer(forKey: format) - } else { - return defaultEstimatedDuration - } + func getEstimatedDuration(_ additionalDuration: Int = 0) -> Int { + estimatedDuration + additionalDuration + } + + private var estimatedDuration: Int { + DataStore.shared.appSettings.matchFormatsDefaultDuration?[self] ?? defaultEstimatedDuration } - func formattedEstimatedDuration() -> String { - Duration.seconds(estimatedDuration * 60).formatted(.units(allowed: [.minutes])) + func formattedEstimatedDuration(_ additionalDuration: Int = 0) -> String { + Duration.seconds((estimatedDuration + additionalDuration) * 60).formatted(.units(allowed: [.minutes])) } func formattedEstimatedBreakDuration() -> String { diff --git a/PadelClub/Manager/SourceFileManager.swift b/PadelClub/Manager/SourceFileManager.swift index 690e930..018a341 100644 --- a/PadelClub/Manager/SourceFileManager.swift +++ b/PadelClub/Manager/SourceFileManager.swift @@ -12,7 +12,7 @@ class SourceFileManager { static let beachPadel = URL(string: "https://beach-padel.app.fft.fr/beachja/index/")! var lastDataSource: String? { - UserDefaults.standard.string(forKey: "lastDataSource") + DataStore.shared.appSettings.lastDataSource } func lastDataSourceDate() -> Date? { diff --git a/PadelClub/ViewModel/AppScreen.swift b/PadelClub/ViewModel/AppScreen.swift new file mode 100644 index 0000000..1eba92f --- /dev/null +++ b/PadelClub/ViewModel/AppScreen.swift @@ -0,0 +1,13 @@ +// +// AppScreen.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import Foundation + +enum AppScreen: CaseIterable, Identifiable { + var id: Self { self } + case matchFormatSettings +} diff --git a/PadelClub/ViewModel/MatchScheduler.swift b/PadelClub/ViewModel/MatchScheduler.swift index 3337951..6fa4495 100644 --- a/PadelClub/ViewModel/MatchScheduler.swift +++ b/PadelClub/ViewModel/MatchScheduler.swift @@ -62,6 +62,7 @@ enum MatchSchedulerOption: Hashable { class MatchScheduler { static let shared = MatchScheduler() + var additionalEstimationDuration : Int = 0 var options: Set = Set(arrayLiteral: .accountUpperBracketBreakTime) var timeDifferenceLimit: Double = 300.0 var loserBracketRotationDifference: Int = 0 @@ -254,7 +255,7 @@ class MatchScheduler { let matchesByCourt = byCourt[court]?.sorted(by: \.startDate!) let lastMatch = matchesByCourt?.last var results = [(String, Date)]() - if let courtFreeDate = lastMatch?.estimatedEndDate() { + if let courtFreeDate = lastMatch?.estimatedEndDate(additionalEstimationDuration) { results.append((court, courtFreeDate)) } return results @@ -276,7 +277,8 @@ class MatchScheduler { _startDate = match.startDate rotationIndex += 1 } - let timeMatch = TimeMatch(matchID: match.id, rotationIndex: rotationIndex, courtIndex: match.courtIndex() ?? 0, startDate: match.startDate!, durationLeft: match.matchFormat.estimatedDuration, minimumBreakTime: match.matchFormat.breakTime.breakTime) + + let timeMatch = TimeMatch(matchID: match.id, rotationIndex: rotationIndex, courtIndex: match.getCourtIndex() ?? 0, startDate: match.startDate!, durationLeft: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), minimumBreakTime: match.matchFormat.breakTime.breakTime) slots.append(timeMatch) } @@ -398,7 +400,7 @@ class MatchScheduler { matchPerRound[first.roundObject!.index] = 1 } } - let timeMatch = TimeMatch(matchID: first.id, rotationIndex: rotationIndex, courtIndex: courtIndex, startDate: rotationStartDate, durationLeft: first.matchFormat.estimatedDuration, minimumBreakTime: first.matchFormat.breakTime.breakTime) + let timeMatch = TimeMatch(matchID: first.id, rotationIndex: rotationIndex, courtIndex: courtIndex, startDate: rotationStartDate, durationLeft: first.matchFormat.getEstimatedDuration(additionalEstimationDuration), minimumBreakTime: first.matchFormat.breakTime.breakTime) slots.append(timeMatch) availableMatchs.removeAll(where: { $0.id == first.id }) } else { diff --git a/PadelClub/ViewModel/NavigationViewModel.swift b/PadelClub/ViewModel/NavigationViewModel.swift index 70669d8..1a8467e 100644 --- a/PadelClub/ViewModel/NavigationViewModel.swift +++ b/PadelClub/ViewModel/NavigationViewModel.swift @@ -10,6 +10,7 @@ import SwiftUI @Observable class NavigationViewModel { var path = NavigationPath() + var selectedTab: TabDestination? var agendaDestination: AgendaDestination? = .activity var tournament: Tournament? } diff --git a/PadelClub/Views/Calling/CallMessageCustomizationView.swift b/PadelClub/Views/Calling/CallMessageCustomizationView.swift index 6d8f309..3187722 100644 --- a/PadelClub/Views/Calling/CallMessageCustomizationView.swift +++ b/PadelClub/Views/Calling/CallMessageCustomizationView.swift @@ -10,18 +10,16 @@ import SwiftUI struct CallMessageCustomizationView: View { @EnvironmentObject var dataStore: DataStore var tournament: Tournament - var user: User @FocusState private var textEditor: Bool @State private var customClubName: String = "" @State private var customCallMessageBody: String = "" @State private var customCallMessageSignature: String = "" - init(tournament: Tournament, user: User) { + init(tournament: Tournament) { self.tournament = tournament - self.user = user - _customCallMessageBody = State(wrappedValue: user.callMessageBody ?? "") - _customCallMessageSignature = State(wrappedValue: user.callMessageSignature ?? "") + _customCallMessageBody = State(wrappedValue: DataStore.shared.appSettings.callMessageBody ?? "") + _customCallMessageSignature = State(wrappedValue: DataStore.shared.appSettings.callMessageSignature ?? "") _customClubName = State(wrappedValue: tournament.clubName ?? "") } @@ -30,11 +28,11 @@ struct CallMessageCustomizationView: View { } var formatMessage: String? { - user.callDisplayFormat ? tournament.matchFormat.computedLongLabel + "." : nil + dataStore.appSettings.callDisplayFormat ? tournament.matchFormat.computedLongLabel + "." : nil } var entryFeeMessage: String? { - user.callDisplayEntryFee ? tournament.entryFeeMessage : nil + dataStore.appSettings.callDisplayEntryFee ? tournament.entryFeeMessage : nil } var computedMessage: String { @@ -47,7 +45,7 @@ struct CallMessageCustomizationView: View { } var body: some View { - @Bindable var user = user + @Bindable var appSettings = dataStore.appSettings List { Section { ZStack { @@ -86,7 +84,7 @@ struct CallMessageCustomizationView: View { } Section { - if user.callUseFullCustomMessage { + if appSettings.callUseFullCustomMessage { Text(self.computedFullCustomMessage()) .contextMenu { Button("Coller dans le presse-papier") { @@ -108,7 +106,7 @@ struct CallMessageCustomizationView: View { Section { LabeledContent { - Toggle(isOn: $user.callUseFullCustomMessage) { + Toggle(isOn: $appSettings.callUseFullCustomMessage) { } } label: { @@ -124,13 +122,13 @@ struct CallMessageCustomizationView: View { .toolbar { ToolbarItem(placement: .topBarTrailing) { Menu { - Picker(selection: $user.callDisplayFormat) { + Picker(selection: $appSettings.callDisplayFormat) { Text("Afficher le format").tag(true) Text("Masquer le format").tag(false) } label: { } - Picker(selection: $user.callDisplayEntryFee) { + Picker(selection: $appSettings.callDisplayEntryFee) { Text("Afficher le prix d'inscription").tag(true) Text("Masquer le prix d'inscription").tag(false) } label: { @@ -151,30 +149,30 @@ struct CallMessageCustomizationView: View { } } } - .onChange(of: user.callUseFullCustomMessage) { - if user.callUseFullCustomMessage == false { - user.callMessageBody = ContactType.defaultCustomMessage + .onChange(of: appSettings.callUseFullCustomMessage) { + if appSettings.callUseFullCustomMessage == false { + appSettings.callMessageBody = ContactType.defaultCustomMessage } _save() } .onChange(of: customCallMessageBody) { - user.callMessageBody = customCallMessageBody + appSettings.callMessageBody = customCallMessageBody _save() } .onChange(of: customCallMessageSignature) { - user.callMessageSignature = customCallMessageSignature + appSettings.callMessageSignature = customCallMessageSignature _save() } - .onChange(of: user.callDisplayEntryFee) { + .onChange(of: appSettings.callDisplayEntryFee) { _save() } - .onChange(of: user.callDisplayFormat) { + .onChange(of: appSettings.callDisplayFormat) { _save() } } private func _save() { - try? dataStore.setUser(user) + dataStore.updateSettings() } func computedFullCustomMessage() -> String { diff --git a/PadelClub/Views/Calling/CallSettingsView.swift b/PadelClub/Views/Calling/CallSettingsView.swift index 48d868e..b718ef6 100644 --- a/PadelClub/Views/Calling/CallSettingsView.swift +++ b/PadelClub/Views/Calling/CallSettingsView.swift @@ -14,16 +14,14 @@ struct CallSettingsView: View { var body: some View { List { - if let user = dataStore.user { - Section { - NavigationLink { - CallMessageCustomizationView(tournament: tournament, user: user) - } label: { - Text("Personnaliser le message de convocation") - } + Section { + NavigationLink { + CallMessageCustomizationView(tournament: tournament) + } label: { + Text("Personnaliser le message de convocation") } } - + Section { RowButtonView("Envoyer un message à tout le monde") { diff --git a/PadelClub/Views/Club/ClubSearchView.swift b/PadelClub/Views/Club/ClubSearchView.swift index f6770ff..f753c68 100644 --- a/PadelClub/Views/Club/ClubSearchView.swift +++ b/PadelClub/Views/Club/ClubSearchView.swift @@ -339,6 +339,7 @@ enum Pratique: String, Codable { case beach = "BEACH" case padel = "PADEL" case tennis = "TENNIS" + case pickle = "PICKLE" } // MARK: - ClubMarker diff --git a/PadelClub/Views/ClubView.swift b/PadelClub/Views/ClubView.swift deleted file mode 100644 index 219d105..0000000 --- a/PadelClub/Views/ClubView.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// ClubView.swift -// PadelClub -// -// Created by Laurent Morvillier on 06/02/2024. -// - -import SwiftUI - -struct ClubView: View { - - var club: Club - - var body: some View { - List(club.tournaments) { tournament in - Text(tournament.tournamentTitle()) - }.navigationTitle(club.name) - } -} - -#Preview { - ClubView(club: Club(name: "AUC", acronym: "test", address: "")) -} diff --git a/PadelClub/Views/Components/Labels.swift b/PadelClub/Views/Components/Labels.swift index 269c18d..eb1471c 100644 --- a/PadelClub/Views/Components/Labels.swift +++ b/PadelClub/Views/Components/Labels.swift @@ -15,13 +15,13 @@ struct LabelOptions: View { struct LabelStructure: View { var body: some View { - Label("Structure", systemImage: "hammer") + Label("Structure", systemImage: "hammer").labelStyle(.titleOnly) } } struct LabelSettings: View { var body: some View { - Label("Réglages", systemImage: "slider.horizontal.3") + Label("Réglages", systemImage: "slider.horizontal.3").labelStyle(.titleOnly) } } diff --git a/PadelClub/Views/Components/StepperView.swift b/PadelClub/Views/Components/StepperView.swift index 2df2604..197c2b8 100644 --- a/PadelClub/Views/Components/StepperView.swift +++ b/PadelClub/Views/Components/StepperView.swift @@ -13,12 +13,12 @@ struct StepperView: View { var title: String? = nil @Binding var count: Int - + var step: Int = 1 var minimum: Int? = nil var maximum: Int? = nil var body: some View { - VStack(spacing: 0) { + VStack { HStack(spacing: 8) { Button(action: { self._subtract() @@ -74,14 +74,14 @@ struct StepperView: View { if let maximum, self.count + 1 > maximum { return } - self.count += 1 + self.count += step } fileprivate func _subtract() { if let minimum, self.count - 1 < minimum { return } - self.count -= 1 + self.count -= step } } diff --git a/PadelClub/Views/ContentView.swift b/PadelClub/Views/ContentView.swift deleted file mode 100644 index 5d92628..0000000 --- a/PadelClub/Views/ContentView.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// ContentView.swift -// PadelClub -// -// Created by Laurent Morvillier on 02/02/2024. -// - -import SwiftUI -import LeStorage - -struct ContentView: View { - - @StateObject var dataStore = DataStore() - - var body: some View { - NavigationStack { - - VStack { - - List(self.dataStore.clubs) { club in - - NavigationLink { - ClubView(club: club) - } label: { - Text(club.name) - } - } - - Button("add") { - self._add() - } - .padding() - .buttonStyle(.bordered) - } - .toolbar(content: { - ToolbarItem { - NavigationLink { - MainUserView() - .environmentObject(self.dataStore) - } label: { - Image(systemName: "person.circle.fill") - } - } - - ToolbarItem { - NavigationLink { - SubscriptionView() - } label: { - Image(systemName: "tennisball.circle.fill") - } - } - }) - .navigationTitle("Home") - - } - } - - func _add() { -// let id = (0...1000000).randomElement()! -// let club: Club = Club(name: "test\(id)", address: "some address") -// self.dataStore.clubs.addOrUpdate(instance: club) - -// for _ in 0...20 { -// var clubs: [Club] = [] -// for _ in 0...20 { -// let id = (0...1000000).randomElement()! -// let club: Club = Club(name: "test\(id)", acronym: "test", address: "some address") -// clubs.append(club) -// } -// do { -// try self.dataStore.clubs.append(contentOfs: clubs) -// } catch { -// Logger.error(error) -// } -// } - } - -} - -#Preview { - ContentView() -} diff --git a/PadelClub/Views/Match/Components/MatchDateView.swift b/PadelClub/Views/Match/Components/MatchDateView.swift index 4665e0b..d6e06f1 100644 --- a/PadelClub/Views/Match/Components/MatchDateView.swift +++ b/PadelClub/Views/Match/Components/MatchDateView.swift @@ -8,6 +8,7 @@ import SwiftUI struct MatchDateView: View { + @Environment(Tournament.self) var tournament: Tournament @EnvironmentObject var dataStore: DataStore var match: Match var showPrefix: Bool = false @@ -31,8 +32,9 @@ struct MatchDateView: View { save() } } else { - Button("Décaler de \(match.matchFormat.estimatedDuration) minutes") { - match.startDate = match.startDate?.addingTimeInterval(Double(match.matchFormat.estimatedDuration) * 60.0) + let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration) + Button("Décaler de \(estimatedDuration) minutes") { + match.startDate = match.startDate?.addingTimeInterval(Double(estimatedDuration) * 60.0) match.endDate = nil save() } diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index d12d91e..fceeee2 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -9,6 +9,7 @@ import SwiftUI struct MatchDetailView: View { @EnvironmentObject var dataStore: DataStore + @Environment(Tournament.self) var tournament: Tournament @Environment(\.dismiss) var dismiss let matchViewStyle: MatchViewStyle @@ -320,14 +321,15 @@ struct MatchDetailView: View { Section { if match.hasEnded() == false { + let rotationDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration) Picker(selection: $startDateSetup) { if match.isReady() { Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5)) Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15)) Text("Tout de suite").tag(MatchDateSetup.now) } - Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-match.matchFormat.estimatedDuration)) - Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(match.matchFormat.estimatedDuration)) + Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-rotationDuration)) + Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(rotationDuration)) Text("À").tag(MatchDateSetup.customDate) } label: { Text("Horaire") diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 3aec8bf..450ec45 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -19,7 +19,6 @@ struct ActivityView: View { @State private var viewStyle: AgendaDestination.ViewStyle = .list @State private var federalTournaments: [FederalTournament] = [] @State private var isGatheringFederalTournaments: Bool = false - @Binding var selectedTab: TabDestination? @State private var error: Error? var runningTournaments: [FederalTournamentHolder] { @@ -257,7 +256,7 @@ struct ActivityView: View { Text("Pour voir vos tournois tenup ici, indiquez vos clubs préférés.") } actions: { RowButtonView("Choisir mes clubs préférés") { - selectedTab = .umpire + navigation.selectedTab = .umpire } } } else { @@ -276,6 +275,5 @@ struct ActivityView: View { } #Preview { - ActivityView(selectedTab: .constant(.activity)) - .environmentObject(DataStore.shared) + ActivityView() } diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index 3e9384a..55cec3a 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -6,18 +6,20 @@ // import SwiftUI +import LeStorage struct MainView: View { @StateObject var dataStore = DataStore.shared @AppStorage("importingFiles") var importingFiles: Bool = false - + @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel + @State private var checkingFilesAttempt: Int = 0 @State private var checkingFiles: Bool = false - @AppStorage("lastDataSource") var lastDataSource: String? - @AppStorage("lastDataSourceMaleUnranked") var lastDataSourceMaleUnranked: Int? - @AppStorage("lastDataSourceFemaleUnranked") var lastDataSourceFemaleUnranked: Int? - + var lastDataSource: String? { + dataStore.appSettings.lastDataSource + } + @Environment(\.managedObjectContext) private var viewContext @FetchRequest( @@ -25,10 +27,10 @@ struct MainView: View { animation: .default) private var players: FetchedResults - @State private var selectedTab: TabDestination? var body: some View { - TabView(selection: $selectedTab) { - ActivityView(selectedTab: $selectedTab) + @Bindable var navigation = navigation + TabView(selection: $navigation.selectedTab) { + ActivityView() .tabItem(for: .activity) TournamentOrganizerView() .tabItem(for: .tournamentOrganizer) @@ -87,7 +89,7 @@ struct MainView: View { } private func _checkSourceFileAvailability() async { - + print(dataStore.appSettings.lastDataSource) print("check internet") print("check files on internet") print("check if any files on internet are more recent than here") @@ -104,21 +106,18 @@ struct MainView: View { private func _startImporting() { importingFiles = true Task { - lastDataSource = await FileImportManager.shared.importDataFromFFT() + let lastDataSource = await FileImportManager.shared.importDataFromFFT() + dataStore.appSettings.lastDataSource = lastDataSource + dataStore.updateSettings() if let lastDataSource, let mostRecentDate = URL.importDateFormatter.date(from: lastDataSource) { - await _calculateCurrentUnrankedValues(mostRecentDateAvailable: mostRecentDate) + await MonthData.calculateCurrentUnrankedValues(mostRecentDateAvailable: mostRecentDate) } importingFiles = false await _downloadPreviousDate() } } - - private func _calculateCurrentUnrankedValues(mostRecentDateAvailable: Date) async { - lastDataSourceMaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: mostRecentDateAvailable, man: true) - lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: mostRecentDateAvailable, man: false) - } - + private func _downloadPreviousDate() async { await SourceFileManager.shared.getAllFiles() } diff --git a/PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift b/PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift index 8cd43e5..734e89a 100644 --- a/PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift +++ b/PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift @@ -24,6 +24,7 @@ struct TournamentOrganizerView: View { ContentUnavailableView("Aucun tournoi sélectionné", systemImage: "rectangle.slash", description: Text("Utilisez l'accès rapide ci-dessous pour éditer un tournoi et passer rapidement d'un tournoi à l'autre.")) .navigationTitle("Gestionnaire de tournois") .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) } } Divider() diff --git a/PadelClub/Views/Navigation/PadelClubView.swift b/PadelClub/Views/Navigation/PadelClubView.swift index fd7d081..08c66d5 100644 --- a/PadelClub/Views/Navigation/PadelClubView.swift +++ b/PadelClub/Views/Navigation/PadelClubView.swift @@ -13,7 +13,12 @@ struct PadelClubView: View { @State private var checkingFiles: Bool = false @State private var importingFiles: Bool = false - @AppStorage("lastDataSource") var lastDataSource: String? + @EnvironmentObject var dataStore: DataStore + + var lastDataSource: String? { + dataStore.appSettings.lastDataSource + } + @Environment(\.managedObjectContext) private var viewContext @FetchRequest( @@ -36,17 +41,38 @@ struct PadelClubView: View { List { if let _lastDataSourceDate { Section { - HStack { - VStack(alignment: .leading) { - Text("Classement mensuel utilisé").font(.caption).foregroundStyle(.secondary) - Text(_lastDataSourceDate.monthYearFormatted) - } - Spacer() + LabeledContent { Image(systemName: "checkmark") + } label: { + Text(_lastDataSourceDate.monthYearFormatted) + Text("Classement mensuel utilisé") } } } + let monthData = dataStore.monthData.sorted(by: \.creationDate).reversed() + ForEach(monthData) { monthData in + Section { + LabeledContent { + if let maleUnrankedValue = monthData.maleUnrankedValue { + Text(maleUnrankedValue.formatted()) + } + } label: { + Text("Messieurs") + Text("Rang d'un non classé") + } + LabeledContent { + if let femaleUnrankedValue = monthData.femaleUnrankedValue { + Text(femaleUnrankedValue.formatted()) + } + } label: { + Text("Dames") + Text("Rang d'une non classée") + } + } header: { + Text(monthData.monthKey) + } + } // // if players.isEmpty { // ContentUnavailableView { @@ -60,6 +86,7 @@ struct PadelClubView: View { // } // } } + .headerProminence(.increased) .navigationTitle(TabDestination.padelClub.title) // .task { // await self._checkSourceFileAvailability() @@ -101,7 +128,12 @@ struct PadelClubView: View { private func _startImporting() { importingFiles = true Task { - lastDataSource = await FileImportManager.shared.importDataFromFFT() + let lastDataSource = await FileImportManager.shared.importDataFromFFT() + dataStore.appSettings.lastDataSource = lastDataSource + dataStore.updateSettings() + if let lastDataSource, let mostRecentDate = URL.importDateFormatter.date(from: lastDataSource) { + await MonthData.calculateCurrentUnrankedValues(mostRecentDateAvailable: mostRecentDate) + } importingFiles = false } } diff --git a/PadelClub/Views/Navigation/Toolbox/DurationSettingsView.swift b/PadelClub/Views/Navigation/Toolbox/DurationSettingsView.swift new file mode 100644 index 0000000..1af446c --- /dev/null +++ b/PadelClub/Views/Navigation/Toolbox/DurationSettingsView.swift @@ -0,0 +1,25 @@ +// +// DurationSettingsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import SwiftUI + +struct DurationSettingsView: View { + var body: some View { + List { + ForEach(MatchFormat.allCases, id: \.self) { matchFormat in + MatchFormatStorageView(matchFormat: matchFormat) + } + } + .navigationTitle("Durées moyennes") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } +} + +#Preview { + DurationSettingsView() +} diff --git a/PadelClub/Views/Navigation/Toolbox/GlobalSettingsView.swift b/PadelClub/Views/Navigation/Toolbox/GlobalSettingsView.swift new file mode 100644 index 0000000..3fc5739 --- /dev/null +++ b/PadelClub/Views/Navigation/Toolbox/GlobalSettingsView.swift @@ -0,0 +1,67 @@ +// +// GlobalSettingsView.swift +// Padel Tournament +// +// Created by Razmig Sarkissian on 16/10/2023. +// + +import SwiftUI + +struct GlobalSettingsView: View { + @EnvironmentObject var dataStore : DataStore + + var body: some View { + @Bindable var appSettings = dataStore.appSettings + List { + Section { + Picker(selection: $appSettings.groupStageMatchFormatPreference) { + Text("Automatique").tag(nil as Int?) + ForEach(MatchFormat.allCases, id: \.self) { format in + Text(format.format).tag(format.rawValue as Int?) + } + } label: { + HStack { + Text("Poule") + Spacer() + } + } + Picker(selection: $appSettings.bracketMatchFormatPreference) { + Text("Automatique").tag(nil as Int?) + ForEach(MatchFormat.allCases, id: \.self) { format in + Text(format.format).tag(format.rawValue as Int?) + } + } label: { + HStack { + Text("Tableau") + Spacer() + } + } + Picker(selection: $appSettings.loserBracketMatchFormatPreference) { + Text("Automatique").tag(nil as Int?) + ForEach(MatchFormat.allCases, id: \.self) { format in + Text(format.format).tag(format.rawValue as Int?) + } + } label: { + HStack { + Text("Match de classement") + Spacer() + } + } + } header: { + Text("Vos formats préférés") + } footer: { + Text("À minima, les règles fédérales seront toujours prises en compte par défaut.") + } + } + .onChange(of: [ + appSettings.bracketMatchFormatPreference, + appSettings.groupStageMatchFormatPreference, + appSettings.loserBracketMatchFormatPreference + ]) { + dataStore.updateSettings() + } + .navigationTitle("Formats par défaut") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } +} diff --git a/PadelClub/Views/Navigation/Toolbox/MatchFormatStorageView.swift b/PadelClub/Views/Navigation/Toolbox/MatchFormatStorageView.swift new file mode 100644 index 0000000..717c440 --- /dev/null +++ b/PadelClub/Views/Navigation/Toolbox/MatchFormatStorageView.swift @@ -0,0 +1,50 @@ +// +// MatchFormatStorageView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import SwiftUI + +struct MatchFormatStorageView: View { + @State private var estimatedDuration: Int + @EnvironmentObject var dataStore: DataStore + + let matchFormat: MatchFormat + + init(matchFormat: MatchFormat) { + self.matchFormat = matchFormat + _estimatedDuration = State(wrappedValue: matchFormat.getEstimatedDuration()) + } + + var body: some View { + Section { + LabeledContent { + StepperView(title: "minutes", count: $estimatedDuration, step: 5) + } label: { + Text("Durée \(matchFormat.format)") + Text(matchFormat.computedShortLabelWithoutPrefix) + } + } footer: { + if estimatedDuration != matchFormat.defaultEstimatedDuration { + HStack { + Spacer() + Button { + self.estimatedDuration = matchFormat.defaultEstimatedDuration + } label: { + Text("remettre la durée par défault") + .underline() + } + .buttonStyle(.borderless) + + } + } + } + .onChange(of: estimatedDuration) { + dataStore.appSettings.saveMatchFormatsDefaultDuration(matchFormat, estimatedDuration: estimatedDuration) + dataStore.updateSettings() + } + } +} + diff --git a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift index e5bae96..afb41de 100644 --- a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift +++ b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift @@ -11,17 +11,34 @@ struct ToolboxView: View { var body: some View { NavigationStack { List { - NavigationLink { - SelectablePlayerListView() - } label: { - Label("Rechercher un joueur", systemImage: "person.fill.viewfinder") + Section { + NavigationLink { + SelectablePlayerListView() + } label: { + Label("Rechercher un joueur", systemImage: "person.fill.viewfinder") + } } - NavigationLink { - RankCalculatorView() - } label: { - Label("Calculateur de points", systemImage: "scalemass") + + Section { + NavigationLink { + RankCalculatorView() + } label: { + Label("Calculateur de points", systemImage: "scalemass") + } } + Section { + NavigationLink { + GlobalSettingsView() + } label: { + Label("Formats de jeu par défaut", systemImage: "megaphone") + } + NavigationLink { + DurationSettingsView() + } label: { + Label("Estimation des durées moyennes", systemImage: "deskclock") + } + } } .navigationTitle(TabDestination.toolbox.title) } diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index da95399..1bcaab2 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -61,7 +61,6 @@ struct UmpireView: View { user.licenceId = nil dataStore.setUser(user) } - .font(.caption) } } @@ -80,7 +79,6 @@ struct UmpireView: View { user.club = nil dataStore.setUser(user) } - .font(.caption) } } } diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index 601bb67..25f32a5 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -194,11 +194,12 @@ struct PlanningSettingsView: View { dispatch.timedMatches.forEach { matchSchedule in if let match = matches.first(where: { $0.id == matchSchedule.matchID }) { - let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(match.matchFormat.estimatedDuration) * 60 + let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration) + let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60 if let startDate = match.groupStageObject?.startDate { let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd) match.startDate = matchStartDate - lastDate = matchStartDate.addingTimeInterval(Double(match.matchFormat.estimatedDuration) * 60) + lastDate = matchStartDate.addingTimeInterval(Double(estimatedDuration) * 60) } match.setCourt(matchSchedule.courtIndex + 1) } diff --git a/PadelClub/Views/Shared/MatchFormatPickerView.swift b/PadelClub/Views/Shared/MatchFormatPickerView.swift index f70c115..1de7981 100644 --- a/PadelClub/Views/Shared/MatchFormatPickerView.swift +++ b/PadelClub/Views/Shared/MatchFormatPickerView.swift @@ -8,6 +8,7 @@ import SwiftUI struct MatchFormatPickerView: View { + @Environment(Tournament.self) var tournament: Tournament let headerLabel: String @Binding var matchFormat: MatchFormat @State private var isExpanded: Bool = false @@ -40,7 +41,7 @@ struct MatchFormatPickerView: View { Text(matchFormat.format).font(.largeTitle) Spacer() VStack(alignment: .trailing) { - Text("~" + matchFormat.formattedEstimatedDuration()) + Text("~" + matchFormat.formattedEstimatedDuration(tournament.additionalEstimationDuration)) Text(matchFormat.formattedEstimatedBreakDuration() + " de pause").foregroundStyle(.secondary).font(.subheadline) } } diff --git a/PadelClub/Views/Shared/SelectablePlayerListView.swift b/PadelClub/Views/Shared/SelectablePlayerListView.swift index e795259..7b72993 100644 --- a/PadelClub/Views/Shared/SelectablePlayerListView.swift +++ b/PadelClub/Views/Shared/SelectablePlayerListView.swift @@ -17,10 +17,15 @@ struct SelectablePlayerListView: View { let allowSelection: Int let playerSelectionAction: PlayerSelectionAction? let contentUnavailableAction: ContentUnavailableAction? - + + @EnvironmentObject var dataStore: DataStore @StateObject private var searchViewModel: SearchViewModel @Environment(\.dismiss) var dismiss - @AppStorage("lastDataSource") var lastDataSource: String? + + var lastDataSource: String? { + dataStore.appSettings.lastDataSource + } + @AppStorage("importingFiles") var importingFiles: Bool = false @State private var searchText: String = "" diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift index 3ac8a52..0b5e2aa 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift @@ -57,7 +57,7 @@ struct TournamentClubSettingsView: View { TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount, max: 100) } } - .onDisappear { + .onChange(of: tournament.courtCount) { try? dataStore.tournaments.addOrUpdate(instance: tournament) } } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift index 094596a..0da60b0 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -58,7 +58,6 @@ struct TournamentGeneralSettingsView: View { } .focused($textFieldIsFocus) .scrollDismissesKeyboard(.immediately) - .navigationTitle("Réglages") .toolbarBackground(.visible, for: .navigationBar) .toolbar { ToolbarItem(placement: .keyboard) { @@ -67,9 +66,28 @@ struct TournamentGeneralSettingsView: View { } } } - .onDisappear { - try? dataStore.tournaments.addOrUpdate(instance: tournament) + .onChange(of: tournament.startDate) { + _save() } + .onChange(of: tournament.entryFee) { + _save() + } + .onChange(of: tournament.name) { + _save() + } + .onChange(of: [ + tournament.dayDuration, + tournament.federalCategory, + tournament.federalLevelCategory, + tournament.federalAgeCategory, + tournament.groupStageSortMode, + ]) { + _save() + } + } + + private func _save() { + try? dataStore.tournaments.addOrUpdate(instance: tournament) } } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift index c477c54..76c9dce 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift @@ -7,11 +7,97 @@ import SwiftUI -struct TournamentMatchFormatsSettingsView: View { +struct TournamentMatchFormatsSettingsView: View { + @Environment(NavigationViewModel.self) var navigation: NavigationViewModel + @Environment(Tournament.self) var tournament: Tournament + @EnvironmentObject var dataStore: DataStore + @State private var confirmUpdate: Bool = false + @State private var updateCompleted: Bool = false var body: some View { + @Bindable var tournament = tournament List { + if confirmUpdate { + RowButtonView("Modifier les matchs existants", role: .destructive) { + _updateAllFormat() + } + } + TournamentFormatSelectionView() + + Section { + LabeledContent { + StepperView(title: "minutes", count: $tournament.additionalEstimationDuration, step: 5) + } label: { + Text("Modifier les durées moyennes") + } + } footer: { + Text("Cette valeur est rajoutée ou soustraite aux valeurs par défaut. Par exemple, cela peut aider à mieux planifier un tournoi débutant ou jeune.") + } + + Section { + NavigationLink { + DurationSettingsView() + } label: { + Label("Estimation des durées moyennes", systemImage: "deskclock") + } + } } + .onChange(of: [tournament.roundFormat, + tournament.groupStageFormat, + tournament.loserRoundFormat, + ]) { + _save() + _confirmOrSave() + } + .onChange(of: tournament.additionalEstimationDuration) { + _save() + } + .onChange(of: dataStore.appSettings.matchFormatsDefaultDuration) { + _confirmOrSave() + } + .overlay(alignment: .bottom) { + if updateCompleted { + Label("Formats mis à jour", systemImage: "checkmark.circle.fill") + .toastFormatted() + .deferredRendering(for: .seconds(2)) + } + } + } + + private func _confirmOrSave() { + switch tournament.state() { + case .initial: + break + case .build: + confirmUpdate = true + } + } + + private func _updateAllFormat() { + updateCompleted = false + let groupStages = tournament.groupStages() + groupStages.forEach { groupStage in + groupStage.updateMatchFormat(tournament.groupStageMatchFormat) + } + + let allRounds = tournament.allRounds() + allRounds.forEach { round in + if round.isLoserBracket() { + round.updateMatchFormat(tournament.loserBracketMatchFormat) + } else { + round.updateMatchFormat(tournament.matchFormat) + } + } + try? dataStore.groupStages.addOrUpdate(contentOfs: groupStages) + try? dataStore.rounds.addOrUpdate(contentOfs: allRounds) + + confirmUpdate = false + updateCompleted = true + + } + + private func _save() { + try? dataStore.tournaments.addOrUpdate(instance: tournament) } } diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index 8e25141..e6d3f55 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -16,6 +16,7 @@ struct TournamentInitView: View { NavigationLink(value: Screen.settings) { LabeledContent { Text(tournament.settingsDescriptionLocalizedLabel()) + .tint(.master) } label: { LabelSettings() } @@ -28,6 +29,7 @@ struct TournamentInitView: View { NavigationLink(value: Screen.structure) { LabeledContent { Text(tournament.structureDescriptionLocalizedLabel()) + .tint(.master) } label: { LabelStructure() } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index c4cfda0..f13aafb 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -11,7 +11,10 @@ struct TournamentView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament var presentationContext: PresentationContext = .agenda - @AppStorage("lastDataSource") var lastDataSource: String? + + var lastDataSource: String? { + dataStore.appSettings.lastDataSource + } var _lastDataSourceDate: Date? { guard let lastDataSource else { return nil } @@ -91,6 +94,7 @@ struct TournamentView: View { .environment(tournament) }) .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) .toolbar { ToolbarItem(placement: .principal) { VStack {