fix micro storage
multistore
Razmig Sarkissian 2 years ago
parent f13a71675d
commit b9f2048546
  1. 28
      PadelClub.xcodeproj/project.pbxproj
  2. 36
      PadelClub/Data/AppSettings.swift
  3. 11
      PadelClub/Data/Club.swift
  4. 43
      PadelClub/Data/DataStore.swift
  5. 1
      PadelClub/Data/Event.swift
  6. 1
      PadelClub/Data/Federal/FederalTournament.swift
  7. 11
      PadelClub/Data/GroupStage.swift
  8. 8
      PadelClub/Data/Match.swift
  9. 9
      PadelClub/Data/MockData.swift
  10. 53
      PadelClub/Data/MonthData.swift
  11. 9
      PadelClub/Data/Round.swift
  12. 50
      PadelClub/Data/Tournament.swift
  13. 13
      PadelClub/Data/User.swift
  14. 14
      PadelClub/Manager/ContactManager.swift
  15. 16
      PadelClub/Manager/PadelRule.swift
  16. 2
      PadelClub/Manager/SourceFileManager.swift
  17. 13
      PadelClub/ViewModel/AppScreen.swift
  18. 8
      PadelClub/ViewModel/MatchScheduler.swift
  19. 1
      PadelClub/ViewModel/NavigationViewModel.swift
  20. 38
      PadelClub/Views/Calling/CallMessageCustomizationView.swift
  21. 14
      PadelClub/Views/Calling/CallSettingsView.swift
  22. 1
      PadelClub/Views/Club/ClubSearchView.swift
  23. 23
      PadelClub/Views/ClubView.swift
  24. 4
      PadelClub/Views/Components/Labels.swift
  25. 8
      PadelClub/Views/Components/StepperView.swift
  26. 82
      PadelClub/Views/ContentView.swift
  27. 6
      PadelClub/Views/Match/Components/MatchDateView.swift
  28. 6
      PadelClub/Views/Match/MatchDetailView.swift
  29. 6
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  30. 33
      PadelClub/Views/Navigation/MainView.swift
  31. 1
      PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift
  32. 48
      PadelClub/Views/Navigation/PadelClubView.swift
  33. 25
      PadelClub/Views/Navigation/Toolbox/DurationSettingsView.swift
  34. 67
      PadelClub/Views/Navigation/Toolbox/GlobalSettingsView.swift
  35. 50
      PadelClub/Views/Navigation/Toolbox/MatchFormatStorageView.swift
  36. 33
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  37. 2
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  38. 5
      PadelClub/Views/Planning/PlanningSettingsView.swift
  39. 3
      PadelClub/Views/Shared/MatchFormatPickerView.swift
  40. 9
      PadelClub/Views/Shared/SelectablePlayerListView.swift
  41. 2
      PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift
  42. 24
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  43. 88
      PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift
  44. 2
      PadelClub/Views/Tournament/TournamentInitView.swift
  45. 6
      PadelClub/Views/Tournament/TournamentView.swift

@ -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 = "<group>"; };
C425D4022B6D249D002A7B48 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
C425D4042B6D249E002A7B48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C425D4072B6D249E002A7B48 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
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 = "<group>"; };
C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
C4A47D622B6D3D6500ADC637 /* Club.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Club.swift; sourceTree = "<group>"; };
C4A47D732B72881F00ADC637 /* ClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubView.swift; sourceTree = "<group>"; };
C4A47D762B73789100ADC637 /* TournamentV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentV1.swift; sourceTree = "<group>"; };
C4A47D7A2B73C0F900ADC637 /* TournamentV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentV2.swift; sourceTree = "<group>"; };
C4A47D7C2B73CDC300ADC637 /* ClubV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubV1.swift; sourceTree = "<group>"; };
@ -327,6 +328,11 @@
FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentClubSettingsView.swift; sourceTree = "<group>"; };
FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentMatchFormatsSettingsView.swift; sourceTree = "<group>"; };
FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentGeneralSettingsView.swift; sourceTree = "<group>"; };
FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSettingsView.swift; sourceTree = "<group>"; };
FF025AE82BD1307E00A86CF8 /* MonthData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthData.swift; sourceTree = "<group>"; };
FF025AEC2BD1513700A86CF8 /* AppScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppScreen.swift; sourceTree = "<group>"; };
FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DurationSettingsView.swift; sourceTree = "<group>"; };
FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchFormatStorageView.swift; sourceTree = "<group>"; };
FF089EB32BB0020000F0AEC7 /* PlayerSexPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSexPickerView.swift; sourceTree = "<group>"; };
FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRowView.swift; sourceTree = "<group>"; };
FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerPopoverView.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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;

@ -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"
}
}

@ -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"
}
}

@ -24,8 +24,29 @@ class DataStore: ObservableObject {
fileprivate(set) var playerRegistrations: StoredCollection<PlayerRegistration>
fileprivate(set) var rounds: StoredCollection<Round>
fileprivate(set) var teamScores: StoredCollection<TeamScore>
fileprivate(set) var monthData: StoredCollection<MonthData>
fileprivate var _userStorage: OptionalStorage<User> = OptionalStorage<User>(fileName: "user.json")
fileprivate var _appSettingsStorage: MicroStorage<AppSettings> = 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<TournamentV1, TournamentV2>(version: 2))
// store.addMigration(Migration<TournamentV2, Tournament>(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)
}

@ -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

@ -203,6 +203,7 @@ enum TypePratique: String, Codable {
case beach = "BEACH"
case padel = "PADEL"
case tennis = "TENNIS"
case pickle = "PICKLE"
}
// MARK: - CategorieTournoi

@ -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"
}
}

@ -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"

@ -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)
}
}

@ -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"
}
}

@ -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())

@ -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"
}
}

@ -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"
}
}

@ -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 {

@ -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 {

@ -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? {

@ -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
}

@ -62,6 +62,7 @@ enum MatchSchedulerOption: Hashable {
class MatchScheduler {
static let shared = MatchScheduler()
var additionalEstimationDuration : Int = 0
var options: Set<MatchSchedulerOption> = 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 {

@ -10,6 +10,7 @@ import SwiftUI
@Observable
class NavigationViewModel {
var path = NavigationPath()
var selectedTab: TabDestination?
var agendaDestination: AgendaDestination? = .activity
var tournament: Tournament?
}

@ -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 {

@ -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") {

@ -339,6 +339,7 @@ enum Pratique: String, Codable {
case beach = "BEACH"
case padel = "PADEL"
case tennis = "TENNIS"
case pickle = "PICKLE"
}
// MARK: - ClubMarker

@ -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: ""))
}

@ -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)
}
}

@ -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
}
}

@ -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()
}

@ -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()
}

@ -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")

@ -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()
}

@ -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<ImportedPlayer>
@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()
}

@ -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()

@ -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
}
}

@ -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()
}

@ -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)
}
}

@ -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()
}
}
}

@ -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)
}

@ -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)
}
}
}

@ -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)
}

@ -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)
}
}

@ -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 = ""

@ -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)
}
}

@ -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)
}
}

@ -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)
}
}

@ -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()
}

@ -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 {

Loading…
Cancel
Save