From 5f6c01387af9ae0abf99525f850dbf6091c9a490 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 29 Aug 2024 12:54:47 +0200 Subject: [PATCH] add PadelClubData project --- PadelClub.xcodeproj/project.pbxproj | 194 ++--- .../contents.xcworkspacedata | 3 + PadelClub/Data/Club+Extensions.swift | 86 +++ PadelClub/Data/Club.swift | 248 ------- PadelClub/Data/Enum+Extensions.swift | 73 ++ PadelClub/Data/GroupStage+Extensions.swift | 55 ++ PadelClub/Data/Match+Extensions.swift | 89 +++ .../Data/MatchScheduler+Extensions.swift | 13 + PadelClub/Data/MonthData+Extensions.swift | 43 ++ PadelClub/Data/MonthData.swift | 101 --- .../Data/PlayerRegistration+Extensions.swift | 230 ++++++ PadelClub/Data/PlayerRegistration.swift | 574 --------------- PadelClub/Data/Round+Extensions.swift | 155 ++++ PadelClub/Data/SeedInterval+Extensions.swift | 20 + .../Data/TeamRegistration+Extensions.swift | 109 +++ PadelClub/Data/Tournament+Extensions.swift | 395 ++++++++++ PadelClub/Data/User+Extensions.swift | 21 + PadelClub/Data/User.swift | 266 ------- PadelClub/Extensions/Array+Extensions.swift | 13 - PadelClub/Extensions/Date+Extensions.swift | 56 +- .../Extensions/Sequence+Extensions.swift | 54 -- PadelClub/Extensions/String+Extensions.swift | 56 +- PadelClub/Utils/DisplayContext.swift | 6 - PadelClub/ViewModel/SearchViewModel.swift | 29 - PadelClub/Views/Cashier/CashierView.swift | 20 +- .../PadelClubData.xcodeproj/project.pbxproj | 695 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../UserInterfaceState.xcuserstate | Bin 0 -> 10402 bytes .../xcschemes/PadelClubData.xcscheme | 80 ++ .../xcschemes/xcschememanagement.plist | 27 + .../PadelClubData}/AppSettings.swift | 0 .../PadelClubData}/DataStore.swift | 2 +- .../PadelClubData/Date+Extensions.swift | 65 ++ .../PadelClubData/DisplayStyle.swift | 14 + .../PadelClubData}/Key.swift | 0 PadelClubData/PadelClubData/Model/Club.swift | 171 +++++ .../PadelClubData/Model}/Court.swift | 49 +- .../PadelClubData/Model}/DateInterval.swift | 15 +- .../PadelClubData/Model}/Event.swift | 79 +- .../PadelClubData/Model}/GroupStage.swift | 117 +-- .../PadelClubData/Model}/Match.swift | 270 +++---- .../PadelClubData/Model}/MatchScheduler.swift | 167 +++-- .../PadelClubData/Model}/MockData.swift | 0 .../PadelClubData/Model/MonthData.swift | 70 ++ .../Model/PlayerRegistration.swift | 363 +++++++++ .../PadelClubData/Model}/Purchase.swift | 0 .../PadelClubData/Model}/Round.swift | 221 ++---- .../Model}/TeamRegistration.swift | 345 ++++----- .../PadelClubData/Model}/TeamScore.swift | 83 ++- .../PadelClubData/Model}/Tournament.swift | 603 +++------------ PadelClubData/PadelClubData/Model/User.swift | 256 +++++++ .../PadelClubData}/MySortDescriptor.swift | 0 .../PadelClubData}/PListReader.swift | 0 .../PadelClubData.docc/PadelClubData.md | 13 + PadelClubData/PadelClubData/PadelClubData.h | 18 + .../PadelClubData}/PadelRule.swift | 143 ++-- .../PadelClubData}/Patcher.swift | 2 +- .../PadelClubData/PlayerFilterOption.swift | 37 + .../PadelClubData}/Screen.swift | 0 .../PadelClubData}/SeedInterval.swift | 7 - .../PadelClubData/Sequence+Extensions.swift | 81 ++ .../PadelClubData}/String+Crypto.swift | 0 .../PadelClubData/String+Extensions.swift | 63 ++ .../PadelClubData}/Subscription/Guard.swift | 0 .../Subscription/StoreItem.swift | 0 .../Subscription/StoreManager.swift | 0 .../PadelClubData}/TournamentStore.swift | 0 .../PadelClubData}/URL+Extensions.swift | 0 .../PadelClubDataTests.swift | 36 + 70 files changed, 4023 insertions(+), 2993 deletions(-) create mode 100644 PadelClub/Data/Club+Extensions.swift delete mode 100644 PadelClub/Data/Club.swift create mode 100644 PadelClub/Data/Enum+Extensions.swift create mode 100644 PadelClub/Data/GroupStage+Extensions.swift create mode 100644 PadelClub/Data/Match+Extensions.swift create mode 100644 PadelClub/Data/MatchScheduler+Extensions.swift create mode 100644 PadelClub/Data/MonthData+Extensions.swift delete mode 100644 PadelClub/Data/MonthData.swift create mode 100644 PadelClub/Data/PlayerRegistration+Extensions.swift delete mode 100644 PadelClub/Data/PlayerRegistration.swift create mode 100644 PadelClub/Data/Round+Extensions.swift create mode 100644 PadelClub/Data/SeedInterval+Extensions.swift create mode 100644 PadelClub/Data/TeamRegistration+Extensions.swift create mode 100644 PadelClub/Data/Tournament+Extensions.swift create mode 100644 PadelClub/Data/User+Extensions.swift delete mode 100644 PadelClub/Data/User.swift create mode 100644 PadelClubData/PadelClubData.xcodeproj/project.pbxproj create mode 100644 PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/xcuserdata/laurentmorvillier.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 PadelClubData/PadelClubData.xcodeproj/xcshareddata/xcschemes/PadelClubData.xcscheme create mode 100644 PadelClubData/PadelClubData.xcodeproj/xcuserdata/laurentmorvillier.xcuserdatad/xcschemes/xcschememanagement.plist rename {PadelClub/Data => PadelClubData/PadelClubData}/AppSettings.swift (100%) rename {PadelClub/Data => PadelClubData/PadelClubData}/DataStore.swift (99%) create mode 100644 PadelClubData/PadelClubData/Date+Extensions.swift create mode 100644 PadelClubData/PadelClubData/DisplayStyle.swift rename {PadelClub/Utils => PadelClubData/PadelClubData}/Key.swift (100%) create mode 100644 PadelClubData/PadelClubData/Model/Club.swift rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/Court.swift (65%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/DateInterval.swift (86%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/Event.swift (70%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/GroupStage.swift (86%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/Match.swift (81%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/MatchScheduler.swift (96%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/MockData.swift (100%) create mode 100644 PadelClubData/PadelClubData/Model/MonthData.swift create mode 100644 PadelClubData/PadelClubData/Model/PlayerRegistration.swift rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/Purchase.swift (100%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/Round.swift (76%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/TeamRegistration.swift (64%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/TeamScore.swift (58%) rename {PadelClub/Data => PadelClubData/PadelClubData/Model}/Tournament.swift (74%) create mode 100644 PadelClubData/PadelClubData/Model/User.swift rename {PadelClub/Extensions => PadelClubData/PadelClubData}/MySortDescriptor.swift (100%) rename {PadelClub/Utils => PadelClubData/PadelClubData}/PListReader.swift (100%) create mode 100755 PadelClubData/PadelClubData/PadelClubData.docc/PadelClubData.md create mode 100644 PadelClubData/PadelClubData/PadelClubData.h rename {PadelClub/Utils => PadelClubData/PadelClubData}/PadelRule.swift (96%) rename {PadelClub/Utils => PadelClubData/PadelClubData}/Patcher.swift (97%) create mode 100644 PadelClubData/PadelClubData/PlayerFilterOption.swift rename {PadelClub/ViewModel => PadelClubData/PadelClubData}/Screen.swift (100%) rename {PadelClub/ViewModel => PadelClubData/PadelClubData}/SeedInterval.swift (85%) create mode 100644 PadelClubData/PadelClubData/Sequence+Extensions.swift rename {PadelClub/Extensions => PadelClubData/PadelClubData}/String+Crypto.swift (100%) create mode 100644 PadelClubData/PadelClubData/String+Extensions.swift rename {PadelClub => PadelClubData/PadelClubData}/Subscription/Guard.swift (100%) rename {PadelClub => PadelClubData/PadelClubData}/Subscription/StoreItem.swift (100%) rename {PadelClub => PadelClubData/PadelClubData}/Subscription/StoreManager.swift (100%) rename {PadelClub/Data => PadelClubData/PadelClubData}/TournamentStore.swift (100%) rename {PadelClub/Extensions => PadelClubData/PadelClubData}/URL+Extensions.swift (100%) create mode 100644 PadelClubData/PadelClubDataTests/PadelClubDataTests.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index af151d8..1809c2e 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -11,6 +11,20 @@ C411C9C32BEBA453003017AD /* ServerDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C411C9C22BEBA453003017AD /* ServerDataTests.swift */; }; C411C9C92BF219CB003017AD /* UserDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C411C9C82BF219CB003017AD /* UserDataTests.swift */; }; C411C9D02BF38F41003017AD /* TokenExemptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C411C9CF2BF38F41003017AD /* TokenExemptionTests.swift */; }; + C423307C2C7E1290001DABF5 /* PadelClubData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C423307B2C7E1290001DABF5 /* PadelClubData.framework */; }; + C423307D2C7E1290001DABF5 /* PadelClubData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C423307B2C7E1290001DABF5 /* PadelClubData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C42330A62C7E13E2001DABF5 /* Club+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330A52C7E13E2001DABF5 /* Club+Extensions.swift */; }; + C42330AD2C7E1553001DABF5 /* GroupStage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330AC2C7E1553001DABF5 /* GroupStage+Extensions.swift */; }; + C42330AF2C7E159F001DABF5 /* Match+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330AE2C7E159F001DABF5 /* Match+Extensions.swift */; }; + C42330B12C7E16C1001DABF5 /* MonthData+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330B02C7E16C1001DABF5 /* MonthData+Extensions.swift */; }; + C42330B32C7E16F9001DABF5 /* PlayerRegistration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330B22C7E16F9001DABF5 /* PlayerRegistration+Extensions.swift */; }; + C42330B52C7E1724001DABF5 /* Round+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330B42C7E1724001DABF5 /* Round+Extensions.swift */; }; + C42330B92C7E189A001DABF5 /* Enum+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330B82C7E189A001DABF5 /* Enum+Extensions.swift */; }; + C42330C32C7E21EC001DABF5 /* SeedInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330C22C7E21EC001DABF5 /* SeedInterval+Extensions.swift */; }; + C42330C82C7E2542001DABF5 /* TeamRegistration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330C72C7E2542001DABF5 /* TeamRegistration+Extensions.swift */; }; + C42330D02C7E296B001DABF5 /* MatchScheduler+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330CF2C7E296B001DABF5 /* MatchScheduler+Extensions.swift */; }; + C42330D82C7E2AB4001DABF5 /* Tournament+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330D72C7E2AB4001DABF5 /* Tournament+Extensions.swift */; }; + C42330E82C7F155C001DABF5 /* User+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330E72C7F155C001DABF5 /* User+Extensions.swift */; }; C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4002B6D249D002A7B48 /* PadelClubApp.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 */; }; @@ -20,36 +34,21 @@ C4489BE22C05BF5000043F3D /* DebugSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4489BE12C05BF5000043F3D /* DebugSettingsView.swift */; }; C44B79112BBDA63A00906534 /* Locale+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B79102BBDA63A00906534 /* Locale+Extensions.swift */; }; C45BAE3B2BC6DF10002EEC8A /* SyncedProducts.storekit in Resources */ = {isa = PBXBuildFile; fileRef = C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */; }; - C45BAE442BCA753E002EEC8A /* Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45BAE432BCA753E002EEC8A /* Purchase.swift */; }; C4607A7D2C04DDE2004CB781 /* APICallsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4607A7C2C04DDE2004CB781 /* APICallsListView.swift */; }; C493B37E2C10AD3600862481 /* LoadingViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C493B37D2C10AD3600862481 /* LoadingViewModifier.swift */; }; C49EF0192BD694290077B5AA /* PurchaseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0182BD694290077B5AA /* PurchaseListView.swift */; }; C49EF01B2BD6A1E80077B5AA /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF01A2BD6A1E80077B5AA /* URLs.swift */; }; C49EF0262BD80AE80077B5AA /* SubscriptionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0252BD80AE80077B5AA /* SubscriptionInfoView.swift */; }; - C49EF0392BDFF4600077B5AA /* LeStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; }; - C49EF03A2BDFF4600077B5AA /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C49EF03C2BE15AF80077B5AA /* String+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */; }; C49EF0422BE23BF50077B5AA /* PaymentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0412BE23BF50077B5AA /* PaymentTests.swift */; }; - C49EF0442BE286780077B5AA /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0432BE286780077B5AA /* Key.swift */; }; - 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 */; }; C4A47D872B7BA36D00ADC637 /* UserCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D862B7BA36D00ADC637 /* UserCreationView.swift */; }; C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D892B7BBB6500ADC637 /* SubscriptionView.swift */; }; - C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8D2B7BBBEC00ADC637 /* StoreManager.swift */; }; - C4A47D912B7BBBEC00ADC637 /* Guard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8E2B7BBBEC00ADC637 /* Guard.swift */; }; - C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8F2B7BBBEC00ADC637 /* StoreItem.swift */; }; C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */; }; C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA52B83948E00ADC637 /* LoginView.swift */; }; C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA82B85F82100ADC637 /* ChangePasswordView.swift */; }; - C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* User.swift */; }; C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DB22B86387500ADC637 /* AccountView.swift */; }; - C4B3A1552C2581DA0078EAA8 /* Patcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B3A1542C2581DA0078EAA8 /* Patcher.swift */; }; C4C01D982C481C0C0059087C /* CapsuleViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C01D972C481C0C0059087C /* CapsuleViewModifier.swift */; }; C4EC6F572BE92CAC000CEAB4 /* local.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4EC6F562BE92CAC000CEAB4 /* local.plist */; }; - C4EC6F592BE92D88000CEAB4 /* PListReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC6F582BE92D88000CEAB4 /* PListReader.swift */; }; C4FC2E272C2AABC90021F3BF /* PasswordField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E262C2AABC90021F3BF /* PasswordField.swift */; }; - C4FC2E2B2C2C0E4D0021F3BF /* TournamentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E2A2C2C0E4D0021F3BF /* TournamentStore.swift */; }; FF025AD82BD0C10F00A86CF8 /* TeamHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AD72BD0C10F00A86CF8 /* TeamHeaderView.swift */; }; FF025ADB2BD0C2D000A86CF8 /* MatchTeamDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */; }; FF025ADD2BD0C94300A86CF8 /* FooterButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */; }; @@ -58,7 +57,6 @@ 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 */; }; @@ -86,7 +84,6 @@ FF1CBC222BB53E590036DAAB /* FederalTournamentHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC212BB53E590036DAAB /* FederalTournamentHolder.swift */; }; FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC202BB53E590036DAAB /* ClubHolder.swift */; }; FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */; }; - FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5522BAB354A00FD8220 /* MockData.swift */; }; FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */; }; FF1DC5572BAB3AED00FD8220 /* ClubsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5562BAB3AED00FD8220 /* ClubsView.swift */; }; FF1DC5592BAB767000FD8220 /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5582BAB767000FD8220 /* Tips.swift */; }; @@ -112,7 +109,6 @@ FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; - FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */; }; FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74F52B919E45004CFE0E /* UmpireView.swift */; }; FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */; }; FF44421C2BE39FA2008BBF0B /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */; }; @@ -149,7 +145,6 @@ FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; - FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */; }; FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */; }; FF6EC9062B947A1000EA7F5A /* NetworkManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9052B947A1000EA7F5A /* NetworkManagerError.swift */; }; @@ -164,10 +159,8 @@ FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; }; - FF8F26382BAD523300650388 /* PadelRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26352BAD523300650388 /* PadelRule.swift */; }; FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263A2BAD528600650388 /* EventCreationView.swift */; }; FF8F263D2BAD627A00650388 /* TournamentConfiguratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */; }; - FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263E2BAD7D5C00650388 /* Event.swift */; }; FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26402BADFC8700650388 /* TournamentInitView.swift */; }; FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */; }; FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */; }; @@ -190,12 +183,6 @@ FF9268092BCEDC2C0080F940 /* CallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268082BCEDC2C0080F940 /* CallView.swift */; }; FF92680B2BCEE3E10080F940 /* ContactManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF92680A2BCEE3E10080F940 /* ContactManager.swift */; }; FF92680D2BCEE5EA0080F940 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF92680C2BCEE5EA0080F940 /* NetworkMonitor.swift */; }; - FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CE72BAEC70100A9A3BD /* GroupStage.swift */; }; - FF967CEC2BAECB9900A9A3BD /* Match.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEB2BAECB9900A9A3BD /* Match.swift */; }; - FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CED2BAECBD700A9A3BD /* Round.swift */; }; - FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */; }; - FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */; }; - FF967CF42BAECC0B00A9A3BD /* TeamRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */; }; FF967CF62BAED51600A9A3BD /* TournamentRunningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */; }; FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF72BAEDF0000A9A3BD /* Labels.swift */; }; FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */; }; @@ -214,7 +201,6 @@ FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */; }; FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; }; - FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; }; FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; }; FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; }; FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */; }; @@ -230,7 +216,6 @@ FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.swift */; }; FFC83D512BB8087E00750834 /* RoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D502BB8087E00750834 /* RoundView.swift */; }; FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */; }; - FFC91B012BD85C2F00B29808 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; }; FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.swift */; }; FFCB74132C4625BB008384D0 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */; }; FFCB74172C480411008384D0 /* CopyPasteButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */; }; @@ -247,8 +232,6 @@ FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */; }; FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */; }; FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; }; - FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; }; - FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; }; FFE103082C353B7600684FC9 /* EventClubSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103072C353B7600684FC9 /* EventClubSettingsView.swift */; }; FFE103102C366DCD00684FC9 /* EditSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */; }; @@ -258,13 +241,11 @@ FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; }; FFF03C942BD91D0C00B516FC /* ButtonValidateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */; }; - FFF116E12BD2A9B600A33B06 /* DateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E02BD2A9B600A33B06 /* DateInterval.swift */; }; FFF116E32BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */; }; FFF1D2CB2C4A22B200C8D33D /* ExportFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF1D2CA2C4A22B200C8D33D /* ExportFormat.swift */; }; FFF527D62BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */; }; FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */; }; FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD32B92392C008466FA /* SourceFileManager.swift */; }; - FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD52B923960008466FA /* URL+Extensions.swift */; }; FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD82B923F3C008466FA /* String+Extensions.swift */; }; FFF8ACDB2B923F48008466FA /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */; }; FFF964502BC25E3700EEF017 /* PlanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF9644F2BC25E3700EEF017 /* PlanningView.swift */; }; @@ -293,13 +274,13 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - FF2BE4892B85E27400592328 /* Embed Frameworks */ = { + C423307E2C7E1290001DABF5 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - C49EF03A2BDFF4600077B5AA /* LeStorage.framework in Embed Frameworks */, + C423307D2C7E1290001DABF5 /* PadelClubData.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -312,6 +293,19 @@ C411C9C82BF219CB003017AD /* UserDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDataTests.swift; sourceTree = ""; }; C411C9CC2BF21DAF003017AD /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; C411C9CF2BF38F41003017AD /* TokenExemptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenExemptionTests.swift; sourceTree = ""; }; + C423307B2C7E1290001DABF5 /* PadelClubData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PadelClubData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C42330A52C7E13E2001DABF5 /* Club+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Club+Extensions.swift"; sourceTree = ""; }; + C42330AC2C7E1553001DABF5 /* GroupStage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GroupStage+Extensions.swift"; sourceTree = ""; }; + C42330AE2C7E159F001DABF5 /* Match+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Match+Extensions.swift"; sourceTree = ""; }; + C42330B02C7E16C1001DABF5 /* MonthData+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MonthData+Extensions.swift"; sourceTree = ""; }; + C42330B22C7E16F9001DABF5 /* PlayerRegistration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlayerRegistration+Extensions.swift"; sourceTree = ""; }; + C42330B42C7E1724001DABF5 /* Round+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Round+Extensions.swift"; sourceTree = ""; }; + C42330B82C7E189A001DABF5 /* Enum+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Enum+Extensions.swift"; sourceTree = ""; }; + C42330C22C7E21EC001DABF5 /* SeedInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SeedInterval+Extensions.swift"; sourceTree = ""; }; + C42330C72C7E2542001DABF5 /* TeamRegistration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TeamRegistration+Extensions.swift"; sourceTree = ""; }; + C42330CF2C7E296B001DABF5 /* MatchScheduler+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MatchScheduler+Extensions.swift"; sourceTree = ""; }; + C42330D72C7E2AB4001DABF5 /* Tournament+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tournament+Extensions.swift"; sourceTree = ""; }; + C42330E72C7F155C001DABF5 /* User+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "User+Extensions.swift"; sourceTree = ""; }; 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 = ""; }; C425D4042B6D249E002A7B48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -324,35 +318,22 @@ C4489BE12C05BF5000043F3D /* DebugSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugSettingsView.swift; sourceTree = ""; }; C44B79102BBDA63A00906534 /* Locale+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Extensions.swift"; sourceTree = ""; }; C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = SyncedProducts.storekit; sourceTree = ""; }; - C45BAE432BCA753E002EEC8A /* Purchase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Purchase.swift; sourceTree = ""; }; C4607A7C2C04DDE2004CB781 /* APICallsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICallsListView.swift; sourceTree = ""; }; C493B37D2C10AD3600862481 /* LoadingViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewModifier.swift; sourceTree = ""; }; C49EF0182BD694290077B5AA /* PurchaseListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseListView.swift; sourceTree = ""; }; C49EF01A2BD6A1E80077B5AA /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; }; C49EF0252BD80AE80077B5AA /* SubscriptionInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionInfoView.swift; sourceTree = ""; }; C49EF0372BDFF3000077B5AA /* LeStorage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LeStorage.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Crypto.swift"; sourceTree = ""; }; C49EF0412BE23BF50077B5AA /* PaymentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentTests.swift; sourceTree = ""; }; - C49EF0432BE286780077B5AA /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = ""; }; - 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 = ""; }; C4A47D862B7BA36D00ADC637 /* UserCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCreationView.swift; sourceTree = ""; }; C4A47D892B7BBB6500ADC637 /* SubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionView.swift; sourceTree = ""; }; - C4A47D8D2B7BBBEC00ADC637 /* StoreManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreManager.swift; sourceTree = ""; }; - C4A47D8E2B7BBBEC00ADC637 /* Guard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Guard.swift; sourceTree = ""; }; - C4A47D8F2B7BBBEC00ADC637 /* StoreItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreItem.swift; sourceTree = ""; }; C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepperView.swift; sourceTree = ""; }; C4A47DA52B83948E00ADC637 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; C4A47DA82B85F82100ADC637 /* ChangePasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangePasswordView.swift; sourceTree = ""; }; - C4A47DAC2B85FCCD00ADC637 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; C4A47DB22B86387500ADC637 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; - C4B3A1542C2581DA0078EAA8 /* Patcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Patcher.swift; sourceTree = ""; }; C4C01D972C481C0C0059087C /* CapsuleViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleViewModifier.swift; sourceTree = ""; }; C4EC6F562BE92CAC000CEAB4 /* local.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = local.plist; sourceTree = ""; }; - C4EC6F582BE92D88000CEAB4 /* PListReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PListReader.swift; sourceTree = ""; }; C4FC2E262C2AABC90021F3BF /* PasswordField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordField.swift; sourceTree = ""; }; - C4FC2E2A2C2C0E4D0021F3BF /* TournamentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStore.swift; sourceTree = ""; }; FF025AD72BD0C10F00A86CF8 /* TeamHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamHeaderView.swift; sourceTree = ""; }; FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchTeamDetailView.swift; sourceTree = ""; }; FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterButtonView.swift; sourceTree = ""; }; @@ -361,7 +342,6 @@ 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 = ""; }; @@ -431,7 +411,6 @@ FF1CBC202BB53E590036DAAB /* ClubHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubHolder.swift; sourceTree = ""; }; FF1CBC212BB53E590036DAAB /* FederalTournamentHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournamentHolder.swift; sourceTree = ""; }; FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubDetailView.swift; sourceTree = ""; }; - FF1DC5522BAB354A00FD8220 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = ""; }; FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateClubView.swift; sourceTree = ""; }; FF1DC5562BAB3AED00FD8220 /* ClubsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubsView.swift; sourceTree = ""; }; FF1DC5582BAB767000FD8220 /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = ""; }; @@ -457,7 +436,6 @@ FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = ""; }; FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; - FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduler.swift; sourceTree = ""; }; FF3F74F52B919E45004CFE0E /* UmpireView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UmpireView.swift; sourceTree = ""; }; FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDestination.swift; sourceTree = ""; }; FF4AB6B42B9248200002987F /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; @@ -493,7 +471,6 @@ FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFilterView.swift; sourceTree = ""; }; FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = ""; }; FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = ""; }; - FF6EC8FD2B94792300EA7F5A /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = ""; }; FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationContext.swift; sourceTree = ""; }; FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Extensions.swift"; sourceTree = ""; }; FF6EC9052B947A1000EA7F5A /* NetworkManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManagerError.swift; sourceTree = ""; }; @@ -508,10 +485,8 @@ FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizedTournamentView.swift; sourceTree = ""; }; FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; FF8E1CE52C006E0200184680 /* Alphabet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alphabet.swift; sourceTree = ""; }; - FF8F26352BAD523300650388 /* PadelRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelRule.swift; sourceTree = ""; }; FF8F263A2BAD528600650388 /* EventCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCreationView.swift; sourceTree = ""; }; FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentConfiguratorView.swift; sourceTree = ""; }; - FF8F263E2BAD7D5C00650388 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; FF8F26402BADFC8700650388 /* TournamentInitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentInitView.swift; sourceTree = ""; }; FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSettingsView.swift; sourceTree = ""; }; FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentDurationManagerView.swift; sourceTree = ""; }; @@ -534,12 +509,6 @@ FF9268082BCEDC2C0080F940 /* CallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallView.swift; sourceTree = ""; }; FF92680A2BCEE3E10080F940 /* ContactManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactManager.swift; sourceTree = ""; }; FF92680C2BCEE5EA0080F940 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; - FF967CE72BAEC70100A9A3BD /* GroupStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStage.swift; sourceTree = ""; }; - FF967CEB2BAECB9900A9A3BD /* Match.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Match.swift; sourceTree = ""; }; - FF967CED2BAECBD700A9A3BD /* Round.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Round.swift; sourceTree = ""; }; - FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamScore.swift; sourceTree = ""; }; - FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRegistration.swift; sourceTree = ""; }; - FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRegistration.swift; sourceTree = ""; }; FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentRunningView.swift; sourceTree = ""; }; FF967CF72BAEDF0000A9A3BD /* Labels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Labels.swift; sourceTree = ""; }; FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageView.swift; sourceTree = ""; }; @@ -559,7 +528,6 @@ FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentBroadcastRowView.swift; sourceTree = ""; }; FFB9C8702BBADDE200A0EF4F /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = ""; }; - FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = ""; }; FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamView.swift; sourceTree = ""; }; FFBF065D2BBD8040009D6715 /* MatchListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchListView.swift; sourceTree = ""; }; FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = ""; }; @@ -575,7 +543,6 @@ FFC83D4E2BB807D100750834 /* RoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundsView.swift; sourceTree = ""; }; FFC83D502BB8087E00750834 /* RoundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundView.swift; sourceTree = ""; }; FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FortuneWheelView.swift; sourceTree = ""; }; - FFC91B002BD85C2F00B29808 /* Court.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Court.swift; sourceTree = ""; }; FFC91B022BD85E2400B29808 /* CourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtView.swift; sourceTree = ""; }; FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageSettingsView.swift; sourceTree = ""; }; FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyPasteButtonView.swift; sourceTree = ""; }; @@ -592,8 +559,6 @@ FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDescriptor.swift; sourceTree = ""; }; FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = ""; }; FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; - FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; - FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = ""; }; FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = ""; }; FFE103072C353B7600684FC9 /* EventClubSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EventClubSettingsView.swift; path = PadelClub/Views/Tournament/Screen/Components/EventClubSettingsView.swift; sourceTree = SOURCE_ROOT; }; FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSharingView.swift; sourceTree = ""; }; @@ -604,13 +569,11 @@ FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; FFF0241F2BF48B1A001F14B4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonValidateView.swift; sourceTree = ""; }; - FFF116E02BD2A9B600A33B06 /* DateInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateInterval.swift; sourceTree = ""; }; FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtAvailabilitySettingsView.swift; sourceTree = ""; }; FFF1D2CA2C4A22B200C8D33D /* ExportFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportFormat.swift; sourceTree = ""; }; FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduleEditorView.swift; sourceTree = ""; }; FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalPlayer.swift; sourceTree = ""; }; FFF8ACD32B92392C008466FA /* SourceFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceFileManager.swift; sourceTree = ""; }; - FFF8ACD52B923960008466FA /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = ""; }; FFF8ACD82B923F3C008466FA /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; FFF9644F2BC25E3700EEF017 /* PlanningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanningView.swift; sourceTree = ""; }; @@ -626,9 +589,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C423307C2C7E1290001DABF5 /* PadelClubData.framework in Frameworks */, FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */, FF92660D2C241CE0002361A4 /* Zip in Frameworks */, - C49EF0392BDFF4600077B5AA /* LeStorage.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -649,16 +612,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - C42330492C7E095F001DABF5 /* Subscription */ = { - isa = PBXGroup; - children = ( - C4A47D8E2B7BBBEC00ADC637 /* Guard.swift */, - C4A47D8D2B7BBBEC00ADC637 /* StoreManager.swift */, - C4A47D8F2B7BBBEC00ADC637 /* StoreItem.swift */, - ); - path = Subscription; - sourceTree = ""; - }; C425D3F42B6D249D002A7B48 = { isa = PBXGroup; children = ( @@ -693,7 +646,6 @@ FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */, C4A47D722B72881500ADC637 /* Views */, FF3F74FD2B91A087004CFE0E /* ViewModel */, - C42330492C7E095F001DABF5 /* Subscription */, C4A47D5F2B6D3B2D00ADC637 /* Data */, FFF8ACD02B9238A2008466FA /* Utils */, FFF8ACD72B923F26008466FA /* Extensions */, @@ -739,6 +691,7 @@ C425D4592B6D255B002A7B48 /* Frameworks */ = { isa = PBXGroup; children = ( + C423307B2C7E1290001DABF5 /* PadelClubData.framework */, C49EF0372BDFF3000077B5AA /* LeStorage.framework */, ); name = Frameworks; @@ -748,27 +701,20 @@ isa = PBXGroup; children = ( C411C9CC2BF21DAF003017AD /* README.md */, - FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */, - C4A47D622B6D3D6500ADC637 /* Club.swift */, - FFC91B002BD85C2F00B29808 /* Court.swift */, - C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */, - FFF116E02BD2A9B600A33B06 /* DateInterval.swift */, - FF8F263E2BAD7D5C00650388 /* Event.swift */, - FF967CE72BAEC70100A9A3BD /* GroupStage.swift */, - FF967CEB2BAECB9900A9A3BD /* Match.swift */, - FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */, - FF1DC5522BAB354A00FD8220 /* MockData.swift */, - FF025AE82BD1307E00A86CF8 /* MonthData.swift */, - FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */, - C45BAE432BCA753E002EEC8A /* Purchase.swift */, - FF967CED2BAECBD700A9A3BD /* Round.swift */, - FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */, - FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */, - C4A47D592B6D383C00ADC637 /* Tournament.swift */, - C4FC2E2A2C2C0E4D0021F3BF /* TournamentStore.swift */, - C4A47DAC2B85FCCD00ADC637 /* User.swift */, FF6EC9012B94799200EA7F5A /* Coredata */, FF6EC9022B9479B900EA7F5A /* Federal */, + C42330A52C7E13E2001DABF5 /* Club+Extensions.swift */, + C42330AC2C7E1553001DABF5 /* GroupStage+Extensions.swift */, + C42330AE2C7E159F001DABF5 /* Match+Extensions.swift */, + C42330B02C7E16C1001DABF5 /* MonthData+Extensions.swift */, + C42330B22C7E16F9001DABF5 /* PlayerRegistration+Extensions.swift */, + C42330B42C7E1724001DABF5 /* Round+Extensions.swift */, + C42330B82C7E189A001DABF5 /* Enum+Extensions.swift */, + C42330C22C7E21EC001DABF5 /* SeedInterval+Extensions.swift */, + C42330C72C7E2542001DABF5 /* TeamRegistration+Extensions.swift */, + C42330D72C7E2AB4001DABF5 /* Tournament+Extensions.swift */, + C42330CF2C7E296B001DABF5 /* MatchScheduler+Extensions.swift */, + C42330E72C7F155C001DABF5 /* User+Extensions.swift */, ); path = Data; sourceTree = ""; @@ -1071,7 +1017,6 @@ FF3F74FD2B91A087004CFE0E /* ViewModel */ = { isa = PBXGroup; children = ( - FF6EC8FD2B94792300EA7F5A /* Screen.swift */, FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */, FF7091652B90F0B000AB08DA /* TabDestination.swift */, FF025AEC2BD1513700A86CF8 /* AppScreen.swift */, @@ -1080,7 +1025,6 @@ FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */, FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */, FFB9C8702BBADDE200A0EF4F /* Selectable.swift */, - FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */, FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */, FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */, FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */, @@ -1339,12 +1283,8 @@ FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */, FF1F4B722BFA00FB000B4573 /* HtmlGenerator.swift */, FF1F4B732BFA00FC000B4573 /* HtmlService.swift */, - C49EF0432BE286780077B5AA /* Key.swift */, FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */, FF92680C2BCEE5EA0080F940 /* NetworkMonitor.swift */, - FF8F26352BAD523300650388 /* PadelRule.swift */, - C4B3A1542C2581DA0078EAA8 /* Patcher.swift */, - C4EC6F582BE92D88000CEAB4 /* PListReader.swift */, FFF8ACD32B92392C008466FA /* SourceFileManager.swift */, FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */, FF1DC5582BAB767000FD8220 /* Tips.swift */, @@ -1363,12 +1303,9 @@ FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */, FF6EC9082B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift */, C44B79102BBDA63A00906534 /* Locale+Extensions.swift */, - FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */, FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */, FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */, - C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */, FFF8ACD82B923F3C008466FA /* String+Extensions.swift */, - FFF8ACD52B923960008466FA /* URL+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -1401,7 +1338,7 @@ C425D3F92B6D249D002A7B48 /* Sources */, C425D3FA2B6D249D002A7B48 /* Frameworks */, C425D3FB2B6D249D002A7B48 /* Resources */, - FF2BE4892B85E27400592328 /* Embed Frameworks */, + C423307E2C7E1290001DABF5 /* Embed Frameworks */, ); buildRules = ( ); @@ -1549,11 +1486,9 @@ C4A47D872B7BA36D00ADC637 /* UserCreationView.swift in Sources */, FF7091662B90F0B000AB08DA /* TabDestination.swift in Sources */, FF9267F82BCE78C70080F940 /* CashierView.swift in Sources */, - FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */, FF5D30532BD94E2E00F2B93D /* PlayerHolder.swift in Sources */, FF11628C2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift in Sources */, FF53FBB82BFB302B0051D4C3 /* ClubCourtSetupView.swift in Sources */, - C4B3A1552C2581DA0078EAA8 /* Patcher.swift in Sources */, FF089EBF2BB0B14600F0AEC7 /* FileImportView.swift in Sources */, C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */, FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */, @@ -1566,7 +1501,6 @@ FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */, FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */, FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */, - FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */, FF025AE12BD0EB9000A86CF8 /* TournamentClubSettingsView.swift in Sources */, FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */, FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */, @@ -1574,23 +1508,23 @@ FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */, FF025ADF2BD0CE0A00A86CF8 /* TeamWeightView.swift in Sources */, FF9268012BCE94920080F940 /* SeedsCallingView.swift in Sources */, + C42330D02C7E296B001DABF5 /* MatchScheduler+Extensions.swift in Sources */, FF9268092BCEDC2C0080F940 /* CallView.swift in Sources */, FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */, FFE103102C366DCD00684FC9 /* EditSharingView.swift in Sources */, FF7091682B90F79F00AB08DA /* TournamentCellView.swift in Sources */, FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */, + C42330D82C7E2AB4001DABF5 /* Tournament+Extensions.swift in Sources */, FF9267FA2BCE78EC0080F940 /* CashierDetailView.swift in Sources */, FFE103082C353B7600684FC9 /* EventClubSettingsView.swift in Sources */, C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */, FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */, FFCD16B32C3E5E590092707B /* TeamsCallingView.swift in Sources */, FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */, - FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */, FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */, FF1162852BD00279000C4809 /* PlayerDetailView.swift in Sources */, FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */, FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */, - FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */, FF0EC5202BB16F680056B6D1 /* SwiftParser.swift in Sources */, C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */, FFC83D512BB8087E00750834 /* RoundView.swift in Sources */, @@ -1601,8 +1535,10 @@ FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, C4C01D982C481C0C0059087C /* CapsuleViewModifier.swift in Sources */, FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */, + C42330B12C7E16C1001DABF5 /* MonthData+Extensions.swift in Sources */, FFF964552BC266CF00EEF017 /* SchedulerView.swift in Sources */, FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */, + C42330AF2C7E159F001DABF5 /* Match+Extensions.swift in Sources */, FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */, FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */, FF5D30562BD95B1100F2B93D /* OngoingView.swift in Sources */, @@ -1619,34 +1555,29 @@ C49EF0262BD80AE80077B5AA /* SubscriptionInfoView.swift in Sources */, FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */, FF7091622B90F04300AB08DA /* TournamentOrganizerView.swift in Sources */, + C42330E82C7F155C001DABF5 /* User+Extensions.swift in Sources */, FF92680D2BCEE5EA0080F940 /* NetworkMonitor.swift in Sources */, FF967CF62BAED51600A9A3BD /* TournamentRunningView.swift in Sources */, FF8F264D2BAE0B4100650388 /* TournamentDatePickerView.swift in Sources */, FFF116E32BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift in Sources */, + C42330AD2C7E1553001DABF5 /* GroupStage+Extensions.swift in Sources */, FFE8C2C02C7601E80046B243 /* ConfirmButtonView.swift in Sources */, C4FC2E272C2AABC90021F3BF /* PasswordField.swift in Sources */, FF967D042BAEF1C300A9A3BD /* MatchRowView.swift in Sources */, C44B79112BBDA63A00906534 /* Locale+Extensions.swift in Sources */, FF1F4B742BFA00FC000B4573 /* HtmlService.swift in Sources */, - FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */, FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */, - C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */, FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */, - FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */, FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */, FF1162872BD004AD000C4809 /* EditingTeamView.swift in Sources */, FF6EC9062B947A1000EA7F5A /* NetworkManagerError.swift in Sources */, - C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */, - C4FC2E2B2C2C0E4D0021F3BF /* TournamentStore.swift in Sources */, FF5647132C0B6F390081F995 /* LoserRoundSettingsView.swift in Sources */, FF3795662B9399AA004EA093 /* Persistence.swift in Sources */, FFCF76072C3BE9BC006C8C3D /* CloseDatePicker.swift in Sources */, FF1DF49B2BD8D23900822FA0 /* BarButtonView.swift in Sources */, FFF964502BC25E3700EEF017 /* PlanningView.swift in Sources */, - FF967CEC2BAECB9900A9A3BD /* Match.swift in Sources */, FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */, FF1CBC222BB53E590036DAAB /* FederalTournamentHolder.swift in Sources */, - C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */, FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */, FF025AD82BD0C10F00A86CF8 /* TeamHeaderView.swift in Sources */, FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */, @@ -1655,30 +1586,26 @@ FF8F263D2BAD627A00650388 /* TournamentConfiguratorView.swift in Sources */, FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */, FF558C632C6CDD020071F9AE /* UnderlineView.swift in Sources */, - FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */, FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */, FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */, FF025ADB2BD0C2D000A86CF8 /* MatchTeamDetailView.swift in Sources */, FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */, - FFF116E12BD2A9B600A33B06 /* DateInterval.swift in Sources */, FF8F26542BAE1E4400650388 /* TableStructureView.swift in Sources */, - C45BAE442BCA753E002EEC8A /* Purchase.swift in Sources */, - FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */, - FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */, + C42330C32C7E21EC001DABF5 /* SeedInterval+Extensions.swift in Sources */, FF5BAF6E2BE0B3C8008B4B7E /* FederalDataViewModel.swift in Sources */, FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */, FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */, FFCFC0162BBC5A4C00B82851 /* SetInputView.swift in Sources */, FFF03C942BD91D0C00B516FC /* ButtonValidateView.swift in Sources */, FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */, + C42330B52C7E1724001DABF5 /* Round+Extensions.swift in Sources */, FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */, FF9268032BCE94A30080F940 /* GroupStageCallingView.swift in Sources */, - C49EF0442BE286780077B5AA /* Key.swift in Sources */, FF11627D2BCF941A000C4809 /* CashierSettingsView.swift in Sources */, FFFCDE0E2BCC833600317DEF /* LoserRoundScheduleEditorView.swift in Sources */, - C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */, FF6EC90B2B947AC000EA7F5A /* Array+Extensions.swift in Sources */, FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */, + C42330C82C7E2542001DABF5 /* TeamRegistration+Extensions.swift in Sources */, FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */, FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */, FFCB74132C4625BB008384D0 /* GroupStageSettingsView.swift in Sources */, @@ -1688,6 +1615,7 @@ FF9267FC2BCE84870080F940 /* PlayerPayView.swift in Sources */, FF2B51552C7A4DAF00FFF126 /* PlanningByCourtView.swift in Sources */, FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */, + C42330A62C7E13E2001DABF5 /* Club+Extensions.swift in Sources */, FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */, FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */, FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */, @@ -1697,7 +1625,6 @@ FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */, FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */, FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */, - FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */, FF5D0D8B2BB4D1E3005CB568 /* CalendarView.swift in Sources */, FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */, FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */, @@ -1705,7 +1632,6 @@ FF025AE32BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift in Sources */, FF11628A2BD05247000C4809 /* DateUpdateManagerView.swift in Sources */, FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */, - FF025AE92BD1307F00A86CF8 /* MonthData.swift in Sources */, FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */, FF1F4B6D2BF9E60B000B4573 /* TournamentBuildView.swift in Sources */, FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */, @@ -1722,23 +1648,20 @@ FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */, C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */, FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */, - C49EF03C2BE15AF80077B5AA /* String+Crypto.swift in Sources */, FF9AC3952BE3627B00C2E883 /* GroupStageTeamReplacementView.swift in Sources */, FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */, + C42330B32C7E16F9001DABF5 /* PlayerRegistration+Extensions.swift in Sources */, FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */, FF0E0B6D2BC254C6005F00A9 /* TournamentScheduleView.swift in Sources */, FF025AF12BD1AEBD00A86CF8 /* MatchFormatStorageView.swift in Sources */, FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */, - C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */, FF967D012BAEF0B400A9A3BD /* MatchSummaryView.swift in Sources */, FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */, - FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */, FF967D092BAF3D4000A9A3BD /* TeamDetailView.swift in Sources */, FF5DA18F2BB9268800A33061 /* GroupStagesSettingsView.swift in Sources */, FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */, + C42330B92C7E189A001DABF5 /* Enum+Extensions.swift in Sources */, FF1F4B752BFA00FC000B4573 /* HtmlGenerator.swift in Sources */, - FF8F26382BAD523300650388 /* PadelRule.swift in Sources */, - FF967CF42BAECC0B00A9A3BD /* TeamRegistration.swift in Sources */, FFF8ACDB2B923F48008466FA /* Date+Extensions.swift in Sources */, FF967CFD2BAEE5F500A9A3BD /* GroupStageView.swift in Sources */, FF1DC5592BAB767000FD8220 /* Tips.swift in Sources */, @@ -1747,13 +1670,11 @@ FFF964532BC262B000EEF017 /* PlanningSettingsView.swift in Sources */, FFF527D62BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift in Sources */, FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */, - FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */, C493B37E2C10AD3600862481 /* LoadingViewModifier.swift in Sources */, FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */, FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */, FFF1D2CB2C4A22B200C8D33D /* ExportFormat.swift in Sources */, FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */, - C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */, FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */, FFCFC0122BBC3E1A00B82851 /* PointView.swift in Sources */, FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */, @@ -1768,7 +1689,6 @@ FFF9645B2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift in Sources */, FFBF41822BF73EB3001B24CB /* EventView.swift in Sources */, C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */, - FFC91B012BD85C2F00B29808 /* Court.swift in Sources */, FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */, FFCB74172C480411008384D0 /* CopyPasteButtonView.swift in Sources */, FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */, @@ -1779,10 +1699,8 @@ FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */, FF5D0D722BB3EFA5005CB568 /* LearnMoreSheetView.swift in Sources */, FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */, - C4EC6F592BE92D88000CEAB4 /* PListReader.swift in Sources */, FF0EC5222BB173E70056B6D1 /* UpdateSourceRankDateView.swift in Sources */, FF025AE72BD1111000A86CF8 /* GlobalSettingsView.swift in Sources */, - C4A47D912B7BBBEC00ADC637 /* Guard.swift in Sources */, C49EF0192BD694290077B5AA /* PurchaseListView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/PadelClub.xcworkspace/contents.xcworkspacedata b/PadelClub.xcworkspace/contents.xcworkspacedata index d6ac0a1..4bbc2d2 100644 --- a/PadelClub.xcworkspace/contents.xcworkspacedata +++ b/PadelClub.xcworkspace/contents.xcworkspacedata @@ -4,6 +4,9 @@ + + diff --git a/PadelClub/Data/Club+Extensions.swift b/PadelClub/Data/Club+Extensions.swift new file mode 100644 index 0000000..caf27fb --- /dev/null +++ b/PadelClub/Data/Club+Extensions.swift @@ -0,0 +1,86 @@ +// +// Club+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension Club { + var isValid: Bool { + name.isEmpty == false && name.count > 3 + } + + func automaticShortName() -> String { + name.acronym() + } + + enum AcronymMode: String, CaseIterable { + case automatic = "Automatique" + case custom = "Personalisée" + } + + func shortNameMode() -> AcronymMode { + (acronym.isEmpty || acronym == automaticShortName()) ? .automatic : .custom + } + + func hasTenupId() -> Bool { + code != nil + } + + func federalLink() -> URL? { + guard let code else { return nil } + return URL(string: "https://tenup.fft.fr/club/\(code)") + } + + func courtName(atIndex courtIndex: Int) -> String { + courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex) + } + + func courtNameIfAvailable(atIndex courtIndex: Int) -> String? { + customizedCourts.first(where: { $0.index == courtIndex })?.name + } + + func update(fromClub club: Club) { + self.acronym = club.acronym + self.name = club.name + self.phone = club.phone + self.code = club.code + self.address = club.address + self.city = club.city + self.zipCode = club.zipCode + self.latitude = club.latitude + self.longitude = club.longitude + } + + func hasBeenCreated(by creatorId: String?) -> Bool { + return creatorId == creator || creator == nil + } + + func isFavorite() -> Bool { + return DataStore.shared.user.clubs.contains(where: { $0 == self.id }) + } + + static func findOrCreate(name: String, code: String?, city: String? = nil, zipCode: String? = nil) -> Club { + + /* + + identify a club : code, name, ?? + + */ + let club: Club? = DataStore.shared.clubs.first(where: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || code != nil && $0.code == code }) + + if let club { + return club + } else { + return Club(creator: StoreCenter.main.userId, name: name, code: code, city: city, zipCode: zipCode) + } + } + + func shareURL() -> URL? { + return URL(string: URLs.main.url.appending(path: "?club=\(id)").absoluteString.removingPercentEncoding!) + } + +} diff --git a/PadelClub/Data/Club.swift b/PadelClub/Data/Club.swift deleted file mode 100644 index 53eca84..0000000 --- a/PadelClub/Data/Club.swift +++ /dev/null @@ -1,248 +0,0 @@ -// -// Club.swift -// PadelClub -// -// Created by Laurent Morvillier on 02/02/2024. -// - -import Foundation -import SwiftUI -import LeStorage - -@Observable -final class Club : ModelObject, Storable, Hashable { - - static func resourceName() -> String { return "clubs" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [.get] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - static func == (lhs: Club, rhs: Club) -> Bool { - lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - return hasher.combine(id) - } - - var id: String = Store.randomId() - var creator: String? - var name: String - var acronym: String - var phone: String? - var code: String? - //var federalClubData: Data? - var address: String? - var city: String? - var zipCode: String? - var latitude: Double? - var longitude: Double? - var courtCount: Int = 2 - var broadcastCode: String? -// var alphabeticalName: Bool = false - - internal init(creator: String? = nil, 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, courtCount: Int = 2, broadcastCode: String? = nil) { - self.name = name - self.creator = creator - self.acronym = acronym ?? name.acronym() - self.phone = phone - self.code = code - self.address = address - self.city = city - self.zipCode = zipCode - self.latitude = latitude - self.longitude = longitude - self.courtCount = courtCount - self.broadcastCode = broadcastCode - } - - override func copyFromServerInstance(_ instance: any Storable) -> Bool { - guard let copy = instance as? Club else { return false } - self.broadcastCode = copy.broadcastCode -// Logger.log("write code: \(self.broadcastCode)") - return true - } - - func clubTitle(_ displayStyle: DisplayStyle = .wide) -> String { - switch displayStyle { - case .wide, .title: - return name - case .short: - return acronym - } - } - - func shareURL() -> URL? { - return URL(string: URLs.main.url.appending(path: "?club=\(id)").absoluteString.removingPercentEncoding!) - } - - var customizedCourts: [Court] { - DataStore.shared.courts.filter { $0.club == self.id }.sorted(by: \.index) - } - - override func deleteDependencies() throws { - let customizedCourts = self.customizedCourts - for customizedCourt in customizedCourts { - try customizedCourt.deleteDependencies() - } - DataStore.shared.courts.deleteDependencies(customizedCourts) - } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _creator = "creator" - case _name = "name" - case _acronym = "acronym" - case _phone = "phone" - case _code = "code" - case _address = "address" - case _city = "city" - case _zipCode = "zipCode" - case _latitude = "latitude" - case _longitude = "longitude" - case _courtCount = "courtCount" - case _broadcastCode = "broadcastCode" -// case _alphabeticalName = "alphabeticalName" - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - - if let creator = creator { - try container.encode(creator, forKey: ._creator) - } else { - try container.encodeNil(forKey: ._creator) - } - - try container.encode(name, forKey: ._name) - try container.encode(acronym, forKey: ._acronym) - - if let phone = phone { - try container.encode(phone, forKey: ._phone) - } else { - try container.encodeNil(forKey: ._phone) - } - - if let code = code { - try container.encode(code, forKey: ._code) - } else { - try container.encodeNil(forKey: ._code) - } - - if let address = address { - try container.encode(address, forKey: ._address) - } else { - try container.encodeNil(forKey: ._address) - } - - if let city = city { - try container.encode(city, forKey: ._city) - } else { - try container.encodeNil(forKey: ._city) - } - - if let zipCode = zipCode { - try container.encode(zipCode, forKey: ._zipCode) - } else { - try container.encodeNil(forKey: ._zipCode) - } - - if let latitude = latitude { - try container.encode(latitude, forKey: ._latitude) - } else { - try container.encodeNil(forKey: ._latitude) - } - - if let longitude = longitude { - try container.encode(longitude, forKey: ._longitude) - } else { - try container.encodeNil(forKey: ._longitude) - } - - try container.encode(courtCount, forKey: ._courtCount) - - if let broadcastCode { - try container.encode(broadcastCode, forKey: ._broadcastCode) - } else { - try container.encodeNil(forKey: ._broadcastCode) - } - - // try container.encode(alphabeticalName, forKey: ._alphabeticalName) - } - -} - -extension Club { - var isValid: Bool { - name.isEmpty == false && name.count > 3 - } - - func automaticShortName() -> String { - name.acronym() - } - - enum AcronymMode: String, CaseIterable { - case automatic = "Automatique" - case custom = "Personalisée" - } - - func shortNameMode() -> AcronymMode { - (acronym.isEmpty || acronym == automaticShortName()) ? .automatic : .custom - } - - func hasTenupId() -> Bool { - code != nil - } - - func federalLink() -> URL? { - guard let code else { return nil } - return URL(string: "https://tenup.fft.fr/club/\(code)") - } - - func courtName(atIndex courtIndex: Int) -> String { - courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex) - } - - func courtNameIfAvailable(atIndex courtIndex: Int) -> String? { - customizedCourts.first(where: { $0.index == courtIndex })?.name - } - - func update(fromClub club: Club) { - self.acronym = club.acronym - self.name = club.name - self.phone = club.phone - self.code = club.code - self.address = club.address - self.city = club.city - self.zipCode = club.zipCode - self.latitude = club.latitude - self.longitude = club.longitude - } - - func hasBeenCreated(by creatorId: String?) -> Bool { - return creatorId == creator || creator == nil - } - - func isFavorite() -> Bool { - return DataStore.shared.user.clubs.contains(where: { $0 == self.id }) - } - - static func findOrCreate(name: String, code: String?, city: String? = nil, zipCode: String? = nil) -> Club { - - /* - - identify a club : code, name, ?? - - */ - let club: Club? = DataStore.shared.clubs.first(where: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || code != nil && $0.code == code }) - - if let club { - return club - } else { - return Club(creator: StoreCenter.main.userId, name: name, code: code, city: city, zipCode: zipCode) - } - } - -} diff --git a/PadelClub/Data/Enum+Extensions.swift b/PadelClub/Data/Enum+Extensions.swift new file mode 100644 index 0000000..be58702 --- /dev/null +++ b/PadelClub/Data/Enum+Extensions.swift @@ -0,0 +1,73 @@ +// +// TournamentCategory+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension TournamentCategory { + +} + +extension TournamentType { + + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + switch self { + case .classic: + return "Classique" + case .doubleBrackets: + return "Double Poules" + } + } + +} + +extension TournamentBuild { + + var computedLabel: String { + if age == .senior { return localizedLabel() } + return localizedLabel() + " " + localizedAge + } + + var localizedTitle: String { + level.localizedLabel() + " " + category.localizedLabel() + } + +} + +extension TeamPosition { + + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + var shortName: String { + switch self { + case .one: + return "#1" + case .two: + return "#2" + } + } + + switch displayStyle { + case .wide, .title: + return "Équipe " + shortName + case .short: + return shortName + } + } +} + +extension MatchFormat { + + func formattedEstimatedBreakDuration() -> String { + var label = Duration.seconds(breakTime.breakTime * 60).formatted(.units(allowed: [.minutes])) + if breakTime.matchCount > 1 { + label += " après \(breakTime.matchCount) match" + label += breakTime.matchCount.pluralSuffix + } + return label + } + +} diff --git a/PadelClub/Data/GroupStage+Extensions.swift b/PadelClub/Data/GroupStage+Extensions.swift new file mode 100644 index 0000000..344ab97 --- /dev/null +++ b/PadelClub/Data/GroupStage+Extensions.swift @@ -0,0 +1,55 @@ +// +// GroupStage+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension GroupStage { + + func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String { + if let name { return name } + switch displayStyle { + case .wide, .title: + return "Poule \(index + 1)" + case .short: + return "#\(index + 1)" + } + } + + func pasteData() -> String { + var data: [String] = [] + data.append(self.groupStageTitle()) + teams().forEach { team in + data.append(team.teamLabelRanked(displayRank: true, displayTeamName: true)) + } + + return data.joined(separator: "\n") + } + +} + +extension GroupStage: Selectable { + func selectionLabel(index: Int) -> String { + groupStageTitle() + } + + func badgeValue() -> Int? { + return runningMatches(playedMatches: _matches()).count + } + + func badgeValueColor() -> Color? { + return nil + } + + func badgeImage() -> Badge? { + if teams().count < size { + return .xmark + } else { + return hasEnded() ? .checkmark : nil + } + } +} diff --git a/PadelClub/Data/Match+Extensions.swift b/PadelClub/Data/Match+Extensions.swift new file mode 100644 index 0000000..a45f93d --- /dev/null +++ b/PadelClub/Data/Match+Extensions.swift @@ -0,0 +1,89 @@ +// +// Match+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension Match { + + func matchWarningSubject() -> String { + [roundTitle(), matchTitle(.short)].compacted().joined(separator: " ") + } + + func matchWarningMessage() -> String { + [roundTitle(), matchTitle(.short), startDate?.localizedDate(), courtName()].compacted().joined(separator: "\n") + } + + func matchTitle(_ displayStyle: DisplayStyle = .wide, inMatches matches: [Match]? = nil) -> String { +#if _DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func matchTitle", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + if let groupStageObject { + return groupStageObject.localizedMatchUpLabel(for: index) + } + + switch displayStyle { + case .wide, .title: + return "Match \(indexInRound(in: matches) + 1)" + case .short: + return "#\(indexInRound(in: matches) + 1)" + } + } + + func roundTitle() -> String? { + if groupStage != nil { return groupStageObject?.groupStageTitle() } + else if let roundObject { return roundObject.roundTitle() } + else { return nil } + } + + func teamNames(_ team: TeamRegistration?) -> [String]? { + return team?.players().map { $0.playerLabel() } + } + + func updateScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { + let teamScoreOne = teamScore(.one) ?? TeamScore(match: id, team: team(.one)) + teamScoreOne.score = matchDescriptor.teamOneScores.joined(separator: ",") + let teamScoreTwo = teamScore(.two) ?? TeamScore(match: id, team: team(.two)) + teamScoreTwo.score = matchDescriptor.teamTwoScores.joined(separator: ",") + do { + try self.tournamentStore.teamScores.addOrUpdate(contentOfs: [teamScoreOne, teamScoreTwo]) + } catch { + Logger.error(error) + } + matchFormat = matchDescriptor.matchFormat + } + + func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { + updateScore(fromMatchDescriptor: matchDescriptor) + if endDate == nil { + endDate = Date() + } + if startDate == nil { + startDate = endDate?.addingTimeInterval(Double(-getDuration()*60)) + } + + let teamOne = team(matchDescriptor.winner) + let teamTwo = team(matchDescriptor.winner.otherTeam) + + teamOne?.hasArrived() + teamTwo?.hasArrived() + + winningTeamId = teamOne?.id + losingTeamId = teamTwo?.id + + confirmed = true + + groupStageObject?.updateGroupStageState() + roundObject?.updateTournamentState() + updateFollowingMatchTeamScore() + } + +} diff --git a/PadelClub/Data/MatchScheduler+Extensions.swift b/PadelClub/Data/MatchScheduler+Extensions.swift new file mode 100644 index 0000000..128a549 --- /dev/null +++ b/PadelClub/Data/MatchScheduler+Extensions.swift @@ -0,0 +1,13 @@ +// +// MatchScheduler+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension MatchScheduler { + +} diff --git a/PadelClub/Data/MonthData+Extensions.swift b/PadelClub/Data/MonthData+Extensions.swift new file mode 100644 index 0000000..8530e46 --- /dev/null +++ b/PadelClub/Data/MonthData+Extensions.swift @@ -0,0 +1,43 @@ +// +// MonthData+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension MonthData { + + static func calculateCurrentUnrankedValues(fromDate: Date) async { + + let fileURL = SourceFileManager.shared.allFiles(true).first(where: { $0.dateFromPath == fromDate && $0.index == 0 }) + print("calculateCurrentUnrankedValues", fromDate.monthYearFormatted, fileURL?.path()) + let fftImportingUncomplete = fileURL?.fftImportingUncomplete() + let fftImportingMaleUnrankValue = fileURL?.fftImportingMaleUnrankValue() + + let incompleteMode = fftImportingUncomplete != nil + + let lastDataSourceMaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: fromDate, man: true) + let lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: fromDate, man: false) + let anonymousCount = await FederalPlayer.anonymousCount(mostRecentDateAvailable: fromDate) + await MainActor.run { + let lastDataSource = URL.importDateFormatter.string(from: fromDate) + let currentMonthData : MonthData = DataStore.shared.monthData.first(where: { $0.monthKey == lastDataSource }) ?? MonthData(monthKey: lastDataSource) + currentMonthData._updateCreationDate() + currentMonthData.maleUnrankedValue = incompleteMode ? fftImportingMaleUnrankValue : lastDataSourceMaleUnranked?.0 + currentMonthData.incompleteMode = incompleteMode + currentMonthData.maleCount = incompleteMode ? fftImportingUncomplete : lastDataSourceMaleUnranked?.1 + currentMonthData.femaleUnrankedValue = lastDataSourceFemaleUnranked?.0 + currentMonthData.femaleCount = lastDataSourceFemaleUnranked?.1 + currentMonthData.anonymousCount = anonymousCount + do { + try DataStore.shared.monthData.addOrUpdate(instance: currentMonthData) + } catch { + Logger.error(error) + } + } + } + +} diff --git a/PadelClub/Data/MonthData.swift b/PadelClub/Data/MonthData.swift deleted file mode 100644 index 9be70f5..0000000 --- a/PadelClub/Data/MonthData.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// MonthData.swift -// PadelClub -// -// Created by Razmig Sarkissian on 18/04/2024. -// - -import Foundation -import SwiftUI -import LeStorage - -@Observable -final class MonthData : ModelObject, Storable { - - static func resourceName() -> String { return "month-data" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - 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 - var maleCount: Int? = nil - var femaleCount: Int? = nil - var anonymousCount: Int? = nil - var incompleteMode: Bool = false - - init(monthKey: String) { - self.monthKey = monthKey - self.creationDate = Date() - } - - fileprivate func _updateCreationDate() { - self.creationDate = Date() - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: ._id) - monthKey = try container.decode(String.self, forKey: ._monthKey) - creationDate = try container.decode(Date.self, forKey: ._creationDate) - maleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._maleUnrankedValue) - femaleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._femaleUnrankedValue) - maleCount = try container.decodeIfPresent(Int.self, forKey: ._maleCount) - femaleCount = try container.decodeIfPresent(Int.self, forKey: ._femaleCount) - anonymousCount = try container.decodeIfPresent(Int.self, forKey: ._anonymousCount) - incompleteMode = try container.decodeIfPresent(Bool.self, forKey: ._incompleteMode) ?? false - - } - - func total() -> Int { - return (maleCount ?? 0) + (femaleCount ?? 0) - } - - static func calculateCurrentUnrankedValues(fromDate: Date) async { - - let fileURL = SourceFileManager.shared.allFiles(true).first(where: { $0.dateFromPath == fromDate && $0.index == 0 }) - print("calculateCurrentUnrankedValues", fromDate.monthYearFormatted, fileURL?.path()) - let fftImportingUncomplete = fileURL?.fftImportingUncomplete() - let fftImportingMaleUnrankValue = fileURL?.fftImportingMaleUnrankValue() - - let incompleteMode = fftImportingUncomplete != nil - - let lastDataSourceMaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: fromDate, man: true) - let lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: fromDate, man: false) - let anonymousCount = await FederalPlayer.anonymousCount(mostRecentDateAvailable: fromDate) - await MainActor.run { - let lastDataSource = URL.importDateFormatter.string(from: fromDate) - let currentMonthData : MonthData = DataStore.shared.monthData.first(where: { $0.monthKey == lastDataSource }) ?? MonthData(monthKey: lastDataSource) - currentMonthData._updateCreationDate() - currentMonthData.maleUnrankedValue = incompleteMode ? fftImportingMaleUnrankValue : lastDataSourceMaleUnranked?.0 - currentMonthData.incompleteMode = incompleteMode - currentMonthData.maleCount = incompleteMode ? fftImportingUncomplete : lastDataSourceMaleUnranked?.1 - currentMonthData.femaleUnrankedValue = lastDataSourceFemaleUnranked?.0 - currentMonthData.femaleCount = lastDataSourceFemaleUnranked?.1 - currentMonthData.anonymousCount = anonymousCount - do { - try DataStore.shared.monthData.addOrUpdate(instance: currentMonthData) - } catch { - Logger.error(error) - } - } - } - - override func deleteDependencies() throws { - } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _monthKey = "monthKey" - case _creationDate = "creationDate" - case _maleUnrankedValue = "maleUnrankedValue" - case _femaleUnrankedValue = "femaleUnrankedValue" - case _maleCount = "maleCount" - case _femaleCount = "femaleCount" - case _anonymousCount = "anonymousCount" - case _incompleteMode = "incompleteMode" - } -} diff --git a/PadelClub/Data/PlayerRegistration+Extensions.swift b/PadelClub/Data/PlayerRegistration+Extensions.swift new file mode 100644 index 0000000..661bf9a --- /dev/null +++ b/PadelClub/Data/PlayerRegistration+Extensions.swift @@ -0,0 +1,230 @@ +// +// PlayerRegistration+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension PlayerRegistration { + + internal init(importedPlayer: ImportedPlayer) { + self.teamRegistration = "" + self.firstName = (importedPlayer.firstName ?? "").trimmed.capitalized + self.lastName = (importedPlayer.lastName ?? "").trimmed.uppercased() + self.licenceId = importedPlayer.license ?? nil + self.rank = Int(importedPlayer.rank) + self.sex = importedPlayer.male ? .male : .female + self.tournamentPlayed = importedPlayer.tournamentPlayed + self.points = importedPlayer.getPoints() + self.clubName = importedPlayer.clubName + self.ligueName = importedPlayer.ligueName + self.assimilation = importedPlayer.assimilation + self.source = .frenchFederation + } + + internal init?(federalData: [String], sex: Int, sexUnknown: Bool) { + let _lastName = federalData[0].trimmed.uppercased() + let _firstName = federalData[1].trimmed.capitalized + if _lastName.isEmpty && _firstName.isEmpty { return nil } + lastName = _lastName + firstName = _firstName + birthdate = federalData[2] + licenceId = federalData[3] + clubName = federalData[4] + let stringRank = federalData[5] + if stringRank.isEmpty { + rank = nil + } else { + rank = Int(stringRank) + } + let _email = federalData[6] + if _email.isEmpty == false { + self.email = _email + } + let _phoneNumber = federalData[7] + if _phoneNumber.isEmpty == false { + self.phoneNumber = _phoneNumber + } + + source = .beachPadel + if sexUnknown { + if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) { + self.sex = .female + } else if FileImportManager.shared.foundInMenData(license: federalData[3]) { + self.sex = .male + } else { + self.sex = nil + } + } else { + self.sex = PlayerSexType(rawValue: sex) + } + } + + func playerLabel(_ displayStyle: DisplayStyle = .wide) -> String { + switch displayStyle { + case .wide, .title: + return lastName.trimmed.capitalized + " " + firstName.trimmed.capitalized + case .short: + let names = lastName.components(separatedBy: .whitespaces) + if lastName.components(separatedBy: .whitespaces).count > 1 { + if let firstLongWord = names.first(where: { $0.count > 3 }) { + return firstLongWord.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "." + } + } + return lastName.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "." + } + } + + func pasteData(_ exportFormat: ExportFormat = .rawText) -> String { + switch exportFormat { + case .rawText: + return [firstName.capitalized, lastName.capitalized, licenceId].compactMap({ $0 }).joined(separator: exportFormat.separator()) + case .csv: + return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator()) + } + } + + @objc + var canonicalName: String { + playerLabel().folding(options: .diacriticInsensitive, locale: .current).lowercased() + } + + func isValidLicenseNumber(year: Int) -> Bool { + guard let licenceId else { return false } + guard licenceId.isLicenseNumber else { return false } + guard licenceId.suffix(6) == "(\(year))" else { return false } + return true + } + + @MainActor + func updateRank(from sources: [CSVParser], lastRank: Int) async throws { + if let dataFound = try await history(from: sources) { + rank = dataFound.rankValue?.toInt() + points = dataFound.points + tournamentPlayed = dataFound.tournamentCountValue?.toInt() + } else { + rank = lastRank + } + } + +} + +extension PlayerRegistration: PlayerHolder { + func getAssimilatedAsMaleRank() -> Int? { + nil + } + + func getFirstName() -> String { + firstName + } + + func getLastName() -> String { + lastName + } + + func getPoints() -> Double? { + self.points + } + + func getRank() -> Int? { + rank + } + + func isUnranked() -> Bool { + rank == nil + } + + func formattedRank() -> String { + self.rankLabel() + } + + func formattedLicense() -> String { + if let licenceId { return licenceId.computedLicense } + return "aucune licence" + } + + var male: Bool { + isMalePlayer() + } + + func history(from sources: [CSVParser]) async throws -> Line? { + guard let license = licenceId?.strippedLicense else { + return try await historyFromName(from: sources) + } + + return await withTaskGroup(of: Line?.self) { group in + for source in sources.filter({ $0.maleData == isMalePlayer() }) { + group.addTask { + guard !Task.isCancelled else { print("Cancelled"); return nil } + + return try? await source.first(where: { line in + line.rawValue.contains(";\(license);") + }) + } + } + + if let first = await group.first(where: { $0 != nil }) { + group.cancelAll() + return first + } else { + return nil + } + } + } + + func historyFromName(from sources: [CSVParser]) async throws -> Line? { + return await withTaskGroup(of: Line?.self) { group in + for source in sources.filter({ $0.maleData == isMalePlayer() }) { + group.addTask { [lastName, firstName] in + guard !Task.isCancelled else { print("Cancelled"); return nil } + + return try? await source.first(where: { line in + line.rawValue.canonicalVersionWithPunctuation.contains(";\(lastName.canonicalVersionWithPunctuation);\(firstName.canonicalVersionWithPunctuation);") + }) + } + } + + if let first = await group.first(where: { $0 != nil }) { + group.cancelAll() + return first + } else { + return nil + } + } + } + + func validateLicenceId(_ year: Int) { + if let currentLicenceId = licenceId { + if currentLicenceId.trimmed.hasSuffix("(\(year-1))") { + self.licenceId = currentLicenceId.replacingOccurrences(of: "\(year-1)", with: "\(year)") + } else if let computedLicense = currentLicenceId.strippedLicense { + self.licenceId = computedLicense + " (\(year))" + } + } + } + + func hasHomonym() -> Bool { + let federalContext = PersistenceController.shared.localContainer.viewContext + let fetchRequest = ImportedPlayer.fetchRequest() + let predicate = NSPredicate(format: "firstName == %@ && lastName == %@", firstName, lastName) + fetchRequest.predicate = predicate + + do { + let count = try federalContext.count(for: fetchRequest) + return count > 1 + } catch { + + } + return false + } + + func hasInvalidLicence() -> Bool { + return (self.isImported() && self.isValidLicenseNumber(year: licenseYearValidity) == false) || + (self.isImported() == false && + (self.licenceId == nil || self.formattedLicense().isLicenseNumber == false || self.licenceId?.isEmpty == true)) + } + +} diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift deleted file mode 100644 index aa70f03..0000000 --- a/PadelClub/Data/PlayerRegistration.swift +++ /dev/null @@ -1,574 +0,0 @@ -// -// PlayerRegistration.swift -// Padel Tournament -// -// Created by razmig on 10/03/2024. -// - -import Foundation -import LeStorage - -@Observable -final class PlayerRegistration: ModelObject, Storable { - static func resourceName() -> String { "player-registrations" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return true } - static var relationshipNames: [String] = ["teamRegistration"] - - var id: String = Store.randomId() - var teamRegistration: String? - var firstName: String - var lastName: String - var licenceId: String? - var rank: Int? - var paymentType: PlayerPaymentType? - var sex: PlayerSexType? - - var tournamentPlayed: Int? - var points: Double? - var clubName: String? - var ligueName: String? - var assimilation: String? - - var phoneNumber: String? - var email: String? - var birthdate: String? - - var computedRank: Int = 0 - var source: PlayerDataSource? - - var hasArrived: Bool = false - - init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerDataSource? = nil, hasArrived: Bool = false) { - self.teamRegistration = teamRegistration - self.firstName = firstName - self.lastName = lastName - self.licenceId = licenceId - self.rank = rank - self.paymentType = paymentType - self.sex = sex - self.tournamentPlayed = tournamentPlayed - self.points = points - self.clubName = clubName - self.ligueName = ligueName - self.assimilation = assimilation - self.phoneNumber = phoneNumber - self.email = email - self.birthdate = birthdate - self.computedRank = computedRank - self.source = source - self.hasArrived = hasArrived - } - - internal init(importedPlayer: ImportedPlayer) { - self.teamRegistration = "" - self.firstName = (importedPlayer.firstName ?? "").trimmed.capitalized - self.lastName = (importedPlayer.lastName ?? "").trimmed.uppercased() - self.licenceId = importedPlayer.license ?? nil - self.rank = Int(importedPlayer.rank) - self.sex = importedPlayer.male ? .male : .female - self.tournamentPlayed = importedPlayer.tournamentPlayed - self.points = importedPlayer.getPoints() - self.clubName = importedPlayer.clubName - self.ligueName = importedPlayer.ligueName - self.assimilation = importedPlayer.assimilation - self.source = .frenchFederation - } - - internal init?(federalData: [String], sex: Int, sexUnknown: Bool) { - let _lastName = federalData[0].trimmed.uppercased() - let _firstName = federalData[1].trimmed.capitalized - if _lastName.isEmpty && _firstName.isEmpty { return nil } - lastName = _lastName - firstName = _firstName - birthdate = federalData[2] - licenceId = federalData[3] - clubName = federalData[4] - let stringRank = federalData[5] - if stringRank.isEmpty { - rank = nil - } else { - rank = Int(stringRank) - } - let _email = federalData[6] - if _email.isEmpty == false { - self.email = _email - } - let _phoneNumber = federalData[7] - if _phoneNumber.isEmpty == false { - self.phoneNumber = _phoneNumber - } - - source = .beachPadel - if sexUnknown { - if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) { - self.sex = .female - } else if FileImportManager.shared.foundInMenData(license: federalData[3]) { - self.sex = .male - } else { - self.sex = nil - } - } else { - self.sex = PlayerSexType(rawValue: sex) - } - } - - var tournamentStore: TournamentStore { - if let store = self.store as? TournamentStore { - return store - } - fatalError("missing store for \(String(describing: type(of: self)))") - } - - var computedAge: Int? { - if let birthdate { - let components = birthdate.components(separatedBy: "/") - if components.count == 3 { - if let year = Calendar.current.dateComponents([.year], from: Date()).year, let age = components.last, let ageInt = Int(age) { - if age.count == 2 { //si l'année est sur 2 chiffres dans le fichier - if ageInt < 23 { - return year - 2000 - ageInt - } else { - return year - 2000 + 100 - ageInt - } - } else { //si l'année est représenté sur 4 chiffres - return year - ageInt - } - } - } - } - return nil - } - - func pasteData(_ exportFormat: ExportFormat = .rawText) -> String { - switch exportFormat { - case .rawText: - return [firstName.capitalized, lastName.capitalized, licenceId].compactMap({ $0 }).joined(separator: exportFormat.separator()) - case .csv: - return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator()) - } - } - - func isPlaying() -> Bool { - team()?.isPlaying() == true - } - - func contains(_ searchField: String) -> Bool { - firstName.localizedCaseInsensitiveContains(searchField) || lastName.localizedCaseInsensitiveContains(searchField) - } - - func isSameAs(_ player: PlayerRegistration) -> Bool { - firstName.localizedCaseInsensitiveCompare(player.firstName) == .orderedSame && - lastName.localizedCaseInsensitiveCompare(player.lastName) == .orderedSame - } - - func tournament() -> Tournament? { - guard let tournament = team()?.tournament else { return nil } - return Store.main.findById(tournament) - } - - func team() -> TeamRegistration? { - guard let teamRegistration else { return nil } - return self.tournamentStore.teamRegistrations.findById(teamRegistration) - } - - func hasPaid() -> Bool { - paymentType != nil - } - - func playerLabel(_ displayStyle: DisplayStyle = .wide) -> String { - switch displayStyle { - case .wide, .title: - return lastName.trimmed.capitalized + " " + firstName.trimmed.capitalized - case .short: - let names = lastName.components(separatedBy: .whitespaces) - if lastName.components(separatedBy: .whitespaces).count > 1 { - if let firstLongWord = names.first(where: { $0.count > 3 }) { - return firstLongWord.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "." - } - } - return lastName.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "." - } - } - - func isImported() -> Bool { - source == .beachPadel - } - - func isValidLicenseNumber(year: Int) -> Bool { - guard let licenceId else { return false } - guard licenceId.isLicenseNumber else { return false } - guard licenceId.suffix(6) == "(\(year))" else { return false } - return true - } - - @objc - var canonicalName: String { - playerLabel().folding(options: .diacriticInsensitive, locale: .current).lowercased() - } - - func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String { - if let rank, rank > 0 { - if rank != computedRank { - return computedRank.formatted() + " (" + rank.formatted() + ")" - } else { - return rank.formatted() - } - } else { - return "non classé" + (isMalePlayer() ? "" : "e") - } - } - - func getRank() -> Int { - computedRank - } - - @MainActor - func updateRank(from sources: [CSVParser], lastRank: Int) async throws { - if let dataFound = try await history(from: sources) { - rank = dataFound.rankValue?.toInt() - points = dataFound.points - tournamentPlayed = dataFound.tournamentCountValue?.toInt() - } else { - rank = lastRank - } - } - - func history(from sources: [CSVParser]) async throws -> Line? { - guard let license = licenceId?.strippedLicense else { - return try await historyFromName(from: sources) - } - - return await withTaskGroup(of: Line?.self) { group in - for source in sources.filter({ $0.maleData == isMalePlayer() }) { - group.addTask { - guard !Task.isCancelled else { print("Cancelled"); return nil } - - return try? await source.first(where: { line in - line.rawValue.contains(";\(license);") - }) - } - } - - if let first = await group.first(where: { $0 != nil }) { - group.cancelAll() - return first - } else { - return nil - } - } - } - - func historyFromName(from sources: [CSVParser]) async throws -> Line? { - return await withTaskGroup(of: Line?.self) { group in - for source in sources.filter({ $0.maleData == isMalePlayer() }) { - group.addTask { [lastName, firstName] in - guard !Task.isCancelled else { print("Cancelled"); return nil } - - return try? await source.first(where: { line in - line.rawValue.canonicalVersionWithPunctuation.contains(";\(lastName.canonicalVersionWithPunctuation);\(firstName.canonicalVersionWithPunctuation);") - }) - } - } - - if let first = await group.first(where: { $0 != nil }) { - group.cancelAll() - return first - } else { - return nil - } - } - } - - func setComputedRank(in tournament: Tournament) { - let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 70_000 - switch tournament.tournamentCategory { - case .men: - computedRank = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0) - default: - computedRank = currentRank - } - } - - func isMalePlayer() -> Bool { - sex == .male - } - - func validateLicenceId(_ year: Int) { - if let currentLicenceId = licenceId { - if currentLicenceId.trimmed.hasSuffix("(\(year-1))") { - self.licenceId = currentLicenceId.replacingOccurrences(of: "\(year-1)", with: "\(year)") - } else if let computedLicense = currentLicenceId.strippedLicense { - self.licenceId = computedLicense + " (\(year))" - } - } - } - - func hasHomonym() -> Bool { - let federalContext = PersistenceController.shared.localContainer.viewContext - let fetchRequest = ImportedPlayer.fetchRequest() - let predicate = NSPredicate(format: "firstName == %@ && lastName == %@", firstName, lastName) - fetchRequest.predicate = predicate - - do { - let count = try federalContext.count(for: fetchRequest) - return count > 1 - } catch { - - } - return false - } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _teamRegistration = "teamRegistration" - case _firstName = "firstName" - case _lastName = "lastName" - case _licenceId = "licenceId" - case _rank = "rank" - case _paymentType = "paymentType" - case _sex = "sex" - case _tournamentPlayed = "tournamentPlayed" - case _points = "points" - case _clubName = "clubName" - case _ligueName = "ligueName" - case _assimilation = "assimilation" - case _birthdate = "birthdate" - case _phoneNumber = "phoneNumber" - case _email = "email" - case _computedRank = "computedRank" - case _source = "source" - case _hasArrived = "hasArrived" - - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - - if let teamRegistration = teamRegistration { - try container.encode(teamRegistration, forKey: ._teamRegistration) - } else { - try container.encodeNil(forKey: ._teamRegistration) - } - - try container.encode(firstName, forKey: ._firstName) - try container.encode(lastName, forKey: ._lastName) - - if let licenceId = licenceId { - try container.encode(licenceId, forKey: ._licenceId) - } else { - try container.encodeNil(forKey: ._licenceId) - } - - if let rank = rank { - try container.encode(rank, forKey: ._rank) - } else { - try container.encodeNil(forKey: ._rank) - } - - if let paymentType = paymentType { - try container.encode(paymentType, forKey: ._paymentType) - } else { - try container.encodeNil(forKey: ._paymentType) - } - - if let sex = sex { - try container.encode(sex, forKey: ._sex) - } else { - try container.encodeNil(forKey: ._sex) - } - - if let tournamentPlayed = tournamentPlayed { - try container.encode(tournamentPlayed, forKey: ._tournamentPlayed) - } else { - try container.encodeNil(forKey: ._tournamentPlayed) - } - - if let points = points { - try container.encode(points, forKey: ._points) - } else { - try container.encodeNil(forKey: ._points) - } - - if let clubName = clubName { - try container.encode(clubName, forKey: ._clubName) - } else { - try container.encodeNil(forKey: ._clubName) - } - - if let ligueName = ligueName { - try container.encode(ligueName, forKey: ._ligueName) - } else { - try container.encodeNil(forKey: ._ligueName) - } - - if let assimilation = assimilation { - try container.encode(assimilation, forKey: ._assimilation) - } else { - try container.encodeNil(forKey: ._assimilation) - } - - if let phoneNumber = phoneNumber { - try container.encode(phoneNumber, forKey: ._phoneNumber) - } else { - try container.encodeNil(forKey: ._phoneNumber) - } - - if let email = email { - try container.encode(email, forKey: ._email) - } else { - try container.encodeNil(forKey: ._email) - } - - if let birthdate = birthdate { - try container.encode(birthdate, forKey: ._birthdate) - } else { - try container.encodeNil(forKey: ._birthdate) - } - - try container.encode(computedRank, forKey: ._computedRank) - - if let source = source { - try container.encode(source, forKey: ._source) - } else { - try container.encodeNil(forKey: ._source) - } - - try container.encode(hasArrived, forKey: ._hasArrived) - } - - enum PlayerDataSource: Int, Codable { - case frenchFederation = 0 - case beachPadel = 1 - } - - enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable { - init?(rawValue: Int?) { - guard let value = rawValue else { return nil } - self.init(rawValue: value) - } - - var id: Self { - self - } - - case female = 0 - case male = 1 - } - - enum PlayerPaymentType: Int, CaseIterable, Identifiable, Codable { - init?(rawValue: Int?) { - guard let value = rawValue else { return nil } - self.init(rawValue: value) - } - - var id: Self { - self - } - - case cash = 0 - case lydia = 1 - case gift = 2 - case check = 3 - case paylib = 4 - case bankTransfer = 5 - case clubHouse = 6 - case creditCard = 7 - case forfeit = 8 - - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { - switch self { - case .check: - return "Chèque" - case .cash: - return "Cash" - case .lydia: - return "Lydia" - case .paylib: - return "Paylib" - case .bankTransfer: - return "Virement" - case .clubHouse: - return "Clubhouse" - case .creditCard: - return "CB" - case .forfeit: - return "Forfait" - case .gift: - return "Offert" - } - } - } - - static func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int { - switch playerRank { - case 0: return 0 - case womanMax: return manMax - womanMax - case manMax: return 0 - case 1...10: return 400 - case 11...30: return 1000 - case 31...60: return 2000 - case 61...100: return 3000 - case 101...200: return 8000 - case 201...500: return 12000 - default: - return 15000 - } - } - - func insertOnServer() { - self.tournamentStore.playerRegistrations.writeChangeAndInsertOnServer(instance: self) - } - -} - -extension PlayerRegistration: Hashable { - static func == (lhs: PlayerRegistration, rhs: PlayerRegistration) -> Bool { - lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} - -extension PlayerRegistration: PlayerHolder { - func getAssimilatedAsMaleRank() -> Int? { - nil - } - - func getFirstName() -> String { - firstName - } - - func getLastName() -> String { - lastName - } - - func getPoints() -> Double? { - self.points - } - - func getRank() -> Int? { - rank - } - - func isUnranked() -> Bool { - rank == nil - } - - func formattedRank() -> String { - self.rankLabel() - } - - func formattedLicense() -> String { - if let licenceId { return licenceId.computedLicense } - return "aucune licence" - } - - var male: Bool { - isMalePlayer() - } -} diff --git a/PadelClub/Data/Round+Extensions.swift b/PadelClub/Data/Round+Extensions.swift new file mode 100644 index 0000000..5003b3b --- /dev/null +++ b/PadelClub/Data/Round+Extensions.swift @@ -0,0 +1,155 @@ +// +// Round+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension Round { + + static func setServerTitle(upperRound: Round, matchIndex: Int) -> String { + if upperRound.index == 0 { return upperRound.roundTitle() } + return upperRound.roundTitle() + " #" + (matchIndex + 1).formatted() + } + + func roundTitle(_ displayStyle: DisplayStyle = .wide, initialMode: Bool = false) -> String { + if parent != nil { + if let seedInterval = seedInterval(initialMode: initialMode) { + return seedInterval.localizedLabel(displayStyle) + } + print("Round pas trouvé", id, parent, index) + return "Match de classement" + } + return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle) + } + + func pasteData() -> String { + var data: [String] = [] + data.append(self.roundTitle()) + + playedMatches().forEach { match in + data.append(match.matchTitle()) + data.append(match.team(.one)?.teamLabelRanked(displayRank: true, displayTeamName: true) ?? "-----") + data.append(match.team(.two)?.teamLabelRanked(displayRank: true, displayTeamName: true) ?? "-----") + } + + return data.joined(separator: "\n") + } + + func correspondingLoserRoundTitle(_ displayStyle: DisplayStyle = .wide) -> String { +#if _DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func correspondingLoserRoundTitle()", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + let initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index) + let seedsAfterThisRound: [TeamRegistration] = self.tournamentStore.teamRegistrations.filter { + $0.bracketPosition != nil + && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex + } + +// let seedsAfterThisRound : [TeamRegistration] = Store.main.filter(isIncluded: { +// $0.tournament == tournament +// && $0.bracketPosition != nil +// && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex +// }) + let playedMatches = playedMatches() + let seedInterval = SeedInterval(first: playedMatches.count + seedsAfterThisRound.count + 1, last: playedMatches.count * 2 + seedsAfterThisRound.count) + return seedInterval.localizedLabel(displayStyle) + } + + func buildLoserBracket() { + guard loserRounds().isEmpty else { return } + let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index) + guard currentRoundMatchCount > 1 else { return } + let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount) + + var loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat + if let parentRound { + loserBracketMatchFormat = tournamentObject()?.loserBracketSmartMatchFormat(parentRound.index) + } + + let rounds = (0.. Bool { + lhs.id == rhs.id + } + + + func selectionLabel(index: Int) -> String { + if let parentRound { + return "Tour #\(parentRound.loserRounds().count - index)" + } else { + return roundTitle(.short) + } + } + + func badgeValue() -> Int? { +#if _DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func badgeValue round of: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + + + if let parentRound { + return parentRound.loserRounds(forRoundIndex: index).flatMap { $0.playedMatches() }.filter({ $0.isRunning() }).count + } else { + return playedMatches().filter({ $0.isRunning() }).count + } + } + + func badgeValueColor() -> Color? { + return nil + } + + func badgeImage() -> Badge? { +#if _DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func badgeImage of round: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + return hasEnded() ? .checkmark : nil + } +} diff --git a/PadelClub/Data/SeedInterval+Extensions.swift b/PadelClub/Data/SeedInterval+Extensions.swift new file mode 100644 index 0000000..fa31578 --- /dev/null +++ b/PadelClub/Data/SeedInterval+Extensions.swift @@ -0,0 +1,20 @@ +// +// SeedInterval+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension SeedInterval { + + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + if dimension < 3 { + return "\(first)\(first.ordinalFormattedSuffix()) place" + } else { + return "Place \(first) à \(last)" + } + } +} diff --git a/PadelClub/Data/TeamRegistration+Extensions.swift b/PadelClub/Data/TeamRegistration+Extensions.swift new file mode 100644 index 0000000..8106f1c --- /dev/null +++ b/PadelClub/Data/TeamRegistration+Extensions.swift @@ -0,0 +1,109 @@ +// +// TeamRegistration+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension TeamRegistration { + + func getPhoneNumbers() -> [String] { + return players().compactMap { $0.phoneNumber }.filter({ $0.isMobileNumber() }) + } + + func playersPasteData(_ exportFormat: ExportFormat = .rawText) -> String { + switch exportFormat { + case .rawText: + return players().map { $0.pasteData(exportFormat) }.joined(separator: exportFormat.newLineSeparator()) + case .csv: + return players().map { [$0.pasteData(exportFormat), isWildCard() ? "WC" : $0.computedRank.formatted() ].joined(separator: exportFormat.separator()) }.joined(separator: exportFormat.separator()) + } + } + + func formattedInscriptionDate(_ exportFormat: ExportFormat = .rawText) -> String? { + switch exportFormat { + case .rawText: + if let registrationDate { + return "Inscrit le " + registrationDate.formatted(.dateTime.weekday().day().month().hour().minute()) + } else { + return nil + } + case .csv: + if let registrationDate { + return registrationDate.formatted(.dateTime.weekday().day().month().hour().minute()) + } else { + return nil + } + } + } + + func formattedSummonDate(_ exportFormat: ExportFormat = .rawText) -> String? { + + switch exportFormat { + case .rawText: + if let callDate { + return "Convoqué le " + callDate.formatted(.dateTime.weekday().day().month().hour().minute()) + } else { + return nil + } + case .csv: + if let callDate { + return callDate.formatted(.dateTime.weekday().day().month().hour().minute()) + } else { + return nil + } + } + } + + func pasteData(_ exportFormat: ExportFormat = .rawText, _ index: Int = 0) -> String { + switch exportFormat { + case .rawText: + return [playersPasteData(exportFormat), formattedInscriptionDate(exportFormat), name].compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator()) + case .csv: + return [index.formatted(), playersPasteData(exportFormat), isWildCard() ? "WC" : weight.formatted()].joined(separator: exportFormat.separator()) + } + } + + func positionLabel() -> String? { + if groupStagePosition != nil { return "Poule" } + if let initialRound = initialRound() { + return initialRound.roundTitle() + } else { + return nil + } + } + + func initialRoundColor() -> Color? { + if walkOut { return Color.logoRed } + if groupStagePosition != nil { return Color.blue } + if let initialRound = initialRound(), let colorHex = RoundRule.colors[safe: initialRound.index] { + return Color(uiColor: .init(fromHex: colorHex)) + } else { + return nil + } + } + + func containsExactlyPlayerLicenses(_ playerLicenses: [String?]) -> Bool { + let arrayOfIds : [String] = unsortedPlayers().compactMap({ $0.licenceId?.strippedLicense?.canonicalVersion }) + let ids : Set = Set(arrayOfIds.sorted()) + let searchedIds = Set(playerLicenses.compactMap({ $0?.strippedLicense?.canonicalVersion }).sorted()) + return ids.hashValue == searchedIds.hashValue + } + + @objc + var canonicalName: String { + players().map { $0.canonicalName }.joined(separator: " ") + } + + func teamLabel(_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false) -> String { + return players().map { $0.playerLabel(displayStyle) }.joined(separator: twoLines ? "\n" : " & ") + } + + func teamLabelRanked(displayRank: Bool, displayTeamName: Bool) -> String { + [displayTeamName ? name : nil, displayRank ? seedIndex() : nil, displayTeamName ? (name == nil ? teamLabel() : name) : teamLabel()].compactMap({ $0 }).joined(separator: " ") + } + +} diff --git a/PadelClub/Data/Tournament+Extensions.swift b/PadelClub/Data/Tournament+Extensions.swift new file mode 100644 index 0000000..5877aa1 --- /dev/null +++ b/PadelClub/Data/Tournament+Extensions.swift @@ -0,0 +1,395 @@ +// +// Tournament+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation +import PadelClubData + +extension Tournament { + + func shareURL(_ pageLink: PageLink = .matches) -> URL? { + if pageLink == .clubBroadcast { + let club = club() + print("club", club) + print("club broadcast code", club?.broadcastCode) + if let club, let broadcastCode = club.broadcastCode { + return URLs.main.url.appending(path: "c/\(broadcastCode)") + } else { + return nil + } + } + return URLs.main.url.appending(path: "tournament/\(id)").appending(path: pageLink.path) + } + + func pasteDataForImporting(_ exportFormat: ExportFormat = .rawText) -> String { + let selectedSortedTeams = selectedSortedTeams() + switch exportFormat { + case .rawText: + return (selectedSortedTeams.compactMap { $0.pasteData(exportFormat) } + ["Liste d'attente"] + waitingListTeams(in: selectedSortedTeams, includingWalkOuts: true).compactMap { $0.pasteData(exportFormat) }).joined(separator: exportFormat.newLineSeparator(2)) + case .csv: + let headers = ["N°", "Nom Prénom", "rang", "Nom Prénom", "rang", "poids"].joined(separator: exportFormat.separator()) + var teamPaste = [headers] + for (index, team) in selectedSortedTeams.enumerated() { + teamPaste.append(team.pasteData(exportFormat, index + 1)) + } + return teamPaste.joined(separator: exportFormat.newLineSeparator()) + } + } + + func importTeams(_ teams: [FileImportManager.TeamHolder]) { + var teamsToImport = [TeamRegistration]() + let players = players().filter { $0.licenceId != nil } + teams.forEach { team in + if let previousTeam = team.previousTeam { + previousTeam.updatePlayers(team.players, inTournamentCategory: team.tournamentCategory) + teamsToImport.append(previousTeam) + } else { + var registrationDate = team.registrationDate + if let previousPlayer = players.first(where: { player in + let ids = team.players.compactMap({ $0.licenceId }) + return ids.contains(player.licenceId!) + }), let previousTeamRegistrationDate = previousPlayer.team()?.registrationDate { + registrationDate = previousTeamRegistrationDate + } + let newTeam = addTeam(team.players, registrationDate: registrationDate, name: team.name) + teamsToImport.append(newTeam) + } + } + + do { + try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teamsToImport) + } catch { + Logger.error(error) + } + do { + try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: teams.flatMap { $0.players }) + } catch { + Logger.error(error) + } + + + if state() == .build && groupStageCount > 0 && groupStageTeams().isEmpty { + setGroupStage(randomize: groupStageSortMode == .random) + } + } + + func homonyms(in players: [PlayerRegistration]) -> [PlayerRegistration] { + players.filter({ $0.hasHomonym() }) + } + + func finalRanking() async -> [Int: [String]] { + var teams: [Int: [String]] = [:] + var ids: Set = Set() + let rounds = rounds() + let final = rounds.last?.playedMatches().last + if let winner = final?.winningTeamId { + teams[1] = [winner] + ids.insert(winner) + } + if let finalist = final?.losingTeamId { + teams[2] = [finalist] + ids.insert(finalist) + } + + let others: [Round] = rounds.flatMap { round in + let losers = round.losers() + let minimumFinalPosition = round.seedInterval()?.last ?? teamCount + if teams[minimumFinalPosition] == nil { + teams[minimumFinalPosition] = losers.map { $0.id } + } else { + teams[minimumFinalPosition]?.append(contentsOf: losers.map { $0.id }) + } + + print("round", round.roundTitle()) + let rounds = round.loserRoundsAndChildren().filter { $0.isRankDisabled() == false && $0.hasNextRound() == false } + print(rounds.count, rounds.map { $0.roundTitle() }) + return rounds + }.compactMap({ $0 }) + + others.forEach { round in + print("round", round.roundTitle()) + if let interval = round.seedInterval() { + print("interval", interval.localizedLabel()) + let playedMatches = round.playedMatches().filter { $0.disabled == false || $0.isReady() } + print("playedMatches", playedMatches.count) + let winners = playedMatches.compactMap({ $0.winningTeamId }).filter({ ids.contains($0) == false }) + print("winners", winners.count) + let losers = playedMatches.compactMap({ $0.losingTeamId }).filter({ ids.contains($0) == false }) + print("losers", losers.count) + if winners.isEmpty { + let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false }) + if disabledIds.isEmpty == false { + _removeStrings(from: &teams, stringsToRemove: disabledIds) + teams[interval.last] = disabledIds + let teamNames : [String] = disabledIds.compactMap { + let t : TeamRegistration? = Store.main.findById($0) + return t + }.map { $0.canonicalName } + print("winners.isEmpty", "\(interval.last) : ", teamNames) + disabledIds.forEach { + ids.insert($0) + } + } + } else { + if winners.isEmpty == false { + _removeStrings(from: &teams, stringsToRemove: winners) + teams[interval.first + winners.count - 1] = winners + let teamNames : [String] = winners.compactMap { + let t: TeamRegistration? = Store.main.findById($0) + return t + }.map { $0.canonicalName } + print("winners", "\(interval.last + winners.count - 1) : ", teamNames) + winners.forEach { ids.insert($0) } + } + + if losers.isEmpty == false { + _removeStrings(from: &teams, stringsToRemove: losers) + teams[interval.last] = losers + let loserTeamNames : [String] = losers.compactMap { + let t: TeamRegistration? = Store.main.findById($0) + return t + }.map { $0.canonicalName } + print("losers", "\(interval.last) : ", loserTeamNames) + losers.forEach { ids.insert($0) } + } + } + } + } + + let groupStages = groupStages() + let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified + + groupStages.forEach { groupStage in + let groupStageTeams = groupStage.teams(true) + for (index, team) in groupStageTeams.enumerated() { + if team.qualified == false { + let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0) + + let _index = baseRank + groupStageWidth + 1 + if let existingTeams = teams[_index] { + teams[_index] = existingTeams + [team.id] + } else { + teams[_index] = [team.id] + } + } + } + } + + return teams + } + +} + +extension Tournament: FederalTournamentHolder { + var holderId: String { id } + + func clubLabel() -> String { + locationLabel() + } + + func subtitleLabel() -> String { + subtitle() + } + + var tournaments: [any TournamentBuildHolder] { + [ + self + ] + } + + func bracketStatus() async -> (status: String, description: String?, cut: TeamRegistration.TeamRange?) { + let availableSeeds = availableSeeds() + var description: String? = nil + if availableSeeds.isEmpty == false { + description = "placer \(availableSeeds.count) équipe\(availableSeeds.count.pluralSuffix)" + } + if description == nil { + let availableQualifiedTeams = availableQualifiedTeams() + if availableQualifiedTeams.isEmpty == false { + description = "placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix + } + } + + var cut: TeamRegistration.TeamRange? = nil + if description == nil && isAnimation() == false { + cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last) + } + + if let round = getActiveRound() { + return ([round.roundTitle(.short), round.roundStatus()].joined(separator: " ").lowercased(), description, cut) + } else { + return ("", description, nil) + } + } + + func groupStageStatus() async -> (status: String, cut: TeamRegistration.TeamRange?) { + let groupStageTeams = groupStageTeams() + let groupStageTeamsCount = groupStageTeams.count + if groupStageTeamsCount == 0 || groupStageTeamsCount != groupStageSpots() { + return ("à compléter", nil) + } + + let cut : TeamRegistration.TeamRange? = isAnimation() ? nil : TeamRegistration.TeamRange(groupStageTeams.first, groupStageTeams.last) + + let runningGroupStages = groupStages().filter({ $0.isRunning() }) + if groupStagesAreOver() { return ("terminées", cut) } + if runningGroupStages.isEmpty { + + let ongoingGroupStages = runningGroupStages.filter({ $0.hasStarted() && $0.hasEnded() == false }) + if ongoingGroupStages.isEmpty == false { + return ("Poule" + ongoingGroupStages.count.pluralSuffix + " " + ongoingGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours", cut) + } + return (groupStages().count.formatted() + " poule" + groupStages().count.pluralSuffix, cut) + } else { + return ("Poule" + runningGroupStages.count.pluralSuffix + " " + runningGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours", cut) + } + } + + func settingsDescriptionLocalizedLabel() -> String { + [courtCount.formatted() + " terrain\(courtCount.pluralSuffix)", entryFeeMessage].joined(separator: ", ") + } + + func structureDescriptionLocalizedLabel() -> String { + let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil + return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ") + } + + fileprivate func _paymentMethodMessage() -> String? { + return DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods + } + + func updateRank(to newDate: Date?) async throws { + guard let newDate else { return } + rankSourceDate = newDate + + 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 formatted: String = URL.importDateFormatter.string(from: newDate) + let monthData: MonthData = MonthData(monthKey: formatted) + monthData.maleUnrankedValue = lastRankMan + monthData.femaleUnrankedValue = lastRankWoman + do { + try DataStore.shared.monthData.addOrUpdate(instance: monthData) + } catch { + Logger.error(error) + } + } + } + + let lastRankMan = currentMonthData()?.maleUnrankedValue + let lastRankWoman = currentMonthData()?.femaleUnrankedValue + let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate } + let sources = dataURLs.map { CSVParser(url: $0) } + + try await unsortedPlayers().concurrentForEach { player in + try await player.updateRank(from: sources, lastRank: (player.sex == .female ? lastRankWoman : lastRankMan) ?? 0) + } + } + + var entryFeeMessage: String { + if let entryFee { + let message: String = "Inscription: \(entryFee.formatted(.currency(code: "EUR"))) par joueur." + return [message, self._paymentMethodMessage()].compactMap { $0 }.joined(separator: "\n") + } else { + return "Inscription: gratuite." + } + } + + func deleteAndBuildEverything() { + resetBracketPosition() + deleteStructure() + deleteGroupStages() + buildGroupStages() + buildBracket() + } + + func buildBracket() { + guard rounds().isEmpty else { return } + let roundCount = RoundRule.numberOfRounds(forTeams: bracketTeamCount()) + + let rounds = (0.. Int { + let players : [PlayerRegistration] = unsortedPlayers() + let selectedTeams : [TeamRegistration] = selectedSortedTeams() + let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) } + let duplicates : [PlayerRegistration] = duplicates(in: players) + let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == nil }) + let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players) + let playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players) + let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) + let waitingList : [TeamRegistration] = waitingListTeams(in: selectedTeams, includingWalkOuts: true) + let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil }) + let waitingListInGroupStage = waitingList.filter({ $0.groupStage != nil }) + + return callDateIssue.count + duplicates.count + problematicPlayers.count + inadequatePlayers.count + playersWithoutValidLicense.count + playersMissing.count + waitingListInBracket.count + waitingListInGroupStage.count + } + + func playersWithoutValidLicense(in players: [PlayerRegistration]) -> [PlayerRegistration] { + let licenseYearValidity = self.licenseYearValidity() + return players.filter { $0.hasInvalidLicence() } + } + + func missingUnrankedValue() -> Bool { + return maleUnrankedValue == nil || femaleUnrankedValue == nil + } + +} + +extension Tournament { + static func newEmptyInstance() -> Tournament { + let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource + var _mostRecentDateAvailable: Date? { + guard let lastDataSource else { return nil } + return URL.importDateFormatter.date(from: lastDataSource) + } + + let rankSourceDate = _mostRecentDateAvailable + let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil && $0.isDeleted == false } + let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments) + let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments) + let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments) +//creator: DataStore.shared.user?.id + return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge) + } + + static func fake() -> Tournament { + return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil) + } + +} diff --git a/PadelClub/Data/User+Extensions.swift b/PadelClub/Data/User+Extensions.swift new file mode 100644 index 0000000..e43044b --- /dev/null +++ b/PadelClub/Data/User+Extensions.swift @@ -0,0 +1,21 @@ +// +// User+Extensions.swift +// PadelClub +// +// Created by Laurent Morvillier on 28/08/2024. +// + +import Foundation + +extension User { + + func currentPlayerData() -> ImportedPlayer? { + guard let licenceId else { return nil } + let federalContext = PersistenceController.shared.localContainer.viewContext + let fetchRequest = ImportedPlayer.fetchRequest() + let predicate = NSPredicate(format: "license == %@", licenceId) + fetchRequest.predicate = predicate + return try? federalContext.fetch(fetchRequest).first + } + +} diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift deleted file mode 100644 index 6ce9d08..0000000 --- a/PadelClub/Data/User.swift +++ /dev/null @@ -1,266 +0,0 @@ -// -// User.swift -// PadelClub -// -// Created by Laurent Morvillier on 21/02/2024. -// - -import Foundation -import LeStorage - -enum UserRight: Int, Codable { - case none = 0 - case edition = 1 - case creation = 2 -} - -@Observable -class User: ModelObject, UserBase, Storable { - - static func resourceName() -> String { "users" } - static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] } - static func filterByStoreIdentifier() -> Bool { return false } - static var relationshipNames: [String] = [] - - public var id: String = Store.randomId() - public var username: String - public var email: String - var clubs: [String] = [] - var umpireCode: String? - var licenceId: String? - var firstName: String - var lastName: String - var phone: String? - var country: String? - - var summonsMessageBody : String? = nil - var summonsMessageSignature: String? = nil - var summonsAvailablePaymentMethods: String? = nil - var summonsDisplayFormat: Bool = false - var summonsDisplayEntryFee: Bool = false - var summonsUseFullCustomMessage: Bool = false - var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil - var bracketMatchFormatPreference: MatchFormat? - var groupStageMatchFormatPreference: MatchFormat? - var loserBracketMatchFormatPreference: MatchFormat? - - var deviceId: String? - - init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?) { - self.username = username - self.firstName = firstName - self.lastName = lastName - self.email = email - self.phone = phone - self.country = country - } - - public func uuid() throws -> UUID { - if let uuid = UUID(uuidString: self.id) { - return uuid - } - throw UUIDError.cantConvertString(string: self.id) - } - - func currentPlayerData() -> ImportedPlayer? { - guard let licenceId else { return nil } - let federalContext = PersistenceController.shared.localContainer.viewContext - let fetchRequest = ImportedPlayer.fetchRequest() - let predicate = NSPredicate(format: "license == %@", licenceId) - fetchRequest.predicate = predicate - return try? federalContext.fetch(fetchRequest).first - } - - func defaultSignature() -> String { - return "Sportivement,\n\(firstName) \(lastName), votre JAP." - } - - func hasTenupClubs() -> Bool { - self.clubsObjects().filter({ $0.code != nil }).isEmpty == false - } - - func hasFavoriteClubsAndCreatedClubs() -> Bool { - clubsObjects(includeCreated: true).isEmpty == false - } - - func setUserClub(_ userClub: Club) { - self.clubs.insert(userClub.id, at: 0) - } - - func clubsObjects(includeCreated: Bool = false) -> [Club] { - return DataStore.shared.clubs.filter({ (includeCreated && $0.creator == id) || clubs.contains($0.id) }) - } - - func createdClubsObjectsNotFavorite() -> [Club] { - return DataStore.shared.clubs.filter({ ($0.creator == id) && clubs.contains($0.id) == false }) - } - - func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) { - if estimatedDuration == matchFormat.defaultEstimatedDuration { - matchFormatsDefaultDuration?.removeValue(forKey: matchFormat) - } else { - matchFormatsDefaultDuration = matchFormatsDefaultDuration ?? [MatchFormat: Int]() - matchFormatsDefaultDuration?[matchFormat] = estimatedDuration - } - } - - func addClub(_ club: Club) { - if !self.clubs.contains(where: { $0.id == club.id }) { - self.clubs.append(club.id) - } - } - - enum CodingKeys: String, CodingKey { - case _id = "id" - case _username = "username" - case _email = "email" - case _clubs = "clubs" - case _umpireCode = "umpireCode" - case _licenceId = "licenceId" - case _firstName = "firstName" - case _lastName = "lastName" - case _phone = "phone" - case _country = "country" - case _summonsMessageBody = "summonsMessageBody" - case _summonsMessageSignature = "summonsMessageSignature" - case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods" - case _summonsDisplayFormat = "summonsDisplayFormat" - case _summonsDisplayEntryFee = "summonsDisplayEntryFee" - case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage" - case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration" - case _bracketMatchFormatPreference = "bracketMatchFormatPreference" - case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" - case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" - case _deviceId = "deviceId" - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(username, forKey: ._username) - try container.encode(email, forKey: ._email) - try container.encode(clubs, forKey: ._clubs) - - if let umpireCode = umpireCode { - try container.encode(umpireCode, forKey: ._umpireCode) - } else { - try container.encodeNil(forKey: ._umpireCode) - } - - if let licenceId = licenceId { - try container.encode(licenceId, forKey: ._licenceId) - } else { - try container.encodeNil(forKey: ._licenceId) - } - - try container.encode(firstName, forKey: ._firstName) - try container.encode(lastName, forKey: ._lastName) - - if let phone = phone { - try container.encode(phone, forKey: ._phone) - } else { - try container.encodeNil(forKey: ._phone) - } - - if let country = country { - try container.encode(country, forKey: ._country) - } else { - try container.encodeNil(forKey: ._country) - } - - if let summonsMessageBody = summonsMessageBody { - try container.encode(summonsMessageBody, forKey: ._summonsMessageBody) - } else { - try container.encodeNil(forKey: ._summonsMessageBody) - } - - if let summonsMessageSignature = summonsMessageSignature { - try container.encode(summonsMessageSignature, forKey: ._summonsMessageSignature) - } else { - try container.encodeNil(forKey: ._summonsMessageSignature) - } - - if let summonsAvailablePaymentMethods = summonsAvailablePaymentMethods { - try container.encode(summonsAvailablePaymentMethods, forKey: ._summonsAvailablePaymentMethods) - } else { - try container.encodeNil(forKey: ._summonsAvailablePaymentMethods) - } - - try container.encode(summonsDisplayFormat, forKey: ._summonsDisplayFormat) - try container.encode(summonsDisplayEntryFee, forKey: ._summonsDisplayEntryFee) - try container.encode(summonsUseFullCustomMessage, forKey: ._summonsUseFullCustomMessage) - - if let matchFormatsDefaultDuration = matchFormatsDefaultDuration { - try container.encode(matchFormatsDefaultDuration, forKey: ._matchFormatsDefaultDuration) - } else { - try container.encodeNil(forKey: ._matchFormatsDefaultDuration) - } - - if let bracketMatchFormatPreference = bracketMatchFormatPreference { - try container.encode(bracketMatchFormatPreference, forKey: ._bracketMatchFormatPreference) - } else { - try container.encodeNil(forKey: ._bracketMatchFormatPreference) - } - - if let groupStageMatchFormatPreference = groupStageMatchFormatPreference { - try container.encode(groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference) - } else { - try container.encodeNil(forKey: ._groupStageMatchFormatPreference) - } - - if let loserBracketMatchFormatPreference = loserBracketMatchFormatPreference { - try container.encode(loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference) - } else { - try container.encodeNil(forKey: ._loserBracketMatchFormatPreference) - } - - if let deviceId { - try container.encode(deviceId, forKey: ._deviceId) - } else { - try container.encodeNil(forKey: ._deviceId) - } - - } - - static func placeHolder() -> User { - return User(username: "", email: "", firstName: "", lastName: "", phone: nil, country: nil) - } - -} - -class UserCreationForm: User, UserPasswordBase { - - init(user: User, username: String, password: String, firstName: String, lastName: String, email: String, phone: String?, country: String?) { - self.password = password - super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country) - - self.summonsMessageBody = user.summonsMessageBody - self.summonsMessageSignature = user.summonsMessageSignature - self.summonsAvailablePaymentMethods = user.summonsAvailablePaymentMethods - self.summonsDisplayFormat = user.summonsDisplayFormat - self.summonsDisplayEntryFee = user.summonsDisplayEntryFee - self.summonsUseFullCustomMessage = user.summonsUseFullCustomMessage - self.matchFormatsDefaultDuration = user.matchFormatsDefaultDuration - self.bracketMatchFormatPreference = user.bracketMatchFormatPreference - self.groupStageMatchFormatPreference = user.groupStageMatchFormatPreference - self.loserBracketMatchFormatPreference = user.loserBracketMatchFormatPreference - - } - - required init(from decoder: Decoder) throws { - fatalError("init(from:) has not been implemented") - } - - public var password: String - - private enum CodingKeys: String, CodingKey { - case password - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.password, forKey: .password) - } -} diff --git a/PadelClub/Extensions/Array+Extensions.swift b/PadelClub/Extensions/Array+Extensions.swift index 62b8d4b..550d4c1 100644 --- a/PadelClub/Extensions/Array+Extensions.swift +++ b/PadelClub/Extensions/Array+Extensions.swift @@ -7,19 +7,6 @@ import Foundation -extension Array { - func chunked(into size: Int) -> [[Element]] { - return stride(from: 0, to: count, by: size).map { - Array(self[$0 ..< Swift.min($0 + size, count)]) - } - } - - func anySatisfy(_ p: (Element) -> Bool) -> Bool { - return first(where: { p($0) }) != nil - //return !self.allSatisfy { !p($0) } - } -} - extension Array where Element: Equatable { /// Remove first collection element that is equal to the given `object` or `element`: diff --git a/PadelClub/Extensions/Date+Extensions.swift b/PadelClub/Extensions/Date+Extensions.swift index 2758f48..0e551fb 100644 --- a/PadelClub/Extensions/Date+Extensions.swift +++ b/PadelClub/Extensions/Date+Extensions.swift @@ -6,6 +6,7 @@ // import Foundation + enum TimeOfDay { case morning case noon @@ -33,8 +34,6 @@ enum TimeOfDay { } - - extension Date { func localizedDate() -> String { self.formatted(.dateTime.weekday().day().month()) + " à " + self.formattedAsHourMinute() @@ -70,36 +69,6 @@ extension Date { } extension Date { - func isInCurrentYear() -> Bool { - let calendar = Calendar.current - let currentYear = calendar.component(.year, from: Date()) - let yearOfDate = calendar.component(.year, from: self) - - return currentYear == yearOfDate - } - - func get(_ components: Calendar.Component..., calendar: Calendar = Calendar.current) -> DateComponents { - return calendar.dateComponents(Set(components), from: self) - } - - func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int { - return calendar.component(component, from: self) - } - - var tomorrowAtNine: Date { - let currentHour = Calendar.current.component(.hour, from: self) - let startOfDay = Calendar.current.startOfDay(for: self) - if currentHour < 8 { - return Calendar.current.date(byAdding: .hour, value: 9, to: startOfDay)! - } else { - let date = Calendar.current.date(byAdding: .day, value: 1, to: startOfDay) - return Calendar.current.date(byAdding: .hour, value: 9, to: date!)! - } - } - - func atBeginningOfDay(hourInt: Int = 9) -> Date { - Calendar.current.date(byAdding: .hour, value: hourInt, to: self.startOfDay)! - } static var firstDayOfWeek = Calendar.current.firstWeekday static var capitalizedFirstLettersOfWeekdays: [String] { @@ -189,34 +158,11 @@ extension Date { Calendar.current.component(.year, from: self) } - var dayInt: Int { - Calendar.current.component(.day, from: self) - } - - var startOfDay: Date { - Calendar.current.startOfDay(for: self) - } - func endOfDay() -> Date { let calendar = Calendar.current return calendar.date(bySettingHour: 23, minute: 59, second: 59, of: self)! } - func atNine() -> Date { - let calendar = Calendar.current - return calendar.date(bySettingHour: 9, minute: 0, second: 0, of: self)! - } - - func atEightAM() -> Date { - let calendar = Calendar.current - return calendar.date(bySettingHour: 8, minute: 0, second: 0, of: self)! - } -} - -extension Date { - func isEarlierThan(_ date: Date) -> Bool { - Calendar.current.compare(self, to: date, toGranularity: .minute) == .orderedAscending - } } extension Date { diff --git a/PadelClub/Extensions/Sequence+Extensions.swift b/PadelClub/Extensions/Sequence+Extensions.swift index 02001b2..053e27b 100644 --- a/PadelClub/Extensions/Sequence+Extensions.swift +++ b/PadelClub/Extensions/Sequence+Extensions.swift @@ -7,21 +7,6 @@ import Foundation -extension Collection { - /// Returns the element at the specified index if it is within bounds, otherwise nil. - subscript (safe index: Index) -> Element? { - return indices.contains(index) ? self[index] : nil - } -} - -extension Sequence { - func sorted(by keyPath: KeyPath) -> [Element] { - return sorted { a, b in - return a[keyPath: keyPath] < b[keyPath: keyPath] - } - } -} - extension Sequence { func pairs() -> AnySequence<(Element, Element)> { AnySequence(zip(self, self.dropFirst())) @@ -46,42 +31,3 @@ extension Sequence { } } } - -enum SortOrder { - case ascending - case descending -} - -extension Sequence { - func sorted(using descriptors: [MySortDescriptor], - order: SortOrder) -> [Element] { - sorted { valueA, valueB in - for descriptor in descriptors { - let result = descriptor.comparator(valueA, valueB) - - switch result { - case .orderedSame: - // Keep iterating if the two elements are equal, - // since that'll let the next descriptor determine - // the sort order: - break - case .orderedAscending: - return order == .ascending - case .orderedDescending: - return order == .descending - } - } - - // If no descriptor was able to determine the sort - // order, we'll default to false (similar to when - // using the '<' operator with the built-in API): - return false - } - } -} -extension Sequence { - func sorted(using descriptors: MySortDescriptor...) -> [Element] { - sorted(using: descriptors, order: .ascending) - } -} - diff --git a/PadelClub/Extensions/String+Extensions.swift b/PadelClub/Extensions/String+Extensions.swift index 1fe097d..be6464a 100644 --- a/PadelClub/Extensions/String+Extensions.swift +++ b/PadelClub/Extensions/String+Extensions.swift @@ -8,31 +8,13 @@ import Foundation // MARK: - Trimming and stuff + extension String { + func trunc(length: Int, trailing: String = "…") -> String { return (self.count > length) ? self.prefix(length) + trailing : self } - var trimmed: String { - trimmingCharacters(in: .whitespacesAndNewlines) - } - - func replaceCharactersFromSet(characterSet: CharacterSet, replacementString: String = "") -> String { - components(separatedBy: characterSet).joined(separator:replacementString) - } - - var canonicalVersion: String { - trimmed.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ").folding(options: .diacriticInsensitive, locale: .current).lowercased() - } - - var canonicalVersionWithPunctuation: String { - trimmed.folding(options: .diacriticInsensitive, locale: .current).lowercased() - } - - var removingFirstCharacter: String { - String(dropFirst()) - } - func isValidEmail() -> Bool { let emailRegEx = "^[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}$" let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegEx) @@ -41,40 +23,6 @@ extension String { } -// MARK: - Club Name -extension String { - func acronym() -> String { - let acronym = canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines) - if acronym.count > 10 { - return concatenateFirstLetters().uppercased() - } else { - return acronym.uppercased() - } - } - - func concatenateFirstLetters() -> String { - // Split the input into sentences - let sentences = self.components(separatedBy: .whitespacesAndNewlines) - if sentences.count == 1 { - return String(self.prefix(10)) - } - // Extract the first character of each sentence - let firstLetters = sentences.compactMap { sentence -> Character? in - let trimmedSentence = sentence.trimmingCharacters(in: .whitespacesAndNewlines) - if trimmedSentence.count > 2 { - if let firstCharacter = trimmedSentence.first { - return firstCharacter - } - } - return nil - } - - // Join the first letters together into a string - let result = String(firstLetters) - return result - } -} - // MARK: - FFT License extension String { var computedLicense: String { diff --git a/PadelClub/Utils/DisplayContext.swift b/PadelClub/Utils/DisplayContext.swift index 1e99890..cd498df 100644 --- a/PadelClub/Utils/DisplayContext.swift +++ b/PadelClub/Utils/DisplayContext.swift @@ -14,12 +14,6 @@ enum DisplayContext { case selection } -enum DisplayStyle { - case title - case wide - case short -} - enum MatchViewStyle { case standardStyle // vue normal case sectionedStandardStyle // vue normal avec des sections indiquant déjà la manche diff --git a/PadelClub/ViewModel/SearchViewModel.swift b/PadelClub/ViewModel/SearchViewModel.swift index a3ec84f..5a41d0d 100644 --- a/PadelClub/ViewModel/SearchViewModel.swift +++ b/PadelClub/ViewModel/SearchViewModel.swift @@ -435,32 +435,3 @@ enum SortOption: Int, CaseIterable, Identifiable { } } } - -enum PlayerFilterOption: Int, Hashable, CaseIterable, Identifiable { - case all = -1 - case male = 1 - case female = 0 - - var id: Int { rawValue } - - func icon() -> String { - switch self { - case .all: - return "Tous" - case .male: - return "Homme" - case .female: - return "Femme" - } - } - - var localizedPlayerLabel: String { - switch self { - case .female: - return "joueuse" - default: - return "joueur" - } - } - -} diff --git a/PadelClub/Views/Cashier/CashierView.swift b/PadelClub/Views/Cashier/CashierView.swift index ce35f7f..00cf6f9 100644 --- a/PadelClub/Views/Cashier/CashierView.swift +++ b/PadelClub/Views/Cashier/CashierView.swift @@ -42,16 +42,16 @@ extension ShareableObject: Transferable { } } -extension Array { - func sorted(by keyPath: KeyPath, order: SortOrder) -> [Element] { - switch order { - case .ascending: - return self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] } - case .descending: - return self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] } - } - } -} +//extension Array { +// func sorted(by keyPath: KeyPath, order: SortOrder) -> [Element] { +// switch order { +// case .ascending: +// return self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] } +// case .descending: +// return self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] } +// } +// } +//} class CashierViewModel: ObservableObject { let id: UUID = UUID() diff --git a/PadelClubData/PadelClubData.xcodeproj/project.pbxproj b/PadelClubData/PadelClubData.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f91ccdd --- /dev/null +++ b/PadelClubData/PadelClubData.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + C42330612C7E118B001DABF5 /* PadelClubData.docc in Sources */ = {isa = PBXBuildFile; fileRef = C42330602C7E118B001DABF5 /* PadelClubData.docc */; }; + C42330672C7E118B001DABF5 /* PadelClubData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C423305C2C7E118B001DABF5 /* PadelClubData.framework */; }; + C423306C2C7E118B001DABF5 /* PadelClubDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C423306B2C7E118B001DABF5 /* PadelClubDataTests.swift */; }; + C423306D2C7E118B001DABF5 /* PadelClubData.h in Headers */ = {isa = PBXBuildFile; fileRef = C423305F2C7E118B001DABF5 /* PadelClubData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C42330782C7E126B001DABF5 /* LeStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C42330772C7E126B001DABF5 /* LeStorage.framework */; }; + C42330792C7E126B001DABF5 /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C42330772C7E126B001DABF5 /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C42330802C7E130A001DABF5 /* Club.swift in Sources */ = {isa = PBXBuildFile; fileRef = C423307F2C7E130A001DABF5 /* Club.swift */; }; + C42330822C7E1330001DABF5 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330812C7E1330001DABF5 /* Court.swift */; }; + C42330942C7E1345001DABF5 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330932C7E1345001DABF5 /* User.swift */; }; + C42330952C7E1345001DABF5 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330912C7E1345001DABF5 /* MockData.swift */; }; + C42330962C7E1345001DABF5 /* Match.swift in Sources */ = {isa = PBXBuildFile; fileRef = C423308B2C7E1344001DABF5 /* Match.swift */; }; + C42330982C7E1345001DABF5 /* Round.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330872C7E1344001DABF5 /* Round.swift */; }; + C42330992C7E1345001DABF5 /* Tournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C423308C2C7E1344001DABF5 /* Tournament.swift */; }; + C423309B2C7E1345001DABF5 /* GroupStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C423308E2C7E1344001DABF5 /* GroupStage.swift */; }; + C423309C2C7E1345001DABF5 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = C423308F2C7E1345001DABF5 /* Event.swift */; }; + C423309D2C7E1345001DABF5 /* Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330842C7E1344001DABF5 /* Purchase.swift */; }; + C423309E2C7E1345001DABF5 /* TeamRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C423308A2C7E1344001DABF5 /* TeamRegistration.swift */; }; + C423309F2C7E1345001DABF5 /* MonthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330852C7E1344001DABF5 /* MonthData.swift */; }; + C42330A02C7E1345001DABF5 /* TeamScore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330832C7E1344001DABF5 /* TeamScore.swift */; }; + C42330A22C7E1345001DABF5 /* MatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330902C7E1345001DABF5 /* MatchScheduler.swift */; }; + C42330A32C7E1345001DABF5 /* DateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330922C7E1345001DABF5 /* DateInterval.swift */; }; + C42330A42C7E1345001DABF5 /* PlayerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330882C7E1344001DABF5 /* PlayerRegistration.swift */; }; + C42330A82C7E1433001DABF5 /* PadelRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330A72C7E1432001DABF5 /* PadelRule.swift */; }; + C42330AB2C7E151A001DABF5 /* PlayerFilterOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330AA2C7E151A001DABF5 /* PlayerFilterOption.swift */; }; + C42330B72C7E17ED001DABF5 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330B62C7E17ED001DABF5 /* String+Extensions.swift */; }; + C42330BB2C7E1D1D001DABF5 /* DisplayStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330BA2C7E1D1D001DABF5 /* DisplayStyle.swift */; }; + C42330BF2C7E20AA001DABF5 /* Sequence+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330BE2C7E20AA001DABF5 /* Sequence+Extensions.swift */; }; + C42330C12C7E2172001DABF5 /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330C02C7E2172001DABF5 /* SeedInterval.swift */; }; + C42330CA2C7E2589001DABF5 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330C92C7E2589001DABF5 /* Date+Extensions.swift */; }; + C42330CC2C7E2643001DABF5 /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330CB2C7E2643001DABF5 /* Screen.swift */; }; + C42330CE2C7E2669001DABF5 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330CD2C7E2669001DABF5 /* MySortDescriptor.swift */; }; + C42330DA2C7E2B3D001DABF5 /* TournamentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330D92C7E2B3D001DABF5 /* TournamentStore.swift */; }; + C42330DC2C7E2B86001DABF5 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = C42330DB2C7E2B86001DABF5 /* Algorithms */; }; + C42330DF2C7F1098001DABF5 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330DE2C7F1098001DABF5 /* DataStore.swift */; }; + C42330E02C7F1098001DABF5 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330DD2C7F1098001DABF5 /* AppSettings.swift */; }; + C42330E22C7F12FA001DABF5 /* PListReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330E12C7F12FA001DABF5 /* PListReader.swift */; }; + C42330E92C7F179D001DABF5 /* Guard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330E32C7F1347001DABF5 /* Guard.swift */; }; + C42330EA2C7F179D001DABF5 /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330E42C7F1347001DABF5 /* StoreManager.swift */; }; + C42330EB2C7F179D001DABF5 /* StoreItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330E52C7F1347001DABF5 /* StoreItem.swift */; }; + C42330ED2C7F17B9001DABF5 /* Patcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330EC2C7F17B8001DABF5 /* Patcher.swift */; }; + C42330EF2C80610D001DABF5 /* String+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330EE2C80610D001DABF5 /* String+Crypto.swift */; }; + C42330F12C8061D9001DABF5 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330F02C8061D9001DABF5 /* Key.swift */; }; + C42330F32C8068D9001DABF5 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42330F22C8068D9001DABF5 /* URL+Extensions.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + C42330682C7E118B001DABF5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C42330532C7E118B001DABF5 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C423305B2C7E118B001DABF5; + remoteInfo = PadelClubData; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + C423307A2C7E126B001DABF5 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + C42330792C7E126B001DABF5 /* LeStorage.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + C423305C2C7E118B001DABF5 /* PadelClubData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PadelClubData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C423305F2C7E118B001DABF5 /* PadelClubData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PadelClubData.h; sourceTree = ""; }; + C42330602C7E118B001DABF5 /* PadelClubData.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = PadelClubData.docc; sourceTree = ""; }; + C42330662C7E118B001DABF5 /* PadelClubDataTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PadelClubDataTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C423306B2C7E118B001DABF5 /* PadelClubDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubDataTests.swift; sourceTree = ""; }; + C42330772C7E126B001DABF5 /* LeStorage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LeStorage.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C423307F2C7E130A001DABF5 /* Club.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Club.swift; sourceTree = ""; }; + C42330812C7E1330001DABF5 /* Court.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Court.swift; sourceTree = ""; }; + C42330832C7E1344001DABF5 /* TeamScore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamScore.swift; sourceTree = ""; }; + C42330842C7E1344001DABF5 /* Purchase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Purchase.swift; sourceTree = ""; }; + C42330852C7E1344001DABF5 /* MonthData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthData.swift; sourceTree = ""; }; + C42330872C7E1344001DABF5 /* Round.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Round.swift; sourceTree = ""; }; + C42330882C7E1344001DABF5 /* PlayerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRegistration.swift; sourceTree = ""; }; + C423308A2C7E1344001DABF5 /* TeamRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRegistration.swift; sourceTree = ""; }; + C423308B2C7E1344001DABF5 /* Match.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Match.swift; sourceTree = ""; }; + C423308C2C7E1344001DABF5 /* Tournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tournament.swift; sourceTree = ""; }; + C423308E2C7E1344001DABF5 /* GroupStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStage.swift; sourceTree = ""; }; + C423308F2C7E1345001DABF5 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; + C42330902C7E1345001DABF5 /* MatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduler.swift; sourceTree = ""; }; + C42330912C7E1345001DABF5 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = ""; }; + C42330922C7E1345001DABF5 /* DateInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateInterval.swift; sourceTree = ""; }; + C42330932C7E1345001DABF5 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + C42330A72C7E1432001DABF5 /* PadelRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelRule.swift; sourceTree = ""; }; + C42330AA2C7E151A001DABF5 /* PlayerFilterOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerFilterOption.swift; sourceTree = ""; }; + C42330B62C7E17ED001DABF5 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; + C42330BA2C7E1D1D001DABF5 /* DisplayStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayStyle.swift; sourceTree = ""; }; + C42330BE2C7E20AA001DABF5 /* Sequence+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Extensions.swift"; sourceTree = ""; }; + C42330C02C7E2172001DABF5 /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = ""; }; + C42330C92C7E2589001DABF5 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; + C42330CB2C7E2643001DABF5 /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = ""; }; + C42330CD2C7E2669001DABF5 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = ""; }; + C42330D92C7E2B3D001DABF5 /* TournamentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStore.swift; sourceTree = ""; }; + C42330DD2C7F1098001DABF5 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; + C42330DE2C7F1098001DABF5 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = ""; }; + C42330E12C7F12FA001DABF5 /* PListReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PListReader.swift; sourceTree = ""; }; + C42330E32C7F1347001DABF5 /* Guard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Guard.swift; sourceTree = ""; }; + C42330E42C7F1347001DABF5 /* StoreManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreManager.swift; sourceTree = ""; }; + C42330E52C7F1347001DABF5 /* StoreItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreItem.swift; sourceTree = ""; }; + C42330EC2C7F17B8001DABF5 /* Patcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Patcher.swift; sourceTree = ""; }; + C42330EE2C80610D001DABF5 /* String+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Crypto.swift"; sourceTree = ""; }; + C42330F02C8061D9001DABF5 /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = ""; }; + C42330F22C8068D9001DABF5 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C42330592C7E118B001DABF5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C42330DC2C7E2B86001DABF5 /* Algorithms in Frameworks */, + C42330782C7E126B001DABF5 /* LeStorage.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C42330632C7E118B001DABF5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C42330672C7E118B001DABF5 /* PadelClubData.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C42330522C7E118B001DABF5 = { + isa = PBXGroup; + children = ( + C423305E2C7E118B001DABF5 /* PadelClubData */, + C423306A2C7E118B001DABF5 /* PadelClubDataTests */, + C423305D2C7E118B001DABF5 /* Products */, + C42330762C7E126B001DABF5 /* Frameworks */, + ); + sourceTree = ""; + }; + C423305D2C7E118B001DABF5 /* Products */ = { + isa = PBXGroup; + children = ( + C423305C2C7E118B001DABF5 /* PadelClubData.framework */, + C42330662C7E118B001DABF5 /* PadelClubDataTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + C423305E2C7E118B001DABF5 /* PadelClubData */ = { + isa = PBXGroup; + children = ( + C42330CB2C7E2643001DABF5 /* Screen.swift */, + C42330BA2C7E1D1D001DABF5 /* DisplayStyle.swift */, + C42330A72C7E1432001DABF5 /* PadelRule.swift */, + C42330AA2C7E151A001DABF5 /* PlayerFilterOption.swift */, + C42330C02C7E2172001DABF5 /* SeedInterval.swift */, + C42330DD2C7F1098001DABF5 /* AppSettings.swift */, + C42330DE2C7F1098001DABF5 /* DataStore.swift */, + C42330D92C7E2B3D001DABF5 /* TournamentStore.swift */, + C42330A92C7E143C001DABF5 /* Model */, + C42330E62C7F1347001DABF5 /* Subscription */, + C423305F2C7E118B001DABF5 /* PadelClubData.h */, + C42330602C7E118B001DABF5 /* PadelClubData.docc */, + C42330B62C7E17ED001DABF5 /* String+Extensions.swift */, + C42330C92C7E2589001DABF5 /* Date+Extensions.swift */, + C42330BE2C7E20AA001DABF5 /* Sequence+Extensions.swift */, + C42330EE2C80610D001DABF5 /* String+Crypto.swift */, + C42330F22C8068D9001DABF5 /* URL+Extensions.swift */, + C42330F02C8061D9001DABF5 /* Key.swift */, + C42330CD2C7E2669001DABF5 /* MySortDescriptor.swift */, + C42330EC2C7F17B8001DABF5 /* Patcher.swift */, + C42330E12C7F12FA001DABF5 /* PListReader.swift */, + ); + path = PadelClubData; + sourceTree = ""; + }; + C423306A2C7E118B001DABF5 /* PadelClubDataTests */ = { + isa = PBXGroup; + children = ( + C423306B2C7E118B001DABF5 /* PadelClubDataTests.swift */, + ); + path = PadelClubDataTests; + sourceTree = ""; + }; + C42330762C7E126B001DABF5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C42330772C7E126B001DABF5 /* LeStorage.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C42330A92C7E143C001DABF5 /* Model */ = { + isa = PBXGroup; + children = ( + C423307F2C7E130A001DABF5 /* Club.swift */, + C42330812C7E1330001DABF5 /* Court.swift */, + C42330922C7E1345001DABF5 /* DateInterval.swift */, + C423308F2C7E1345001DABF5 /* Event.swift */, + C423308E2C7E1344001DABF5 /* GroupStage.swift */, + C423308B2C7E1344001DABF5 /* Match.swift */, + C42330902C7E1345001DABF5 /* MatchScheduler.swift */, + C42330912C7E1345001DABF5 /* MockData.swift */, + C42330852C7E1344001DABF5 /* MonthData.swift */, + C42330882C7E1344001DABF5 /* PlayerRegistration.swift */, + C42330842C7E1344001DABF5 /* Purchase.swift */, + C42330872C7E1344001DABF5 /* Round.swift */, + C423308A2C7E1344001DABF5 /* TeamRegistration.swift */, + C42330832C7E1344001DABF5 /* TeamScore.swift */, + C423308C2C7E1344001DABF5 /* Tournament.swift */, + C42330932C7E1345001DABF5 /* User.swift */, + ); + path = Model; + sourceTree = ""; + }; + C42330E62C7F1347001DABF5 /* Subscription */ = { + isa = PBXGroup; + children = ( + C42330E32C7F1347001DABF5 /* Guard.swift */, + C42330E42C7F1347001DABF5 /* StoreManager.swift */, + C42330E52C7F1347001DABF5 /* StoreItem.swift */, + ); + path = Subscription; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + C42330572C7E118B001DABF5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C423306D2C7E118B001DABF5 /* PadelClubData.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + C423305B2C7E118B001DABF5 /* PadelClubData */ = { + isa = PBXNativeTarget; + buildConfigurationList = C42330702C7E118B001DABF5 /* Build configuration list for PBXNativeTarget "PadelClubData" */; + buildPhases = ( + C42330572C7E118B001DABF5 /* Headers */, + C42330582C7E118B001DABF5 /* Sources */, + C42330592C7E118B001DABF5 /* Frameworks */, + C423305A2C7E118B001DABF5 /* Resources */, + C423307A2C7E126B001DABF5 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PadelClubData; + packageProductDependencies = ( + C42330DB2C7E2B86001DABF5 /* Algorithms */, + ); + productName = PadelClubData; + productReference = C423305C2C7E118B001DABF5 /* PadelClubData.framework */; + productType = "com.apple.product-type.framework"; + }; + C42330652C7E118B001DABF5 /* PadelClubDataTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C42330732C7E118B001DABF5 /* Build configuration list for PBXNativeTarget "PadelClubDataTests" */; + buildPhases = ( + C42330622C7E118B001DABF5 /* Sources */, + C42330632C7E118B001DABF5 /* Frameworks */, + C42330642C7E118B001DABF5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C42330692C7E118B001DABF5 /* PBXTargetDependency */, + ); + name = PadelClubDataTests; + productName = PadelClubDataTests; + productReference = C42330662C7E118B001DABF5 /* PadelClubDataTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C42330532C7E118B001DABF5 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + TargetAttributes = { + C423305B2C7E118B001DABF5 = { + CreatedOnToolsVersion = 15.4; + }; + C42330652C7E118B001DABF5 = { + CreatedOnToolsVersion = 15.4; + }; + }; + }; + buildConfigurationList = C42330562C7E118B001DABF5 /* Build configuration list for PBXProject "PadelClubData" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = C42330522C7E118B001DABF5; + packageReferences = ( + C42330C42C7E23F8001DABF5 /* XCRemoteSwiftPackageReference "swift-algorithms" */, + ); + productRefGroup = C423305D2C7E118B001DABF5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C423305B2C7E118B001DABF5 /* PadelClubData */, + C42330652C7E118B001DABF5 /* PadelClubDataTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C423305A2C7E118B001DABF5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C42330642C7E118B001DABF5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C42330582C7E118B001DABF5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C42330F32C8068D9001DABF5 /* URL+Extensions.swift in Sources */, + C42330E92C7F179D001DABF5 /* Guard.swift in Sources */, + C42330EA2C7F179D001DABF5 /* StoreManager.swift in Sources */, + C42330EB2C7F179D001DABF5 /* StoreItem.swift in Sources */, + C42330BB2C7E1D1D001DABF5 /* DisplayStyle.swift in Sources */, + C42330942C7E1345001DABF5 /* User.swift in Sources */, + C42330A22C7E1345001DABF5 /* MatchScheduler.swift in Sources */, + C423309E2C7E1345001DABF5 /* TeamRegistration.swift in Sources */, + C42330612C7E118B001DABF5 /* PadelClubData.docc in Sources */, + C423309C2C7E1345001DABF5 /* Event.swift in Sources */, + C42330CE2C7E2669001DABF5 /* MySortDescriptor.swift in Sources */, + C42330E22C7F12FA001DABF5 /* PListReader.swift in Sources */, + C42330CC2C7E2643001DABF5 /* Screen.swift in Sources */, + C42330A42C7E1345001DABF5 /* PlayerRegistration.swift in Sources */, + C42330992C7E1345001DABF5 /* Tournament.swift in Sources */, + C42330E02C7F1098001DABF5 /* AppSettings.swift in Sources */, + C423309B2C7E1345001DABF5 /* GroupStage.swift in Sources */, + C42330802C7E130A001DABF5 /* Club.swift in Sources */, + C42330CA2C7E2589001DABF5 /* Date+Extensions.swift in Sources */, + C423309F2C7E1345001DABF5 /* MonthData.swift in Sources */, + C42330B72C7E17ED001DABF5 /* String+Extensions.swift in Sources */, + C42330C12C7E2172001DABF5 /* SeedInterval.swift in Sources */, + C42330822C7E1330001DABF5 /* Court.swift in Sources */, + C42330952C7E1345001DABF5 /* MockData.swift in Sources */, + C42330982C7E1345001DABF5 /* Round.swift in Sources */, + C42330A02C7E1345001DABF5 /* TeamScore.swift in Sources */, + C42330AB2C7E151A001DABF5 /* PlayerFilterOption.swift in Sources */, + C42330DF2C7F1098001DABF5 /* DataStore.swift in Sources */, + C42330A82C7E1433001DABF5 /* PadelRule.swift in Sources */, + C423309D2C7E1345001DABF5 /* Purchase.swift in Sources */, + C42330F12C8061D9001DABF5 /* Key.swift in Sources */, + C42330BF2C7E20AA001DABF5 /* Sequence+Extensions.swift in Sources */, + C42330EF2C80610D001DABF5 /* String+Crypto.swift in Sources */, + C42330962C7E1345001DABF5 /* Match.swift in Sources */, + C42330ED2C7F17B9001DABF5 /* Patcher.swift in Sources */, + C42330DA2C7E2B3D001DABF5 /* TournamentStore.swift in Sources */, + C42330A32C7E1345001DABF5 /* DateInterval.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C42330622C7E118B001DABF5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C423306C2C7E118B001DABF5 /* PadelClubDataTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + C42330692C7E118B001DABF5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C423305B2C7E118B001DABF5 /* PadelClubData */; + targetProxy = C42330682C7E118B001DABF5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + C423306E2C7E118B001DABF5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C423306F2C7E118B001DABF5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + C42330712C7E118B001DABF5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 526E96RFNP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.PadelClubData; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C42330722C7E118B001DABF5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 526E96RFNP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.PadelClubData; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + C42330742C7E118B001DABF5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 526E96RFNP; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.PadelClubDataTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C42330752C7E118B001DABF5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 526E96RFNP; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.PadelClubDataTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C42330562C7E118B001DABF5 /* Build configuration list for PBXProject "PadelClubData" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C423306E2C7E118B001DABF5 /* Debug */, + C423306F2C7E118B001DABF5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C42330702C7E118B001DABF5 /* Build configuration list for PBXNativeTarget "PadelClubData" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C42330712C7E118B001DABF5 /* Debug */, + C42330722C7E118B001DABF5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C42330732C7E118B001DABF5 /* Build configuration list for PBXNativeTarget "PadelClubDataTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C42330742C7E118B001DABF5 /* Debug */, + C42330752C7E118B001DABF5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + C42330C42C7E23F8001DABF5 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-algorithms.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.2.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + C42330DB2C7E2B86001DABF5 /* Algorithms */ = { + isa = XCSwiftPackageProductDependency; + package = C42330C42C7E23F8001DABF5 /* XCRemoteSwiftPackageReference "swift-algorithms" */; + productName = Algorithms; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = C42330532C7E118B001DABF5 /* Project object */; +} diff --git a/PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/xcuserdata/laurentmorvillier.xcuserdatad/UserInterfaceState.xcuserstate b/PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/xcuserdata/laurentmorvillier.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..92ecd7ae67b29e3cabf18084a5239e8919df0065 GIT binary patch literal 10402 zcmd5?33QXywm#=%fM#ftrb(JP9YDagv~&bz=mhivrP5N`QV41Lmj==#CTXFl&*Md9 zP*G6GIcM*0 ze|w+QdRfQck?&PU0he5+Kc_h0Gu`Nh_H}?jm;+N**K+ zlSO1PSwfyB&y#l2K~|GB*CWsd#2oeQJf?k5&f?LKXxtm?C14xdd zkpd}^8fhn#l%-DRTK(1Vzf*B4lImOzzrP)+Pz)th(2in}h6?EhnqXHZWem^GFBqOZ zFeNj6cvebgc3xIWZbnu{N?vY$=79W+jO?Q9T)Q&1xUgt~$2Z;Yb<}Z%p1S5H&K(F% zY(R-9c@;7u6EY(UvLYLbM+sC!#k2?QNu#KQN~vrWN zC4Bd_D>ZB$r@P)Wqu4(j)>c;M<2d)^aOrTLqlvRCQ=nj^!#~D3)dzZCU*T{ybN=Bz zPg6vaIyc1Af;Bg0h9+!*r*_A`sP@!Fltjo&JD$2`C%S*}3 z$%e1O{DPF+frVKqMY+SXGcyYZ=HzAP*p+cN$PyN_r(J2zOdn{=8emIL%gC|i4jf=h zgHL)^`T*O&%uHKU=r9JNJe2$*8iZ~`x1+&m2pWopQ5B7$Y8p#5R7-U)!VwgpLR5r? z!_!DOfH;~=Q)oIo4Pb|$1S!fIJu^xiEzYS9*iF7D{F>+EU{6)hYJ9-yat557e}c0< z(8xC{cKf}6R~}zivvAMrnz0GO-6z3VMfJ|kPNAiYLlr2w1C^n2G@k0Iff_r|1bD5Y zCV1_~M734cdFr`T=swCBNDa@E8a@S(vy5|rJY0Ra7C>}_&(rMXD*$r+p&9L{9wjeF zQ;-AIqB?4(7HXxo<%mNKXe#1pJWZrY@C`G^4tKf&oG++k`K@C>3V>ypZM4H52+zcd z?Mm%kDX4F#68?zhCppt*&{52(Ig-%dR zkK5sEZHHDfDoe`72E7KaQW5f$dEm&PVQ7w7=x(HKLwC_$ZRj4_n}GsT!*2WR2bj4J z{T30d}VXSd2pBz`$xi*&9&{ zP=I^TW8m&rp;yp)aQM5?K6D2Ch<*nLF2phT7I5CFI1iVCldi=aZUiSi3*Unu#E;^~ z!8I@8=O>u|4|F8r53l5v`av`YK)7h8*Tuv3*ye!C>E!vJk^>Y54>wGW%i8pu69ASDFak=Pc5zo zZFe>}0rvT2JcJ&$E7c`sW10g@;4XvgKo2pK44K;k*wf@T^eDZx4V3|m0hILW9>jte zm0A|?HP;22eO%%&@37zohDKV1mavf))6_QfH0{quif{IFzEU68!1;V!eF0e2RFAK< zXl8&5s(o>xe}9lFJdc(xqiMY0%TU|ou-JJ%pQE({Eko^S22hFMYgm&)6R$uUQ1WWD z61@nz@e+C&tww7=N7kZsXg$rKnKX-L(;S*h2hu_GHhMc9yc%spo6u&o1-**4qSw$i z^g4P2ZKp%%Fq%j6X#p*yf2VWli*yxzDTwPK(C+dIWq>AuVrKQFVE=YyzkII2G!yUU^^Z*pwqSUU3myrbi!HC_FG+C_4Spu&X;Ca!083vz$Pm7Ihz0^M>|?Q z%>m#gH^>px1ixLG=;hmms)NM6J`xT=EdWmoxdsPJP{`FbPvt$Mt+S|<@qRxX(G4)_ zpFGAf)7j*l#npFWrYhKbDtH|xBD0VK=wtK=fbwpj-JgQ@DJcu@&}hy*HRzMCD+>}O zB~~Ijlt28>(7uV+)wiSlXvXyg)nmP!yVwm}#;-(xUY+|fnrjG@d&W7ZHU@Zw`T{L& zLx*V*O$#Z>QS=o`UJ2@P9GyTX(J6Eqlwvp?K}XVJ`nQ$nYrvAT@cj)C!BKQHoeJOg z@gz{i?2(6JrpXVBG6v=nr67}LcVw)#AO=hQ)W z1kNR-pimjyav&9jt>BiOb)gb|Xa0pz8@fjCpp}O)!2&=|5mI0=?ty#aC@jHJEW>hm zQeY+U)cQ_p&8sWm2%D}X$a&*|LvYN&F^Ed_2Yi4+H!7OK&*H!??6E72?jVWl9O=gP zs)$-P&%}@fHzK(_0Oz+OuMM7VmUXhjgiweo8ljz`#)eZmB|({Q@ZGSYoydz)itiX-QU-?Dj!W4r zx;jr&s>2H|ia(H4b|Mhk*p)fFt_Qy}O8L^VX0O-d3#9TO89J%dh&J8nZv~#f$NLe8 z%W$Y2S28hJF5sp*-g>IR4d98i5x51O3=6g6DG*K6H3J)Q2X5*^os@PDiuFJzJ3%oB zGhRBadzhJyU7axFr5-xH)B4@KG(Kj_e(LJ7NcliZ}97X5^Lbd|zp}Xe5-ddcVW`DWEH+)~x!C#INKd*MZ|PL=ZFCNu-KpXa@~8X;JLT>4{w@`NJE-Ch{AVh@3x5jv5BxFy z1n+83C;dl0f!U&2vMZZ?mO_Nott$ zhIZ~te1c7L1Rur6@Nv3;K1v^>k1vPB;3j+;^#&F=j4p(16j+q80?0iJs30lF&0uuS zKh80OPxwI+BC@tKV^CJ-@j}|g7Eu}O4tQ9|L7u_s1r`YwrHyD39G$T%esBMQ(K6Uq zKD&x9VjzmE@OStU{vKb(Kj16)M|_n&NuQ#N=wiBrK24vY&$1*Z`>-Tu@B?dMAC~0g zKd`1-+2(}8x_U?;`aLd=#RQ|BwLSeQ9xFIn+fA zp8RKx$8Z5hJrIG&=FHD^FAMBiWYa$tyR5m^-wNSQQ)K)8e>OC@GnOP`+(00VFfL$}IXagDAKmAh~Qs1IZw| zny&3y(O{By2~}`3JTfH?b4E&AOvc>Qw*;gurRzZ2^(>jFfZn0F zl_`1Ru$p%wf_L+C&9S*lelqQU6_f4 zJOvOefC58jG|n#+U^@Ue3LYO{Y3B3(A!fw|x(*nk%^JS8tgew`mlH|jn%#gh0jE0{ z>jbG|0B9QU?_g$vrOY8ytO(7|-*+$(_$QWhaZiPz+@zeIC4JVi8o5iY$q5MD?(%>JZEe^9LaRA2NJ3NU^iHIJ+CeIFyEqp&@P055l6-6In`LfN1L}`Vrj;bnu=|O4COdTKaPGB2XH#f~=&w=*R73 z6?ut%LU;cMl;+=&4YGl3m?elCly=aI}-H;T$7*oot7cK6!)g zZzFHg1As|jgG>nkyCD$0kx4OVcn_A2RB$~QKI|RjBREr5)<$;GLm>>=%`!c#VJOo> zJ|hPi2J9pI$pLzpeo2pXkk82>@&!FgPtva$23TPJoAj5r7k^MOv$|`*u2LX)t&i02 z{8qkWuphg;yJ_Ia_H2UCVI3zY87`in$J)p#dYtyXVJpZP*ow2{9E;3YKpi~o9X8Q+ zdV=5Z^W-0};SeEn?c{vdhJQ=GW8+?=r`pIRdithuVbm++M{>0a#`Oo!fPMv0V;cPB zre~z3q@|^2r)L%wrR5f-XQrhW*qz=+kDK%O?G9g4b|ydf&*XmrHNvS{{wzDl&*Y!M z3m^VHw9;S6?@XfK=$SV12R+LS?XOSAwn2c&Iez&i(TC}Y&T$1oK{V37BoGP2f*yjN zf+&GRAQi|2a{3KDPyazL&~NEQ`W?MQzkdn!7AR4YKq*iQVg(w37NA){FN0gSOn;zP z=#TU&yeCvU{Y5V4RQ@hD7A~eB>M4W}s19zH90ADiHUUk$zPK1J8zEW-N&xPZuP7@V z6(n+Q2!$ht2*p}pT71jv!MG~i8L0X7GcXG4DuAz>W(EL;3+Vt?89pE70PyGqkcZ}F zPDy8%>3Ic(DH$2V3*b6EcR)&h_P~sk{GyEfoT7}Pq71k-4IXMuXpHon)U0Y}DLe4= z?Bw(y_zL<2GxTsbqelrS8Ggbs2rhE(fD6$YG?|QnA0bQuujhv!9o)-Mnam~g;b#X6 z$QH62eo%0MTqKv^hXhy1Ri+-~S2!cNKp{}UQD_7@_)&ooeo&AlC=>()j|sL3jtWh} z9AU1oQdlKy5H<^E2wR1B3GWf!E4)uQTewj8q;QdNiSQZWbHb&<~AIJ>vP|#o}kg&xv0Uw~0H%Z;5w`&xp^7 z&qv9kG*LsMilW?62cnKeU5WZdB9=r+q>^ZfQW7H>Bq@-TO3EbTC6$tDNsWX`=1JyD z9+o^MSuR;E*(BL6*(Et8`ATv|a!ztyazS!Ya!GPoaz%1g^0QPfRY+A*wNxY3N%c~r z)GW11bEkP_|UoAzLSV zUAA4ePj*ChTy|1+T6RhHo9qwSH93|Gm!lPAct<)h`*@&@^X z^11S-ZQ?;n>QO#35t6Hwwt2(H<79)#^jj_cf#3aS^j_Dth6*DNNJZ3@6b1^Go zw#U31b2#R7%$b;TG3R4`P|MWOYNa||ouSTB=cos&Z&MFe4^`)>tJO8?$?7TUT6Mj; zLG4ld)B$yidbauj^&Is)^J92m>UY#T)w|T6sQ0K3st>6TtBF4A^r*J#&j*K0RwH*4S2zO8*%`@Z%=?N04y+9TSt+COv> zom!{S>2!LXQJ19at?Q%fr%TqQ>PG9Fx`%ZubZd3%bQ^SU>GtaO=?>^V*L|V;Qg>8$ zTz67;S$9QuRrj;*pSoXlzw7=LhvG8HPcI z+YLhu!wmU`QHDxGm7&^DW0-81Zg3mC2EU=%FvCC%4;bbc<{2I`EHS)bXft#eRv6Y8 z)*99uHX1e?-ZJbo95fs?95{TrgZTTrylX5~I`@XG}L{7_*Ey#(~D$jDw9s zjd{ibW07%$vDjE^tT#3o8;#S9E~DG%HTsMJV~cU7ahCCJ<6`4x<3ZzTQ%{q@lw%rY zsx{qhT3}jXT5H;1+GN^dde8KsX{YI9({9tJrlY14rcJ1mu!NtS7rCX2`7vji+HmisLWEzeq(T9#SbE$b{B zEt@S{E!!+_SU#}qwCu8cVmWL%X*q5A+H%%%(Q?Uh*>c5l)vB?Mv9?%eTOYK}wLWBh z#QLc9dFx8+TI*KpHtT!VUDi*nN3AEUr>tLF&sx8+{$%}^t*1?5%d!ozmD(oQrr4Tn zUYpO>Y@13Vtdv0n(af|VcQYgG202-Dce`JGq!WK^R^4NKWx|Hal9a26yGC0 zDqb2dk5|O2;!W`x@uTDI@h$NW#XlAQO8k!ay$QkuNrEmRA>r19 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PadelClubData/PadelClubData.xcodeproj/xcuserdata/laurentmorvillier.xcuserdatad/xcschemes/xcschememanagement.plist b/PadelClubData/PadelClubData.xcodeproj/xcuserdata/laurentmorvillier.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..c8a78c6 --- /dev/null +++ b/PadelClubData/PadelClubData.xcodeproj/xcuserdata/laurentmorvillier.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + PadelClubData.xcscheme_^#shared#^_ + + orderHint + 3 + + + SuppressBuildableAutocreation + + C423305B2C7E118B001DABF5 + + primary + + + C42330652C7E118B001DABF5 + + primary + + + + + diff --git a/PadelClub/Data/AppSettings.swift b/PadelClubData/PadelClubData/AppSettings.swift similarity index 100% rename from PadelClub/Data/AppSettings.swift rename to PadelClubData/PadelClubData/AppSettings.swift diff --git a/PadelClub/Data/DataStore.swift b/PadelClubData/PadelClubData/DataStore.swift similarity index 99% rename from PadelClub/Data/DataStore.swift rename to PadelClubData/PadelClubData/DataStore.swift index 2a05d74..de5e55d 100644 --- a/PadelClub/Data/DataStore.swift +++ b/PadelClubData/PadelClubData/DataStore.swift @@ -51,7 +51,7 @@ class DataStore: ObservableObject { init() { let store = Store.main - let serverURL: String = URLs.api.rawValue + let serverURL: String = "" // URLs.api.rawValue StoreCenter.main.blackListUserName("apple-test") #if DEBUG diff --git a/PadelClubData/PadelClubData/Date+Extensions.swift b/PadelClubData/PadelClubData/Date+Extensions.swift new file mode 100644 index 0000000..2efd173 --- /dev/null +++ b/PadelClubData/PadelClubData/Date+Extensions.swift @@ -0,0 +1,65 @@ +// +// Date+Extensions.swift +// PadelClubData +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation + +extension Date { + + var dayInt: Int { + Calendar.current.component(.day, from: self) + } + + func isEarlierThan(_ date: Date) -> Bool { + Calendar.current.compare(self, to: date, toGranularity: .minute) == .orderedAscending + } + + func atNine() -> Date { + let calendar = Calendar.current + return calendar.date(bySettingHour: 9, minute: 0, second: 0, of: self)! + } + + func atEightAM() -> Date { + let calendar = Calendar.current + return calendar.date(bySettingHour: 8, minute: 0, second: 0, of: self)! + } + + var tomorrowAtNine: Date { + let currentHour = Calendar.current.component(.hour, from: self) + let startOfDay = Calendar.current.startOfDay(for: self) + if currentHour < 8 { + return Calendar.current.date(byAdding: .hour, value: 9, to: startOfDay)! + } else { + let date = Calendar.current.date(byAdding: .day, value: 1, to: startOfDay) + return Calendar.current.date(byAdding: .hour, value: 9, to: date!)! + } + } + + func isInCurrentYear() -> Bool { + let calendar = Calendar.current + let currentYear = calendar.component(.year, from: Date()) + let yearOfDate = calendar.component(.year, from: self) + + return currentYear == yearOfDate + } + + func get(_ components: Calendar.Component..., calendar: Calendar = Calendar.current) -> DateComponents { + return calendar.dateComponents(Set(components), from: self) + } + + func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int { + return calendar.component(component, from: self) + } + + func atBeginningOfDay(hourInt: Int = 9) -> Date { + Calendar.current.date(byAdding: .hour, value: hourInt, to: self.startOfDay)! + } + + var startOfDay: Date { + Calendar.current.startOfDay(for: self) + } + +} diff --git a/PadelClubData/PadelClubData/DisplayStyle.swift b/PadelClubData/PadelClubData/DisplayStyle.swift new file mode 100644 index 0000000..36500aa --- /dev/null +++ b/PadelClubData/PadelClubData/DisplayStyle.swift @@ -0,0 +1,14 @@ +// +// DisplayStyle.swift +// PadelClubData +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation + +enum DisplayStyle { + case title + case wide + case short +} diff --git a/PadelClub/Utils/Key.swift b/PadelClubData/PadelClubData/Key.swift similarity index 100% rename from PadelClub/Utils/Key.swift rename to PadelClubData/PadelClubData/Key.swift diff --git a/PadelClubData/PadelClubData/Model/Club.swift b/PadelClubData/PadelClubData/Model/Club.swift new file mode 100644 index 0000000..0e6923a --- /dev/null +++ b/PadelClubData/PadelClubData/Model/Club.swift @@ -0,0 +1,171 @@ +// +// Club.swift +// PadelClub +// +// Created by Laurent Morvillier on 02/02/2024. +// + +import Foundation +import SwiftUI +import LeStorage + +final class Club : ModelObject, Storable, Hashable { + + static func resourceName() -> String { return "clubs" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [.get] } + static func filterByStoreIdentifier() -> Bool { return false } + static var relationshipNames: [String] = [] + + static func == (lhs: Club, rhs: Club) -> Bool { + lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + return hasher.combine(id) + } + + var id: String = Store.randomId() + var creator: String? + var name: String + var acronym: String + var phone: String? + var code: String? + //var federalClubData: Data? + var address: String? + var city: String? + var zipCode: String? + var latitude: Double? + var longitude: Double? + var courtCount: Int = 2 + var broadcastCode: String? +// var alphabeticalName: Bool = false + + internal init(creator: String? = nil, 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, courtCount: Int = 2, broadcastCode: String? = nil) { + self.name = name + self.creator = creator + self.acronym = acronym ?? name.acronym() + self.phone = phone + self.code = code + self.address = address + self.city = city + self.zipCode = zipCode + self.latitude = latitude + self.longitude = longitude + self.courtCount = courtCount + self.broadcastCode = broadcastCode + } + + override func copyFromServerInstance(_ instance: any Storable) -> Bool { + guard let copy = instance as? Club else { return false } + self.broadcastCode = copy.broadcastCode +// Logger.log("write code: \(self.broadcastCode)") + return true + } + + func clubTitle(_ displayStyle: DisplayStyle = .wide) -> String { + switch displayStyle { + case .wide, .title: + return name + case .short: + return acronym + } + } + + var customizedCourts: [Court] { + let courts: [Court] = DataStore.shared.courts.filter { $0.club == self.id } + return courts.sorted(by: \Court.index) + } + + override func deleteDependencies() throws { + let customizedCourts = self.customizedCourts + for customizedCourt in customizedCourts { + try customizedCourt.deleteDependencies() + } + DataStore.shared.courts.deleteDependencies(customizedCourts) + } + +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _creator = "creator" +// case _name = "name" +// case _acronym = "acronym" +// case _phone = "phone" +// case _code = "code" +// case _address = "address" +// case _city = "city" +// case _zipCode = "zipCode" +// case _latitude = "latitude" +// case _longitude = "longitude" +// case _courtCount = "courtCount" +// case _broadcastCode = "broadcastCode" +//// case _alphabeticalName = "alphabeticalName" +// } + +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// +// if let creator = creator { +// try container.encode(creator, forKey: ._creator) +// } else { +// try container.encodeNil(forKey: ._creator) +// } +// +// try container.encode(name, forKey: ._name) +// try container.encode(acronym, forKey: ._acronym) +// +// if let phone = phone { +// try container.encode(phone, forKey: ._phone) +// } else { +// try container.encodeNil(forKey: ._phone) +// } +// +// if let code = code { +// try container.encode(code, forKey: ._code) +// } else { +// try container.encodeNil(forKey: ._code) +// } +// +// if let address = address { +// try container.encode(address, forKey: ._address) +// } else { +// try container.encodeNil(forKey: ._address) +// } +// +// if let city = city { +// try container.encode(city, forKey: ._city) +// } else { +// try container.encodeNil(forKey: ._city) +// } +// +// if let zipCode = zipCode { +// try container.encode(zipCode, forKey: ._zipCode) +// } else { +// try container.encodeNil(forKey: ._zipCode) +// } +// +// if let latitude = latitude { +// try container.encode(latitude, forKey: ._latitude) +// } else { +// try container.encodeNil(forKey: ._latitude) +// } +// +// if let longitude = longitude { +// try container.encode(longitude, forKey: ._longitude) +// } else { +// try container.encodeNil(forKey: ._longitude) +// } +// +// try container.encode(courtCount, forKey: ._courtCount) +// +// if let broadcastCode { +// try container.encode(broadcastCode, forKey: ._broadcastCode) +// } else { +// try container.encodeNil(forKey: ._broadcastCode) +// } +// +// // try container.encode(alphabeticalName, forKey: ._alphabeticalName) +// } + +} diff --git a/PadelClub/Data/Court.swift b/PadelClubData/PadelClubData/Model/Court.swift similarity index 65% rename from PadelClub/Data/Court.swift rename to PadelClubData/PadelClubData/Model/Court.swift index 22f29a0..47ae6de 100644 --- a/PadelClub/Data/Court.swift +++ b/PadelClubData/PadelClubData/Model/Court.swift @@ -9,7 +9,6 @@ import Foundation import SwiftUI import LeStorage -@Observable final class Court : ModelObject, Storable, Hashable { static func resourceName() -> String { return "courts" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } @@ -64,30 +63,30 @@ final class Court : ModelObject, Storable, Hashable { override func deleteDependencies() throws { } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _index = "index" - case _club = "club" - case _name = "name" - case _exitAllowed = "exitAllowed" - case _indoor = "indoor" - } +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _index = "index" +// case _club = "club" +// case _name = "name" +// case _exitAllowed = "exitAllowed" +// case _indoor = "indoor" +// } - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(index, forKey: ._index) - try container.encode(club, forKey: ._club) - - if let name = name { - try container.encode(name, forKey: ._name) - } else { - try container.encodeNil(forKey: ._name) - } - - try container.encode(exitAllowed, forKey: ._exitAllowed) - try container.encode(indoor, forKey: ._indoor) - } +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// try container.encode(index, forKey: ._index) +// try container.encode(club, forKey: ._club) +// +// if let name = name { +// try container.encode(name, forKey: ._name) +// } else { +// try container.encodeNil(forKey: ._name) +// } +// +// try container.encode(exitAllowed, forKey: ._exitAllowed) +// try container.encode(indoor, forKey: ._indoor) +// } } diff --git a/PadelClub/Data/DateInterval.swift b/PadelClubData/PadelClubData/Model/DateInterval.swift similarity index 86% rename from PadelClub/Data/DateInterval.swift rename to PadelClubData/PadelClubData/Model/DateInterval.swift index 23438bd..0231404 100644 --- a/PadelClub/Data/DateInterval.swift +++ b/PadelClubData/PadelClubData/Model/DateInterval.swift @@ -9,7 +9,6 @@ import Foundation import SwiftUI import LeStorage -@Observable final class DateInterval: ModelObject, Storable { static func resourceName() -> String { return "date-intervals" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } @@ -48,13 +47,13 @@ final class DateInterval: ModelObject, Storable { override func deleteDependencies() throws { } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _event = "event" - case _courtIndex = "courtIndex" - case _startDate = "startDate" - case _endDate = "endDate" - } +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _event = "event" +// case _courtIndex = "courtIndex" +// case _startDate = "startDate" +// case _endDate = "endDate" +// } func insertOnServer() throws { try DataStore.shared.dateIntervals.writeChangeAndInsertOnServer(instance: self) diff --git a/PadelClub/Data/Event.swift b/PadelClubData/PadelClubData/Model/Event.swift similarity index 70% rename from PadelClub/Data/Event.swift rename to PadelClubData/PadelClubData/Model/Event.swift index 54c68d8..707225c 100644 --- a/PadelClub/Data/Event.swift +++ b/PadelClubData/PadelClubData/Model/Event.swift @@ -9,7 +9,6 @@ import Foundation import LeStorage import SwiftUI -@Observable final class Event: ModelObject, Storable { static func resourceName() -> String { return "events" } @@ -107,45 +106,45 @@ final class Event: ModelObject, Storable { extension Event { - enum CodingKeys: String, CodingKey { - case _id = "id" - case _creator = "creator" - case _club = "club" - case _creationDate = "creationDate" - case _name = "name" - case _tenupId = "tenupId" - } +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _creator = "creator" +// case _club = "club" +// case _creationDate = "creationDate" +// case _name = "name" +// case _tenupId = "tenupId" +// } - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - - if let creator = creator { - try container.encode(creator, forKey: ._creator) - } else { - try container.encodeNil(forKey: ._creator) - } - - if let club = club { - try container.encode(club, forKey: ._club) - } else { - try container.encodeNil(forKey: ._club) - } - - try container.encode(creationDate, forKey: ._creationDate) - - if let name = name { - try container.encode(name, forKey: ._name) - } else { - try container.encodeNil(forKey: ._name) - } - - if let tenupId = tenupId { - try container.encode(tenupId, forKey: ._tenupId) - } else { - try container.encodeNil(forKey: ._tenupId) - } - } +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// +// if let creator = creator { +// try container.encode(creator, forKey: ._creator) +// } else { +// try container.encodeNil(forKey: ._creator) +// } +// +// if let club = club { +// try container.encode(club, forKey: ._club) +// } else { +// try container.encodeNil(forKey: ._club) +// } +// +// try container.encode(creationDate, forKey: ._creationDate) +// +// if let name = name { +// try container.encode(name, forKey: ._name) +// } else { +// try container.encodeNil(forKey: ._name) +// } +// +// if let tenupId = tenupId { +// try container.encode(tenupId, forKey: ._tenupId) +// } else { +// try container.encodeNil(forKey: ._tenupId) +// } +// } } diff --git a/PadelClub/Data/GroupStage.swift b/PadelClubData/PadelClubData/Model/GroupStage.swift similarity index 86% rename from PadelClub/Data/GroupStage.swift rename to PadelClubData/PadelClubData/Model/GroupStage.swift index ac2d9c7..ab2d6a7 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClubData/PadelClubData/Model/GroupStage.swift @@ -10,7 +10,6 @@ import LeStorage import Algorithms import SwiftUI -@Observable final class GroupStage: ModelObject, Storable { static func resourceName() -> String { "group-stages" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } @@ -64,16 +63,6 @@ final class GroupStage: ModelObject, Storable { teams().first(where: { $0.groupStagePosition == groupStagePosition }) } - func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String { - if let name { return name } - switch displayStyle { - case .wide, .title: - return "Poule \(index + 1)" - case .short: - return "#\(index + 1)" - } - } - func isRunning() -> Bool { // at least a match has started _matches().anySatisfy({ $0.isRunning() }) } @@ -374,16 +363,6 @@ final class GroupStage: ModelObject, Storable { } } - func pasteData() -> String { - var data: [String] = [] - data.append(self.groupStageTitle()) - teams().forEach { team in - data.append(team.teamLabelRanked(displayRank: true, displayTeamName: true)) - } - - return data.joined(separator: "\n") - } - override func deleteDependencies() throws { let matches = self._matches() for match in matches { @@ -392,32 +371,32 @@ final class GroupStage: ModelObject, Storable { self.tournamentStore.matches.deleteDependencies(matches) } - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(tournament, forKey: ._tournament) - try container.encode(index, forKey: ._index) - try container.encode(size, forKey: ._size) - - if let format = format { - try container.encode(format, forKey: ._format) - } else { - try container.encodeNil(forKey: ._format) - } - - if let startDate = startDate { - try container.encode(startDate, forKey: ._startDate) - } else { - try container.encodeNil(forKey: ._startDate) - } - - if let name = name { - try container.encode(name, forKey: ._name) - } else { - try container.encodeNil(forKey: ._name) - } - } +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// try container.encode(tournament, forKey: ._tournament) +// try container.encode(index, forKey: ._index) +// try container.encode(size, forKey: ._size) +// +// if let format = format { +// try container.encode(format, forKey: ._format) +// } else { +// try container.encodeNil(forKey: ._format) +// } +// +// if let startDate = startDate { +// try container.encode(startDate, forKey: ._startDate) +// } else { +// try container.encodeNil(forKey: ._startDate) +// } +// +// if let name = name { +// try container.encode(name, forKey: ._name) +// } else { +// try container.encodeNil(forKey: ._name) +// } +// } func insertOnServer() { self.tournamentStore.groupStages.writeChangeAndInsertOnServer(instance: self) @@ -428,36 +407,14 @@ final class GroupStage: ModelObject, Storable { } -extension GroupStage { - enum CodingKeys: String, CodingKey { - case _id = "id" - case _tournament = "tournament" - case _index = "index" - case _size = "size" - case _format = "format" - case _startDate = "startDate" - case _name = "name" - } -} - -extension GroupStage: Selectable { - func selectionLabel(index: Int) -> String { - groupStageTitle() - } - - func badgeValue() -> Int? { - return runningMatches(playedMatches: _matches()).count - } - - func badgeValueColor() -> Color? { - return nil - } - - func badgeImage() -> Badge? { - if teams().count < size { - return .xmark - } else { - return hasEnded() ? .checkmark : nil - } - } -} +//extension GroupStage { +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _tournament = "tournament" +// case _index = "index" +// case _size = "size" +// case _format = "format" +// case _startDate = "startDate" +// case _name = "name" +// } +//} diff --git a/PadelClub/Data/Match.swift b/PadelClubData/PadelClubData/Model/Match.swift similarity index 81% rename from PadelClub/Data/Match.swift rename to PadelClubData/PadelClubData/Model/Match.swift index 148abb1..33900ac 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClubData/PadelClubData/Model/Match.swift @@ -8,17 +8,12 @@ import Foundation import LeStorage -@Observable final class Match: ModelObject, Storable { static func resourceName() -> String { "matches" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } static func filterByStoreIdentifier() -> Bool { return true } static var relationshipNames: [String] = ["round", "groupStage"] - static func setServerTitle(upperRound: Round, matchIndex: Int) -> String { - if upperRound.index == 0 { return upperRound.roundTitle() } - return upperRound.roundTitle() + " #" + (matchIndex + 1).formatted() - } var byeState: Bool = false @@ -99,40 +94,15 @@ defer { #endif if groupStage != nil { return index - } else if let index = (matches ?? roundObject?.playedMatches().sorted(by: \.index))?.firstIndex(where: { $0.id == id }) { - return index + } else { + let matches = (matches ?? roundObject?.playedMatches().sorted(by: \.index)) + if let index = matches?.firstIndex(where: { $0.id == id }) { + return index + } } return RoundRule.matchIndexWithinRound(fromMatchIndex: index) } - func matchWarningSubject() -> String { - [roundTitle(), matchTitle(.short)].compacted().joined(separator: " ") - } - - func matchWarningMessage() -> String { - [roundTitle(), matchTitle(.short), startDate?.localizedDate(), courtName()].compacted().joined(separator: "\n") - } - - func matchTitle(_ displayStyle: DisplayStyle = .wide, inMatches matches: [Match]? = nil) -> String { -#if _DEBUG_TIME //DEBUGING TIME -let start = Date() -defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func matchTitle", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) -} -#endif - if let groupStageObject { - return groupStageObject.localizedMatchUpLabel(for: index) - } - - switch displayStyle { - case .wide, .title: - return "Match \(indexInRound(in: matches) + 1)" - case .short: - return "#\(indexInRound(in: matches) + 1)" - } - } - func isSeedLocked(atTeamPosition teamPosition: TeamPosition) -> Bool { return previousMatch(teamPosition)?.disabled == true } @@ -415,12 +385,6 @@ defer { } } - func roundTitle() -> String? { - if groupStage != nil { return groupStageObject?.groupStageTitle() } - else if let roundObject { return roundObject.roundTitle() } - else { return nil } - } - func topPreviousRoundMatchIndex() -> Int { return index * 2 + 1 } @@ -499,44 +463,6 @@ defer { updateFollowingMatchTeamScore() } - func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { - updateScore(fromMatchDescriptor: matchDescriptor) - if endDate == nil { - endDate = Date() - } - if startDate == nil { - startDate = endDate?.addingTimeInterval(Double(-getDuration()*60)) - } - - let teamOne = team(matchDescriptor.winner) - let teamTwo = team(matchDescriptor.winner.otherTeam) - - teamOne?.hasArrived() - teamTwo?.hasArrived() - - winningTeamId = teamOne?.id - losingTeamId = teamTwo?.id - - confirmed = true - - groupStageObject?.updateGroupStageState() - roundObject?.updateTournamentState() - updateFollowingMatchTeamScore() - } - - func updateScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { - let teamScoreOne = teamScore(.one) ?? TeamScore(match: id, team: team(.one)) - teamScoreOne.score = matchDescriptor.teamOneScores.joined(separator: ",") - let teamScoreTwo = teamScore(.two) ?? TeamScore(match: id, team: team(.two)) - teamScoreTwo.score = matchDescriptor.teamTwoScores.joined(separator: ",") - do { - try self.tournamentStore.teamScores.addOrUpdate(contentOfs: [teamScoreOne, teamScoreTwo]) - } catch { - Logger.error(error) - } - matchFormat = matchDescriptor.matchFormat - } - func updateFollowingMatchTeamScore() { followingMatch()?.updateTeamScores() _loserMatch()?.updateTeamScores() @@ -789,10 +715,6 @@ defer { } } - func teamNames(_ team: TeamRegistration?) -> [String]? { - return team?.players().map { $0.playerLabel() } - } - func teamWalkOut(_ team: TeamRegistration?) -> Bool { return teamScore(ofTeam: team)?.isWalkOut() == true } @@ -840,97 +762,97 @@ defer { return roundObject?.parent != nil } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _round = "round" - case _groupStage = "groupStage" - case _startDate = "startDate" - case _endDate = "endDate" - case _index = "index" - case _format = "format" -// case _court = "court" - case _courtIndex = "courtIndex" - case _servingTeamId = "servingTeamId" - case _winningTeamId = "winningTeamId" - case _losingTeamId = "losingTeamId" -// case _broadcasted = "broadcasted" - case _name = "name" -// case _order = "order" - case _disabled = "disabled" - case _confirmed = "confirmed" - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - - if let round = round { - try container.encode(round, forKey: ._round) - } else { - try container.encodeNil(forKey: ._round) - } - - if let groupStage = groupStage { - try container.encode(groupStage, forKey: ._groupStage) - } else { - try container.encodeNil(forKey: ._groupStage) - } - - if let startDate = startDate { - try container.encode(startDate, forKey: ._startDate) - } else { - try container.encodeNil(forKey: ._startDate) - } - - if let endDate = endDate { - try container.encode(endDate, forKey: ._endDate) - } else { - try container.encodeNil(forKey: ._endDate) - } - - try container.encode(index, forKey: ._index) - - if let format = format { - try container.encode(format, forKey: ._format) - } else { - try container.encodeNil(forKey: ._format) - } - - if let servingTeamId = servingTeamId { - try container.encode(servingTeamId, forKey: ._servingTeamId) - } else { - try container.encodeNil(forKey: ._servingTeamId) - } - - if let winningTeamId = winningTeamId { - try container.encode(winningTeamId, forKey: ._winningTeamId) - } else { - try container.encodeNil(forKey: ._winningTeamId) - } - - if let losingTeamId = losingTeamId { - try container.encode(losingTeamId, forKey: ._losingTeamId) - } else { - try container.encodeNil(forKey: ._losingTeamId) - } - - if let name = name { - try container.encode(name, forKey: ._name) - } else { - try container.encodeNil(forKey: ._name) - } - - try container.encode(disabled, forKey: ._disabled) - - if let courtIndex = courtIndex { - try container.encode(courtIndex, forKey: ._courtIndex) - } else { - try container.encodeNil(forKey: ._courtIndex) - } - - try container.encode(confirmed, forKey: ._confirmed) - } +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _round = "round" +// case _groupStage = "groupStage" +// case _startDate = "startDate" +// case _endDate = "endDate" +// case _index = "index" +// case _format = "format" +//// case _court = "court" +// case _courtIndex = "courtIndex" +// case _servingTeamId = "servingTeamId" +// case _winningTeamId = "winningTeamId" +// case _losingTeamId = "losingTeamId" +//// case _broadcasted = "broadcasted" +// case _name = "name" +//// case _order = "order" +// case _disabled = "disabled" +// case _confirmed = "confirmed" +// } + +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// +// if let round = round { +// try container.encode(round, forKey: ._round) +// } else { +// try container.encodeNil(forKey: ._round) +// } +// +// if let groupStage = groupStage { +// try container.encode(groupStage, forKey: ._groupStage) +// } else { +// try container.encodeNil(forKey: ._groupStage) +// } +// +// if let startDate = startDate { +// try container.encode(startDate, forKey: ._startDate) +// } else { +// try container.encodeNil(forKey: ._startDate) +// } +// +// if let endDate = endDate { +// try container.encode(endDate, forKey: ._endDate) +// } else { +// try container.encodeNil(forKey: ._endDate) +// } +// +// try container.encode(index, forKey: ._index) +// +// if let format = format { +// try container.encode(format, forKey: ._format) +// } else { +// try container.encodeNil(forKey: ._format) +// } +// +// if let servingTeamId = servingTeamId { +// try container.encode(servingTeamId, forKey: ._servingTeamId) +// } else { +// try container.encodeNil(forKey: ._servingTeamId) +// } +// +// if let winningTeamId = winningTeamId { +// try container.encode(winningTeamId, forKey: ._winningTeamId) +// } else { +// try container.encodeNil(forKey: ._winningTeamId) +// } +// +// if let losingTeamId = losingTeamId { +// try container.encode(losingTeamId, forKey: ._losingTeamId) +// } else { +// try container.encodeNil(forKey: ._losingTeamId) +// } +// +// if let name = name { +// try container.encode(name, forKey: ._name) +// } else { +// try container.encodeNil(forKey: ._name) +// } +// +// try container.encode(disabled, forKey: ._disabled) +// +// if let courtIndex = courtIndex { +// try container.encode(courtIndex, forKey: ._courtIndex) +// } else { +// try container.encodeNil(forKey: ._courtIndex) +// } +// +// try container.encode(confirmed, forKey: ._confirmed) +// } func insertOnServer() { self.tournamentStore.matches.writeChangeAndInsertOnServer(instance: self) diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClubData/PadelClubData/Model/MatchScheduler.swift similarity index 96% rename from PadelClub/Data/MatchScheduler.swift rename to PadelClubData/PadelClubData/Model/MatchScheduler.swift index 3e7c92c..3f9f5e4 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClubData/PadelClubData/Model/MatchScheduler.swift @@ -9,7 +9,6 @@ import Foundation import LeStorage import SwiftUI -@Observable final class MatchScheduler : ModelObject, Storable { static func resourceName() -> String { return "match-scheduler" } @@ -58,22 +57,22 @@ final class MatchScheduler : ModelObject, Storable { self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _tournament = "tournament" - case _timeDifferenceLimit = "timeDifferenceLimit" - case _loserBracketRotationDifference = "loserBracketRotationDifference" - case _upperBracketRotationDifference = "upperBracketRotationDifference" - case _accountUpperBracketBreakTime = "accountUpperBracketBreakTime" - case _accountLoserBracketBreakTime = "accountLoserBracketBreakTime" - case _randomizeCourts = "randomizeCourts" - case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant" - case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice" - case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext" - case _groupStageChunkCount = "groupStageChunkCount" - case _overrideCourtsUnavailability = "overrideCourtsUnavailability" - case _shouldTryToFillUpCourtsAvailable = "shouldTryToFillUpCourtsAvailable" - } +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _tournament = "tournament" +// case _timeDifferenceLimit = "timeDifferenceLimit" +// case _loserBracketRotationDifference = "loserBracketRotationDifference" +// case _upperBracketRotationDifference = "upperBracketRotationDifference" +// case _accountUpperBracketBreakTime = "accountUpperBracketBreakTime" +// case _accountLoserBracketBreakTime = "accountLoserBracketBreakTime" +// case _randomizeCourts = "randomizeCourts" +// case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant" +// case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice" +// case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext" +// case _groupStageChunkCount = "groupStageChunkCount" +// case _overrideCourtsUnavailability = "overrideCourtsUnavailability" +// case _shouldTryToFillUpCourtsAvailable = "shouldTryToFillUpCourtsAvailable" +// } var courtsUnavailability: [DateInterval]? { guard let event = tournamentObject()?.eventObject() else { return nil } @@ -266,72 +265,6 @@ final class MatchScheduler : ModelObject, Storable { } } - func roundMatchCanBePlayed(_ match: Match, roundObject: Round, slots: [TimeMatch], rotationIndex: Int, targetedStartDate: Date, minimumTargetedEndDate: inout Date) -> Bool { - print(roundObject.roundTitle(), match.matchTitle()) - - if let roundStartDate = roundObject.startDate, targetedStartDate < roundStartDate { - print("can't start \(targetedStartDate) earlier than \(roundStartDate)") - if targetedStartDate == minimumTargetedEndDate { - minimumTargetedEndDate = roundStartDate - } else { - minimumTargetedEndDate = min(roundStartDate, minimumTargetedEndDate) - } - return false - } - - let previousMatches = roundObject.precedentMatches(ofMatch: match) - if previousMatches.isEmpty { return true } - - let previousMatchSlots = slots.filter({ slot in - previousMatches.map { $0.id }.contains(slot.matchID) - }) - - if previousMatchSlots.isEmpty { - if previousMatches.filter({ $0.disabled == false }).allSatisfy({ $0.startDate != nil }) { - return true - } - return false - } - - if previousMatches.filter({ $0.disabled == false }).count > previousMatchSlots.count { - if previousMatches.filter({ $0.disabled == false }).anySatisfy({ $0.startDate != nil }) { - return true - } - return false - } - - var includeBreakTime = false - - if accountLoserBracketBreakTime && roundObject.isLoserBracket() { - includeBreakTime = true - } - - if accountUpperBracketBreakTime && roundObject.isLoserBracket() == false { - includeBreakTime = true - } - - let previousMatchIsInPreviousRotation = previousMatchSlots.allSatisfy({ $0.rotationIndex + rotationDifference(loserBracket: roundObject.isLoserBracket()) < rotationIndex }) - - guard let minimumPossibleEndDate = previousMatchSlots.map({ $0.estimatedEndDate(includeBreakTime: includeBreakTime) }).max() else { - return previousMatchIsInPreviousRotation - } - - if targetedStartDate >= minimumPossibleEndDate { - if rotationDifferenceIsImportant { - return previousMatchIsInPreviousRotation - } else { - return true - } - } else { - if targetedStartDate == minimumTargetedEndDate { - minimumTargetedEndDate = minimumPossibleEndDate - } else { - minimumTargetedEndDate = min(minimumPossibleEndDate, minimumTargetedEndDate) - } - return false - } - } - func getNextStartDate(fromPreviousRotationSlots slots: [TimeMatch], includeBreakTime: Bool) -> Date? { slots.map { $0.estimatedEndDate(includeBreakTime: includeBreakTime) }.min() } @@ -535,7 +468,7 @@ final class MatchScheduler : ModelObject, Storable { return canBePlayed }) { - print(first.roundObject!.roundTitle(), first.matchTitle(), courtIndex, rotationStartDate) +// print(first.roundObject!.roundTitle(), first.matchTitle(), courtIndex, rotationStartDate) if first.roundObject!.parent == nil { if let roundIndex = matchPerRound[first.roundObject!.id] { @@ -698,6 +631,72 @@ final class MatchScheduler : ModelObject, Storable { } return updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate) } + + func roundMatchCanBePlayed(_ match: Match, roundObject: Round, slots: [TimeMatch], rotationIndex: Int, targetedStartDate: Date, minimumTargetedEndDate: inout Date) -> Bool { +// print(roundObject.roundTitle(), match.matchTitle()) + + if let roundStartDate = roundObject.startDate, targetedStartDate < roundStartDate { + print("can't start \(targetedStartDate) earlier than \(roundStartDate)") + if targetedStartDate == minimumTargetedEndDate { + minimumTargetedEndDate = roundStartDate + } else { + minimumTargetedEndDate = min(roundStartDate, minimumTargetedEndDate) + } + return false + } + + let previousMatches = roundObject.precedentMatches(ofMatch: match) + if previousMatches.isEmpty { return true } + + let previousMatchSlots = slots.filter({ slot in + previousMatches.map { $0.id }.contains(slot.matchID) + }) + + if previousMatchSlots.isEmpty { + if previousMatches.filter({ $0.disabled == false }).allSatisfy({ $0.startDate != nil }) { + return true + } + return false + } + + if previousMatches.filter({ $0.disabled == false }).count > previousMatchSlots.count { + if previousMatches.filter({ $0.disabled == false }).anySatisfy({ $0.startDate != nil }) { + return true + } + return false + } + + var includeBreakTime = false + + if accountLoserBracketBreakTime && roundObject.isLoserBracket() { + includeBreakTime = true + } + + if accountUpperBracketBreakTime && roundObject.isLoserBracket() == false { + includeBreakTime = true + } + + let previousMatchIsInPreviousRotation = previousMatchSlots.allSatisfy({ $0.rotationIndex + rotationDifference(loserBracket: roundObject.isLoserBracket()) < rotationIndex }) + + guard let minimumPossibleEndDate = previousMatchSlots.map({ $0.estimatedEndDate(includeBreakTime: includeBreakTime) }).max() else { + return previousMatchIsInPreviousRotation + } + + if targetedStartDate >= minimumPossibleEndDate { + if rotationDifferenceIsImportant { + return previousMatchIsInPreviousRotation + } else { + return true + } + } else { + if targetedStartDate == minimumTargetedEndDate { + minimumTargetedEndDate = minimumPossibleEndDate + } else { + minimumTargetedEndDate = min(minimumPossibleEndDate, minimumTargetedEndDate) + } + return false + } + } } struct GroupStageTimeMatch { diff --git a/PadelClub/Data/MockData.swift b/PadelClubData/PadelClubData/Model/MockData.swift similarity index 100% rename from PadelClub/Data/MockData.swift rename to PadelClubData/PadelClubData/Model/MockData.swift diff --git a/PadelClubData/PadelClubData/Model/MonthData.swift b/PadelClubData/PadelClubData/Model/MonthData.swift new file mode 100644 index 0000000..0b65375 --- /dev/null +++ b/PadelClubData/PadelClubData/Model/MonthData.swift @@ -0,0 +1,70 @@ +// +// MonthData.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import Foundation +import SwiftUI +import LeStorage + +final class MonthData : ModelObject, Storable { + + static func resourceName() -> String { return "month-data" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + static func filterByStoreIdentifier() -> Bool { return false } + static var relationshipNames: [String] = [] + + 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 + var maleCount: Int? = nil + var femaleCount: Int? = nil + var anonymousCount: Int? = nil + var incompleteMode: Bool = false + + init(monthKey: String) { + self.monthKey = monthKey + self.creationDate = Date() + } + + fileprivate func _updateCreationDate() { + self.creationDate = Date() + } + +// required init(from decoder: Decoder) throws { +// let container = try decoder.container(keyedBy: CodingKeys.self) +// id = try container.decode(String.self, forKey: ._id) +// monthKey = try container.decode(String.self, forKey: ._monthKey) +// creationDate = try container.decode(Date.self, forKey: ._creationDate) +// maleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._maleUnrankedValue) +// femaleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._femaleUnrankedValue) +// maleCount = try container.decodeIfPresent(Int.self, forKey: ._maleCount) +// femaleCount = try container.decodeIfPresent(Int.self, forKey: ._femaleCount) +// anonymousCount = try container.decodeIfPresent(Int.self, forKey: ._anonymousCount) +// incompleteMode = try container.decodeIfPresent(Bool.self, forKey: ._incompleteMode) ?? false + +// } + + func total() -> Int { + return (maleCount ?? 0) + (femaleCount ?? 0) + } + + override func deleteDependencies() throws { + } + +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _monthKey = "monthKey" +// case _creationDate = "creationDate" +// case _maleUnrankedValue = "maleUnrankedValue" +// case _femaleUnrankedValue = "femaleUnrankedValue" +// case _maleCount = "maleCount" +// case _femaleCount = "femaleCount" +// case _anonymousCount = "anonymousCount" +// case _incompleteMode = "incompleteMode" +// } +} diff --git a/PadelClubData/PadelClubData/Model/PlayerRegistration.swift b/PadelClubData/PadelClubData/Model/PlayerRegistration.swift new file mode 100644 index 0000000..2ffc5d1 --- /dev/null +++ b/PadelClubData/PadelClubData/Model/PlayerRegistration.swift @@ -0,0 +1,363 @@ +// +// PlayerRegistration.swift +// Padel Tournament +// +// Created by razmig on 10/03/2024. +// + +import Foundation +import LeStorage + +final class PlayerRegistration: ModelObject, Storable { + static func resourceName() -> String { "player-registrations" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + static func filterByStoreIdentifier() -> Bool { return true } + static var relationshipNames: [String] = ["teamRegistration"] + + var id: String = Store.randomId() + var teamRegistration: String? + var firstName: String + var lastName: String + var licenceId: String? + var rank: Int? + var paymentType: PlayerPaymentType? + var sex: PlayerSexType? + + var tournamentPlayed: Int? + var points: Double? + var clubName: String? + var ligueName: String? + var assimilation: String? + + var phoneNumber: String? + var email: String? + var birthdate: String? + + var computedRank: Int = 0 + var source: PlayerDataSource? + + var hasArrived: Bool = false + + init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerDataSource? = nil, hasArrived: Bool = false) { + self.teamRegistration = teamRegistration + self.firstName = firstName + self.lastName = lastName + self.licenceId = licenceId + self.rank = rank + self.paymentType = paymentType + self.sex = sex + self.tournamentPlayed = tournamentPlayed + self.points = points + self.clubName = clubName + self.ligueName = ligueName + self.assimilation = assimilation + self.phoneNumber = phoneNumber + self.email = email + self.birthdate = birthdate + self.computedRank = computedRank + self.source = source + self.hasArrived = hasArrived + } + + var tournamentStore: TournamentStore { + if let store = self.store as? TournamentStore { + return store + } + fatalError("missing store for \(String(describing: type(of: self)))") + } + + var computedAge: Int? { + if let birthdate { + let components = birthdate.components(separatedBy: "/") + if components.count == 3 { + if let year = Calendar.current.dateComponents([.year], from: Date()).year, let age = components.last, let ageInt = Int(age) { + if age.count == 2 { //si l'année est sur 2 chiffres dans le fichier + if ageInt < 23 { + return year - 2000 - ageInt + } else { + return year - 2000 + 100 - ageInt + } + } else { //si l'année est représenté sur 4 chiffres + return year - ageInt + } + } + } + } + return nil + } + + func isPlaying() -> Bool { + team()?.isPlaying() == true + } + + func contains(_ searchField: String) -> Bool { + firstName.localizedCaseInsensitiveContains(searchField) || lastName.localizedCaseInsensitiveContains(searchField) + } + + func isSameAs(_ player: PlayerRegistration) -> Bool { + firstName.localizedCaseInsensitiveCompare(player.firstName) == .orderedSame && + lastName.localizedCaseInsensitiveCompare(player.lastName) == .orderedSame + } + + func tournament() -> Tournament? { + guard let tournament = team()?.tournament else { return nil } + return Store.main.findById(tournament) + } + + func team() -> TeamRegistration? { + guard let teamRegistration else { return nil } + return self.tournamentStore.teamRegistrations.findById(teamRegistration) + } + + func hasPaid() -> Bool { + paymentType != nil + } + + func isImported() -> Bool { + source == .beachPadel + } + + func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String { + if let rank, rank > 0 { + if rank != computedRank { + return computedRank.formatted() + " (" + rank.formatted() + ")" + } else { + return rank.formatted() + } + } else { + return "non classé" + (isMalePlayer() ? "" : "e") + } + } + + func getRank() -> Int { + computedRank + } + + func isMalePlayer() -> Bool { + sex == .male + } + + func setComputedRank(in tournament: Tournament) { + let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 70_000 + switch tournament.tournamentCategory { + case .men: + computedRank = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0) + default: + computedRank = currentRank + } + } + +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _teamRegistration = "teamRegistration" +// case _firstName = "firstName" +// case _lastName = "lastName" +// case _licenceId = "licenceId" +// case _rank = "rank" +// case _paymentType = "paymentType" +// case _sex = "sex" +// case _tournamentPlayed = "tournamentPlayed" +// case _points = "points" +// case _clubName = "clubName" +// case _ligueName = "ligueName" +// case _assimilation = "assimilation" +// case _birthdate = "birthdate" +// case _phoneNumber = "phoneNumber" +// case _email = "email" +// case _computedRank = "computedRank" +// case _source = "source" +// case _hasArrived = "hasArrived" +// +// } + +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// +// if let teamRegistration = teamRegistration { +// try container.encode(teamRegistration, forKey: ._teamRegistration) +// } else { +// try container.encodeNil(forKey: ._teamRegistration) +// } +// +// try container.encode(firstName, forKey: ._firstName) +// try container.encode(lastName, forKey: ._lastName) +// +// if let licenceId = licenceId { +// try container.encode(licenceId, forKey: ._licenceId) +// } else { +// try container.encodeNil(forKey: ._licenceId) +// } +// +// if let rank = rank { +// try container.encode(rank, forKey: ._rank) +// } else { +// try container.encodeNil(forKey: ._rank) +// } +// +// if let paymentType = paymentType { +// try container.encode(paymentType, forKey: ._paymentType) +// } else { +// try container.encodeNil(forKey: ._paymentType) +// } +// +// if let sex = sex { +// try container.encode(sex, forKey: ._sex) +// } else { +// try container.encodeNil(forKey: ._sex) +// } +// +// if let tournamentPlayed = tournamentPlayed { +// try container.encode(tournamentPlayed, forKey: ._tournamentPlayed) +// } else { +// try container.encodeNil(forKey: ._tournamentPlayed) +// } +// +// if let points = points { +// try container.encode(points, forKey: ._points) +// } else { +// try container.encodeNil(forKey: ._points) +// } +// +// if let clubName = clubName { +// try container.encode(clubName, forKey: ._clubName) +// } else { +// try container.encodeNil(forKey: ._clubName) +// } +// +// if let ligueName = ligueName { +// try container.encode(ligueName, forKey: ._ligueName) +// } else { +// try container.encodeNil(forKey: ._ligueName) +// } +// +// if let assimilation = assimilation { +// try container.encode(assimilation, forKey: ._assimilation) +// } else { +// try container.encodeNil(forKey: ._assimilation) +// } +// +// if let phoneNumber = phoneNumber { +// try container.encode(phoneNumber, forKey: ._phoneNumber) +// } else { +// try container.encodeNil(forKey: ._phoneNumber) +// } +// +// if let email = email { +// try container.encode(email, forKey: ._email) +// } else { +// try container.encodeNil(forKey: ._email) +// } +// +// if let birthdate = birthdate { +// try container.encode(birthdate, forKey: ._birthdate) +// } else { +// try container.encodeNil(forKey: ._birthdate) +// } +// +// try container.encode(computedRank, forKey: ._computedRank) +// +// if let source = source { +// try container.encode(source, forKey: ._source) +// } else { +// try container.encodeNil(forKey: ._source) +// } +// +// try container.encode(hasArrived, forKey: ._hasArrived) +// } + + enum PlayerDataSource: Int, Codable { + case frenchFederation = 0 + case beachPadel = 1 + } + + enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable { + init?(rawValue: Int?) { + guard let value = rawValue else { return nil } + self.init(rawValue: value) + } + + var id: Self { + self + } + + case female = 0 + case male = 1 + } + + enum PlayerPaymentType: Int, CaseIterable, Identifiable, Codable { + init?(rawValue: Int?) { + guard let value = rawValue else { return nil } + self.init(rawValue: value) + } + + var id: Self { + self + } + + case cash = 0 + case lydia = 1 + case gift = 2 + case check = 3 + case paylib = 4 + case bankTransfer = 5 + case clubHouse = 6 + case creditCard = 7 + case forfeit = 8 + + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + switch self { + case .check: + return "Chèque" + case .cash: + return "Cash" + case .lydia: + return "Lydia" + case .paylib: + return "Paylib" + case .bankTransfer: + return "Virement" + case .clubHouse: + return "Clubhouse" + case .creditCard: + return "CB" + case .forfeit: + return "Forfait" + case .gift: + return "Offert" + } + } + } + + static func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int { + switch playerRank { + case 0: return 0 + case womanMax: return manMax - womanMax + case manMax: return 0 + case 1...10: return 400 + case 11...30: return 1000 + case 31...60: return 2000 + case 61...100: return 3000 + case 101...200: return 8000 + case 201...500: return 12000 + default: + return 15000 + } + } + + func insertOnServer() { + self.tournamentStore.playerRegistrations.writeChangeAndInsertOnServer(instance: self) + } + +} + +extension PlayerRegistration: Hashable { + static func == (lhs: PlayerRegistration, rhs: PlayerRegistration) -> Bool { + lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} diff --git a/PadelClub/Data/Purchase.swift b/PadelClubData/PadelClubData/Model/Purchase.swift similarity index 100% rename from PadelClub/Data/Purchase.swift rename to PadelClubData/PadelClubData/Model/Purchase.swift diff --git a/PadelClub/Data/Round.swift b/PadelClubData/PadelClubData/Model/Round.swift similarity index 76% rename from PadelClub/Data/Round.swift rename to PadelClubData/PadelClubData/Model/Round.swift index 27eee27..f3d6192 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClubData/PadelClubData/Model/Round.swift @@ -9,7 +9,6 @@ import Foundation import LeStorage import SwiftUI -@Observable final class Round: ModelObject, Storable { static func resourceName() -> String { "rounds" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } @@ -53,15 +52,6 @@ final class Round: ModelObject, Storable { // MARK: - - var matchFormat: MatchFormat { - get { - format ?? .defaultFormatForMatchType(.bracket) - } - set { - format = newValue - } - } - func hasStarted() -> Bool { return playedMatches().anySatisfy({ $0.hasStarted() }) } @@ -427,46 +417,10 @@ defer { } - func correspondingLoserRoundTitle(_ displayStyle: DisplayStyle = .wide) -> String { -#if _DEBUG_TIME //DEBUGING TIME - let start = Date() - defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func correspondingLoserRoundTitle()", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) - } -#endif - let initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index) - let seedsAfterThisRound: [TeamRegistration] = self.tournamentStore.teamRegistrations.filter { - $0.bracketPosition != nil - && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex - } - -// let seedsAfterThisRound : [TeamRegistration] = Store.main.filter(isIncluded: { -// $0.tournament == tournament -// && $0.bracketPosition != nil -// && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex -// }) - let playedMatches = playedMatches() - let seedInterval = SeedInterval(first: playedMatches.count + seedsAfterThisRound.count + 1, last: playedMatches.count * 2 + seedsAfterThisRound.count) - return seedInterval.localizedLabel(displayStyle) - } - func hasNextRound() -> Bool { return nextRound()?.isRankDisabled() == false } - func pasteData() -> String { - var data: [String] = [] - data.append(self.roundTitle()) - - playedMatches().forEach { match in - data.append(match.matchTitle()) - data.append(match.team(.one)?.teamLabelRanked(displayRank: true, displayTeamName: true) ?? "-----") - data.append(match.team(.two)?.teamLabelRanked(displayRank: true, displayTeamName: true) ?? "-----") - } - - return data.joined(separator: "\n") - } func seedInterval(initialMode: Bool = false) -> SeedInterval? { #if _DEBUG_TIME //DEBUGING TIME @@ -505,17 +459,6 @@ defer { return nil } - func roundTitle(_ displayStyle: DisplayStyle = .wide, initialMode: Bool = false) -> String { - if parent != nil { - if let seedInterval = seedInterval(initialMode: initialMode) { - return seedInterval.localizedLabel(displayStyle) - } - print("Round pas trouvé", id, parent, index) - return "Match de classement" - } - return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle) - } - func updateTournamentState() { if let tournamentObject = tournamentObject(), index == 0, isUpperBracket(), hasEnded() { tournamentObject.endDate = Date() @@ -572,53 +515,20 @@ defer { } } - func buildLoserBracket() { - guard loserRounds().isEmpty else { return } - let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index) - guard currentRoundMatchCount > 1 else { return } - let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount) - - var loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat - if let parentRound { - loserBracketMatchFormat = tournamentObject()?.loserBracketSmartMatchFormat(parentRound.index) - } - - let rounds = (0.. Bool { - lhs.id == rhs.id - } - - - func selectionLabel(index: Int) -> String { - if let parentRound { - return "Tour #\(parentRound.loserRounds().count - index)" - } else { - return roundTitle(.short) - } - } - - func badgeValue() -> Int? { -#if _DEBUG_TIME //DEBUGING TIME - let start = Date() - defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func badgeValue round of: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) - } -#endif - - if let parentRound { - return parentRound.loserRounds(forRoundIndex: index).flatMap { $0.playedMatches() }.filter({ $0.isRunning() }).count - } else { - return playedMatches().filter({ $0.isRunning() }).count - } - } - - func badgeValueColor() -> Color? { - return nil - } - - func badgeImage() -> Badge? { -#if _DEBUG_TIME //DEBUGING TIME - let start = Date() - defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func badgeImage of round: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) - } -#endif - return hasEnded() ? .checkmark : nil - } -} diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClubData/PadelClubData/Model/TeamRegistration.swift similarity index 64% rename from PadelClub/Data/TeamRegistration.swift rename to PadelClubData/PadelClubData/Model/TeamRegistration.swift index c5db5d1..c5f3974 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClubData/PadelClubData/Model/TeamRegistration.swift @@ -9,7 +9,6 @@ import Foundation import LeStorage import SwiftUI -@Observable final class TeamRegistration: ModelObject, Storable { static func resourceName() -> String { "team-registrations" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } @@ -146,10 +145,6 @@ final class TeamRegistration: ModelObject, Storable { return confirmationDate != nil } - func getPhoneNumbers() -> [String] { - return players().compactMap { $0.phoneNumber }.filter({ $0.isMobileNumber() }) - } - func getMail() -> [String] { let mails = players().compactMap({ $0.email }) return mails @@ -191,11 +186,6 @@ final class TeamRegistration: ModelObject, Storable { tournamentObject()?.tournamentCategory ?? .men } - @objc - var canonicalName: String { - players().map { $0.canonicalName }.joined(separator: " ") - } - func hasMemberOfClub(_ codeClubOrClubName: String?) -> Bool { guard let codeClubOrClubName else { return true } return unsortedPlayers().anySatisfy({ @@ -207,14 +197,6 @@ final class TeamRegistration: ModelObject, Storable { self.setWeight(from: self.players(), inTournamentCategory: tournamentCategory) } - func teamLabel(_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false) -> String { - return players().map { $0.playerLabel(displayStyle) }.joined(separator: twoLines ? "\n" : " & ") - } - - func teamLabelRanked(displayRank: Bool, displayTeamName: Bool) -> String { - [displayTeamName ? name : nil, displayRank ? seedIndex() : nil, displayTeamName ? (name == nil ? teamLabel() : name) : teamLabel()].compactMap({ $0 }).joined(separator: " ") - } - func seedIndex() -> String? { guard let tournament = tournamentObject() else { return nil } guard let index = index(in: tournament.selectedSortedTeams()) else { return nil } @@ -237,13 +219,6 @@ final class TeamRegistration: ModelObject, Storable { return unsortedPlayers().anySatisfy({ $0.contains(searchField) }) || self.name?.localizedCaseInsensitiveContains(searchField) == true } - func containsExactlyPlayerLicenses(_ playerLicenses: [String?]) -> Bool { - let arrayOfIds : [String] = unsortedPlayers().compactMap({ $0.licenceId?.strippedLicense?.canonicalVersion }) - let ids : Set = Set(arrayOfIds.sorted()) - let searchedIds = Set(playerLicenses.compactMap({ $0?.strippedLicense?.canonicalVersion }).sorted()) - return ids.hashValue == searchedIds.hashValue - } - func includes(players: [PlayerRegistration]) -> Bool { let unsortedPlayers = unsortedPlayers() guard players.count == unsortedPlayers.count else { return false } @@ -276,25 +251,6 @@ final class TeamRegistration: ModelObject, Storable { return bracketPosition != nil } - func positionLabel() -> String? { - if groupStagePosition != nil { return "Poule" } - if let initialRound = initialRound() { - return initialRound.roundTitle() - } else { - return nil - } - } - - func initialRoundColor() -> Color? { - if walkOut { return Color.logoRed } - if groupStagePosition != nil { return Color.blue } - if let initialRound = initialRound(), let colorHex = RoundRule.colors[safe: initialRound.index] { - return Color(uiColor: .init(fromHex: colorHex)) - } else { - return nil - } - } - func resetGroupeStagePosition() { if let groupStage { let matches = self.tournamentStore.matches.filter({ $0.groupStage == groupStage }).map { $0.id } @@ -327,63 +283,10 @@ final class TeamRegistration: ModelObject, Storable { resetBracketPosition() } - func pasteData(_ exportFormat: ExportFormat = .rawText, _ index: Int = 0) -> String { - switch exportFormat { - case .rawText: - return [playersPasteData(exportFormat), formattedInscriptionDate(exportFormat), name].compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator()) - case .csv: - return [index.formatted(), playersPasteData(exportFormat), isWildCard() ? "WC" : weight.formatted()].joined(separator: exportFormat.separator()) - } - } - var computedRegistrationDate: Date { return registrationDate ?? .distantFuture } - func formattedInscriptionDate(_ exportFormat: ExportFormat = .rawText) -> String? { - switch exportFormat { - case .rawText: - if let registrationDate { - return "Inscrit le " + registrationDate.formatted(.dateTime.weekday().day().month().hour().minute()) - } else { - return nil - } - case .csv: - if let registrationDate { - return registrationDate.formatted(.dateTime.weekday().day().month().hour().minute()) - } else { - return nil - } - } - } - - func formattedSummonDate(_ exportFormat: ExportFormat = .rawText) -> String? { - - switch exportFormat { - case .rawText: - if let callDate { - return "Convoqué le " + callDate.formatted(.dateTime.weekday().day().month().hour().minute()) - } else { - return nil - } - case .csv: - if let callDate { - return callDate.formatted(.dateTime.weekday().day().month().hour().minute()) - } else { - return nil - } - } - } - - func playersPasteData(_ exportFormat: ExportFormat = .rawText) -> String { - switch exportFormat { - case .rawText: - return players().map { $0.pasteData(exportFormat) }.joined(separator: exportFormat.newLineSeparator()) - case .csv: - return players().map { [$0.pasteData(exportFormat), isWildCard() ? "WC" : $0.computedRank.formatted() ].joined(separator: exportFormat.separator()) }.joined(separator: exportFormat.separator()) - } - } - func updatePlayers(_ players: Set, inTournamentCategory tournamentCategory: TournamentCategory) { let previousPlayers = Set(unsortedPlayers()) let playersToRemove = previousPlayers.subtracting(players) @@ -455,6 +358,10 @@ final class TeamRegistration: ModelObject, Storable { } } + func unrankValue(for malePlayer: Bool) -> Int { + return tournamentObject()?.unrankValue(for: malePlayer) ?? 70_000 + } + func setWeight(from players: [PlayerRegistration], inTournamentCategory tournamentCategory: TournamentCategory) { let significantPlayerCount = significantPlayerCount() weight = (players.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+) @@ -477,10 +384,6 @@ final class TeamRegistration: ModelObject, Storable { return missing } - func unrankValue(for malePlayer: Bool) -> Int { - return tournamentObject()?.unrankValue(for: malePlayer) ?? 70_000 - } - func groupStageObject() -> GroupStage? { guard let groupStage else { return nil } return self.tournamentStore.groupStages.findById(groupStage) @@ -503,127 +406,127 @@ final class TeamRegistration: ModelObject, Storable { return Store.main.findById(tournament) } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _tournament = "tournament" - case _groupStage = "groupStage" - case _registrationDate = "registrationDate" - case _callDate = "callDate" - case _bracketPosition = "bracketPosition" - case _groupStagePosition = "groupStagePosition" - case _comment = "comment" - case _source = "source" - case _sourceValue = "sourceValue" - case _logo = "logo" - case _name = "name" - case _wildCardBracket = "wildCardBracket" - case _wildCardGroupStage = "wildCardGroupStage" - case _weight = "weight" - case _walkOut = "walkOut" - case _lockedWeight = "lockedWeight" - case _confirmationDate = "confirmationDate" - case _qualified = "qualified" - case _finalRanking = "finalRanking" - case _pointsEarned = "pointsEarned" - } +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _tournament = "tournament" +// case _groupStage = "groupStage" +// case _registrationDate = "registrationDate" +// case _callDate = "callDate" +// case _bracketPosition = "bracketPosition" +// case _groupStagePosition = "groupStagePosition" +// case _comment = "comment" +// case _source = "source" +// case _sourceValue = "sourceValue" +// case _logo = "logo" +// case _name = "name" +// case _wildCardBracket = "wildCardBracket" +// case _wildCardGroupStage = "wildCardGroupStage" +// case _weight = "weight" +// case _walkOut = "walkOut" +// case _lockedWeight = "lockedWeight" +// case _confirmationDate = "confirmationDate" +// case _qualified = "qualified" +// case _finalRanking = "finalRanking" +// case _pointsEarned = "pointsEarned" +// } - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(tournament, forKey: ._tournament) - - if let groupStage = groupStage { - try container.encode(groupStage, forKey: ._groupStage) - } else { - try container.encodeNil(forKey: ._groupStage) - } - - if let registrationDate = registrationDate { - try container.encode(registrationDate, forKey: ._registrationDate) - } else { - try container.encodeNil(forKey: ._registrationDate) - } - - if let callDate = callDate { - try container.encode(callDate, forKey: ._callDate) - } else { - try container.encodeNil(forKey: ._callDate) - } - - if let bracketPosition = bracketPosition { - try container.encode(bracketPosition, forKey: ._bracketPosition) - } else { - try container.encodeNil(forKey: ._bracketPosition) - } - - if let groupStagePosition = groupStagePosition { - try container.encode(groupStagePosition, forKey: ._groupStagePosition) - } else { - try container.encodeNil(forKey: ._groupStagePosition) - } - - if let comment = comment { - try container.encode(comment, forKey: ._comment) - } else { - try container.encodeNil(forKey: ._comment) - } - - if let source = source { - try container.encode(source, forKey: ._source) - } else { - try container.encodeNil(forKey: ._source) - } - - if let sourceValue = sourceValue { - try container.encode(sourceValue, forKey: ._sourceValue) - } else { - try container.encodeNil(forKey: ._sourceValue) - } - - if let logo = logo { - try container.encode(logo, forKey: ._logo) - } else { - try container.encodeNil(forKey: ._logo) - } - - if let name = name { - try container.encode(name, forKey: ._name) - } else { - try container.encodeNil(forKey: ._name) - } - - try container.encode(walkOut, forKey: ._walkOut) - try container.encode(wildCardBracket, forKey: ._wildCardBracket) - try container.encode(wildCardGroupStage, forKey: ._wildCardGroupStage) - try container.encode(weight, forKey: ._weight) - - if let lockedWeight = lockedWeight { - try container.encode(lockedWeight, forKey: ._lockedWeight) - } else { - try container.encodeNil(forKey: ._lockedWeight) - } - - if let confirmationDate = confirmationDate { - try container.encode(confirmationDate, forKey: ._confirmationDate) - } else { - try container.encodeNil(forKey: ._confirmationDate) - } - - try container.encode(qualified, forKey: ._qualified) - - if let finalRanking { - try container.encode(finalRanking, forKey: ._finalRanking) - } else { - try container.encodeNil(forKey: ._finalRanking) - } - - if let pointsEarned { - try container.encode(pointsEarned, forKey: ._pointsEarned) - } else { - try container.encodeNil(forKey: ._pointsEarned) - } - } +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// try container.encode(tournament, forKey: ._tournament) +// +// if let groupStage = groupStage { +// try container.encode(groupStage, forKey: ._groupStage) +// } else { +// try container.encodeNil(forKey: ._groupStage) +// } +// +// if let registrationDate = registrationDate { +// try container.encode(registrationDate, forKey: ._registrationDate) +// } else { +// try container.encodeNil(forKey: ._registrationDate) +// } +// +// if let callDate = callDate { +// try container.encode(callDate, forKey: ._callDate) +// } else { +// try container.encodeNil(forKey: ._callDate) +// } +// +// if let bracketPosition = bracketPosition { +// try container.encode(bracketPosition, forKey: ._bracketPosition) +// } else { +// try container.encodeNil(forKey: ._bracketPosition) +// } +// +// if let groupStagePosition = groupStagePosition { +// try container.encode(groupStagePosition, forKey: ._groupStagePosition) +// } else { +// try container.encodeNil(forKey: ._groupStagePosition) +// } +// +// if let comment = comment { +// try container.encode(comment, forKey: ._comment) +// } else { +// try container.encodeNil(forKey: ._comment) +// } +// +// if let source = source { +// try container.encode(source, forKey: ._source) +// } else { +// try container.encodeNil(forKey: ._source) +// } +// +// if let sourceValue = sourceValue { +// try container.encode(sourceValue, forKey: ._sourceValue) +// } else { +// try container.encodeNil(forKey: ._sourceValue) +// } +// +// if let logo = logo { +// try container.encode(logo, forKey: ._logo) +// } else { +// try container.encodeNil(forKey: ._logo) +// } +// +// if let name = name { +// try container.encode(name, forKey: ._name) +// } else { +// try container.encodeNil(forKey: ._name) +// } +// +// try container.encode(walkOut, forKey: ._walkOut) +// try container.encode(wildCardBracket, forKey: ._wildCardBracket) +// try container.encode(wildCardGroupStage, forKey: ._wildCardGroupStage) +// try container.encode(weight, forKey: ._weight) +// +// if let lockedWeight = lockedWeight { +// try container.encode(lockedWeight, forKey: ._lockedWeight) +// } else { +// try container.encodeNil(forKey: ._lockedWeight) +// } +// +// if let confirmationDate = confirmationDate { +// try container.encode(confirmationDate, forKey: ._confirmationDate) +// } else { +// try container.encodeNil(forKey: ._confirmationDate) +// } +// +// try container.encode(qualified, forKey: ._qualified) +// +// if let finalRanking { +// try container.encode(finalRanking, forKey: ._finalRanking) +// } else { +// try container.encodeNil(forKey: ._finalRanking) +// } +// +// if let pointsEarned { +// try container.encode(pointsEarned, forKey: ._pointsEarned) +// } else { +// try container.encodeNil(forKey: ._pointsEarned) +// } +// } func insertOnServer() { self.tournamentStore.teamRegistrations.writeChangeAndInsertOnServer(instance: self) diff --git a/PadelClub/Data/TeamScore.swift b/PadelClubData/PadelClubData/Model/TeamScore.swift similarity index 58% rename from PadelClub/Data/TeamScore.swift rename to PadelClubData/PadelClubData/Model/TeamScore.swift index 8948d2d..6dfaa74 100644 --- a/PadelClub/Data/TeamScore.swift +++ b/PadelClubData/PadelClubData/Model/TeamScore.swift @@ -8,7 +8,6 @@ import Foundation import LeStorage -@Observable final class TeamScore: ModelObject, Storable { static func resourceName() -> String { "team-scores" } @@ -70,48 +69,48 @@ final class TeamScore: ModelObject, Storable { return walkOut != nil } - enum CodingKeys: String, CodingKey { - case _id = "id" - case _match = "match" - case _teamRegistration = "teamRegistration" - //case _playerRegistrations = "playerRegistrations" - case _score = "score" - case _walkOut = "walkOut" - case _luckyLoser = "luckyLoser" - } +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _match = "match" +// case _teamRegistration = "teamRegistration" +// //case _playerRegistrations = "playerRegistrations" +// case _score = "score" +// case _walkOut = "walkOut" +// case _luckyLoser = "luckyLoser" +// } - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id, forKey: ._id) - try container.encode(match, forKey: ._match) - - if let teamRegistration = teamRegistration { - try container.encode(teamRegistration, forKey: ._teamRegistration) - } else { - try container.encodeNil(forKey: ._teamRegistration) - } - - //try container.encode(playerRegistrations, forKey: ._playerRegistrations) - - if let score = score { - try container.encode(score, forKey: ._score) - } else { - try container.encodeNil(forKey: ._score) - } - - if let walkOut = walkOut { - try container.encode(walkOut, forKey: ._walkOut) - } else { - try container.encodeNil(forKey: ._walkOut) - } - - if let luckyLoser = luckyLoser { - try container.encode(luckyLoser, forKey: ._luckyLoser) - } else { - try container.encodeNil(forKey: ._luckyLoser) - } - } +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// try container.encode(match, forKey: ._match) +// +// if let teamRegistration = teamRegistration { +// try container.encode(teamRegistration, forKey: ._teamRegistration) +// } else { +// try container.encodeNil(forKey: ._teamRegistration) +// } +// +// //try container.encode(playerRegistrations, forKey: ._playerRegistrations) +// +// if let score = score { +// try container.encode(score, forKey: ._score) +// } else { +// try container.encodeNil(forKey: ._score) +// } +// +// if let walkOut = walkOut { +// try container.encode(walkOut, forKey: ._walkOut) +// } else { +// try container.encodeNil(forKey: ._walkOut) +// } +// +// if let luckyLoser = luckyLoser { +// try container.encode(luckyLoser, forKey: ._luckyLoser) +// } else { +// try container.encodeNil(forKey: ._luckyLoser) +// } +// } func insertOnServer() { self.tournamentStore.teamScores.writeChangeAndInsertOnServer(instance: self) diff --git a/PadelClub/Data/Tournament.swift b/PadelClubData/PadelClubData/Model/Tournament.swift similarity index 74% rename from PadelClub/Data/Tournament.swift rename to PadelClubData/PadelClubData/Model/Tournament.swift index 5ab4fd8..2d23f6f 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClubData/PadelClubData/Model/Tournament.swift @@ -9,7 +9,6 @@ import Foundation import LeStorage import SwiftUI -@Observable final class Tournament : ModelObject, Storable { static func resourceName() -> String { "tournaments" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } @@ -223,89 +222,93 @@ final class Tournament : ModelObject, Storable { } func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: ._id) - if let event { - try container.encode(event, forKey: ._event) - } else { - try container.encodeNil(forKey: ._event) - } - if let name { - try container.encode(name, forKey: ._name) - } else { - try container.encodeNil(forKey: ._name) - } - try container.encode(startDate, forKey: ._startDate) - if let endDate { - try container.encode(endDate, forKey: ._endDate) - } else { - try container.encodeNil(forKey: ._endDate) - } - - try container.encode(creationDate, forKey: ._creationDate) - try container.encode(isPrivate, forKey: ._isPrivate) - if let groupStageFormat { - try container.encode(groupStageFormat, forKey: ._groupStageFormat) - } else { - try container.encodeNil(forKey: ._groupStageFormat) - } - if let roundFormat { - try container.encode(roundFormat, forKey: ._roundFormat) - } else { - try container.encodeNil(forKey: ._roundFormat) - } - if let loserRoundFormat { - try container.encode(loserRoundFormat, forKey: ._loserRoundFormat) - } else { - try container.encodeNil(forKey: ._loserRoundFormat) - } - try container.encode(groupStageSortMode, forKey: ._groupStageSortMode) - try container.encode(groupStageCount, forKey: ._groupStageCount) - if let rankSourceDate { - try container.encode(rankSourceDate, forKey: ._rankSourceDate) - } else { - try container.encodeNil(forKey: ._rankSourceDate) - } - try container.encode(dayDuration, forKey: ._dayDuration) - try container.encode(teamCount, forKey: ._teamCount) - try container.encode(teamSorting, forKey: ._teamSorting) - try container.encode(federalCategory, forKey: ._federalCategory) - try container.encode(federalLevelCategory, forKey: ._federalLevelCategory) - try container.encode(federalAgeCategory, forKey: ._federalAgeCategory) - if let closedRegistrationDate { - try container.encode(closedRegistrationDate, forKey: ._closedRegistrationDate) - } else { - try container.encodeNil(forKey: ._closedRegistrationDate) - } - - try container.encode(groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified) - try container.encode(courtCount, forKey: ._courtCount) - try container.encode(prioritizeClubMembers, forKey: ._prioritizeClubMembers) - try container.encode(qualifiedPerGroupStage, forKey: ._qualifiedPerGroupStage) - try container.encode(teamsPerGroupStage, forKey: ._teamsPerGroupStage) - if let entryFee { - try container.encode(entryFee, forKey: ._entryFee) - } else { - try container.encodeNil(forKey: ._entryFee) - } - - try self._encodePayment(container: &container) - try container.encode(additionalEstimationDuration, forKey: ._additionalEstimationDuration) - try container.encode(isDeleted, forKey: ._isDeleted) - try self._encodeIsCanceled(container: &container) - try container.encode(publishTeams, forKey: ._publishTeams) - try container.encode(publishSummons, forKey: ._publishSummons) - try container.encode(publishBrackets, forKey: ._publishBrackets) - try container.encode(publishGroupStages, forKey: ._publishGroupStages) - try container.encode(shouldVerifyBracket, forKey: ._shouldVerifyBracket) - try container.encode(shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage) - try container.encode(hideTeamsWeight, forKey: ._hideTeamsWeight) - try container.encode(publishTournament, forKey: ._publishTournament) - try container.encode(hidePointsEarned, forKey: ._hidePointsEarned) - try container.encode(publishRankings, forKey: ._publishRankings) } +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// if let event { +// try container.encode(event, forKey: ._event) +// } else { +// try container.encodeNil(forKey: ._event) +// } +// if let name { +// try container.encode(name, forKey: ._name) +// } else { +// try container.encodeNil(forKey: ._name) +// } +// try container.encode(startDate, forKey: ._startDate) +// if let endDate { +// try container.encode(endDate, forKey: ._endDate) +// } else { +// try container.encodeNil(forKey: ._endDate) +// } +// +// try container.encode(creationDate, forKey: ._creationDate) +// try container.encode(isPrivate, forKey: ._isPrivate) +// if let groupStageFormat { +// try container.encode(groupStageFormat, forKey: ._groupStageFormat) +// } else { +// try container.encodeNil(forKey: ._groupStageFormat) +// } +// if let roundFormat { +// try container.encode(roundFormat, forKey: ._roundFormat) +// } else { +// try container.encodeNil(forKey: ._roundFormat) +// } +// if let loserRoundFormat { +// try container.encode(loserRoundFormat, forKey: ._loserRoundFormat) +// } else { +// try container.encodeNil(forKey: ._loserRoundFormat) +// } +// try container.encode(groupStageSortMode, forKey: ._groupStageSortMode) +// try container.encode(groupStageCount, forKey: ._groupStageCount) +// if let rankSourceDate { +// try container.encode(rankSourceDate, forKey: ._rankSourceDate) +// } else { +// try container.encodeNil(forKey: ._rankSourceDate) +// } +// try container.encode(dayDuration, forKey: ._dayDuration) +// try container.encode(teamCount, forKey: ._teamCount) +// try container.encode(teamSorting, forKey: ._teamSorting) +// try container.encode(federalCategory, forKey: ._federalCategory) +// try container.encode(federalLevelCategory, forKey: ._federalLevelCategory) +// try container.encode(federalAgeCategory, forKey: ._federalAgeCategory) +// if let closedRegistrationDate { +// try container.encode(closedRegistrationDate, forKey: ._closedRegistrationDate) +// } else { +// try container.encodeNil(forKey: ._closedRegistrationDate) +// } +// +// try container.encode(groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified) +// try container.encode(courtCount, forKey: ._courtCount) +// try container.encode(prioritizeClubMembers, forKey: ._prioritizeClubMembers) +// try container.encode(qualifiedPerGroupStage, forKey: ._qualifiedPerGroupStage) +// try container.encode(teamsPerGroupStage, forKey: ._teamsPerGroupStage) +// if let entryFee { +// try container.encode(entryFee, forKey: ._entryFee) +// } else { +// try container.encodeNil(forKey: ._entryFee) +// } +// +// try self._encodePayment(container: &container) +// try container.encode(additionalEstimationDuration, forKey: ._additionalEstimationDuration) +// try container.encode(isDeleted, forKey: ._isDeleted) +// try self._encodeIsCanceled(container: &container) +// try container.encode(publishTeams, forKey: ._publishTeams) +// try container.encode(publishSummons, forKey: ._publishSummons) +// try container.encode(publishBrackets, forKey: ._publishBrackets) +// try container.encode(publishGroupStages, forKey: ._publishGroupStages) +// try container.encode(shouldVerifyBracket, forKey: ._shouldVerifyBracket) +// try container.encode(shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage) +// try container.encode(hideTeamsWeight, forKey: ._hideTeamsWeight) +// try container.encode(publishTournament, forKey: ._publishTournament) +// try container.encode(hidePointsEarned, forKey: ._hidePointsEarned) +// try container.encode(publishRankings, forKey: ._publishRankings) +// } + fileprivate func _encodePayment(container: inout KeyedEncodingContainer) throws { guard let payment else { @@ -493,20 +496,6 @@ final class Tournament : ModelObject, Storable { } } - func shareURL(_ pageLink: PageLink = .matches) -> URL? { - if pageLink == .clubBroadcast { - let club = club() - print("club", club) - print("club broadcast code", club?.broadcastCode) - if let club, let broadcastCode = club.broadcastCode { - return URLs.main.url.appending(path: "c/\(broadcastCode)") - } else { - return nil - } - } - return URLs.main.url.appending(path: "tournament/\(id)").appending(path: pageLink.path) - } - func courtUsed() -> [Int] { #if DEBUG //DEBUGING TIME let start = Date() @@ -529,21 +518,6 @@ defer { return Store.main.findById(event) } - func pasteDataForImporting(_ exportFormat: ExportFormat = .rawText) -> String { - let selectedSortedTeams = selectedSortedTeams() - switch exportFormat { - case .rawText: - return (selectedSortedTeams.compactMap { $0.pasteData(exportFormat) } + ["Liste d'attente"] + waitingListTeams(in: selectedSortedTeams, includingWalkOuts: true).compactMap { $0.pasteData(exportFormat) }).joined(separator: exportFormat.newLineSeparator(2)) - case .csv: - let headers = ["N°", "Nom Prénom", "rang", "Nom Prénom", "rang", "poids"].joined(separator: exportFormat.separator()) - var teamPaste = [headers] - for (index, team) in selectedSortedTeams.enumerated() { - teamPaste.append(team.pasteData(exportFormat, index + 1)) - } - return teamPaste.joined(separator: exportFormat.newLineSeparator()) - } - } - func club() -> Club? { return eventObject()?.clubObject() } @@ -952,10 +926,6 @@ defer { return duplicates } - func homonyms(in players: [PlayerRegistration]) -> [PlayerRegistration] { - players.filter({ $0.hasHomonym() }) - } - func unsortedPlayers() -> [PlayerRegistration] { return Array(self.tournamentStore.playerRegistrations) } @@ -976,19 +946,6 @@ defer { return self.tournamentStore.playerRegistrations.sorted(by: \.computedRank) } - func unrankValue(for malePlayer: Bool) -> Int? { - switch tournamentCategory { - case .unlisted: - return nil - case .men: - return maleUnrankedValue - case .women: - return femaleUnrankedValue - case .mix: - return malePlayer ? maleUnrankedValue : femaleUnrankedValue - } - } - //todo var clubName: String? { return self.eventObject()?.clubObject()?.name @@ -1034,55 +991,11 @@ defer { } } - func playersWithoutValidLicense(in players: [PlayerRegistration]) -> [PlayerRegistration] { - let licenseYearValidity = self.licenseYearValidity() - return players.filter({ - ($0.isImported() && $0.isValidLicenseNumber(year: licenseYearValidity) == false) || ($0.isImported() == false && ($0.licenceId == nil || $0.formattedLicense().isLicenseNumber == false || $0.licenceId?.isEmpty == true)) - }) - } - func getStartDate(ofSeedIndex seedIndex: Int?) -> Date? { guard let seedIndex else { return nil } return selectedSortedTeams()[safe: seedIndex]?.callDate } - func importTeams(_ teams: [FileImportManager.TeamHolder]) { - var teamsToImport = [TeamRegistration]() - let players = players().filter { $0.licenceId != nil } - teams.forEach { team in - if let previousTeam = team.previousTeam { - previousTeam.updatePlayers(team.players, inTournamentCategory: team.tournamentCategory) - teamsToImport.append(previousTeam) - } else { - var registrationDate = team.registrationDate - if let previousPlayer = players.first(where: { player in - let ids = team.players.compactMap({ $0.licenceId }) - return ids.contains(player.licenceId!) - }), let previousTeamRegistrationDate = previousPlayer.team()?.registrationDate { - registrationDate = previousTeamRegistrationDate - } - let newTeam = addTeam(team.players, registrationDate: registrationDate, name: team.name) - teamsToImport.append(newTeam) - } - } - - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teamsToImport) - } catch { - Logger.error(error) - } - do { - try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: teams.flatMap { $0.players }) - } catch { - Logger.error(error) - } - - - if state() == .build && groupStageCount > 0 && groupStageTeams().isEmpty { - setGroupStage(randomize: groupStageSortMode == .random) - } - } - func maximumCourtsPerGroupSage() -> Int { if teamsPerGroupStage > 1 { return min(teamsPerGroupStage / 2, courtCount) @@ -1091,22 +1004,6 @@ defer { } } - func registrationIssues() -> Int { - let players : [PlayerRegistration] = unsortedPlayers() - let selectedTeams : [TeamRegistration] = selectedSortedTeams() - let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) } - let duplicates : [PlayerRegistration] = duplicates(in: players) - let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == nil }) - let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players) - let playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players) - let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) - let waitingList : [TeamRegistration] = waitingListTeams(in: selectedTeams, includingWalkOuts: true) - let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil }) - let waitingListInGroupStage = waitingList.filter({ $0.groupStage != nil }) - - return callDateIssue.count + duplicates.count + problematicPlayers.count + inadequatePlayers.count + playersWithoutValidLicense.count + playersMissing.count + waitingListInBracket.count + waitingListInGroupStage.count - } - func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration) -> Bool { guard let summonDate = team.callDate else { return true } guard let expectedSummonDate = team.expectedSummonDate() else { return true } @@ -1179,107 +1076,6 @@ defer { } - func finalRanking() async -> [Int: [String]] { - var teams: [Int: [String]] = [:] - var ids: Set = Set() - let rounds = rounds() - let final = rounds.last?.playedMatches().last - if let winner = final?.winningTeamId { - teams[1] = [winner] - ids.insert(winner) - } - if let finalist = final?.losingTeamId { - teams[2] = [finalist] - ids.insert(finalist) - } - - let others: [Round] = rounds.flatMap { round in - let losers = round.losers() - let minimumFinalPosition = round.seedInterval()?.last ?? teamCount - if teams[minimumFinalPosition] == nil { - teams[minimumFinalPosition] = losers.map { $0.id } - } else { - teams[minimumFinalPosition]?.append(contentsOf: losers.map { $0.id }) - } - - print("round", round.roundTitle()) - let rounds = round.loserRoundsAndChildren().filter { $0.isRankDisabled() == false && $0.hasNextRound() == false } - print(rounds.count, rounds.map { $0.roundTitle() }) - return rounds - }.compactMap({ $0 }) - - others.forEach { round in - print("round", round.roundTitle()) - if let interval = round.seedInterval() { - print("interval", interval.localizedLabel()) - let playedMatches = round.playedMatches().filter { $0.disabled == false || $0.isReady() } - print("playedMatches", playedMatches.count) - let winners = playedMatches.compactMap({ $0.winningTeamId }).filter({ ids.contains($0) == false }) - print("winners", winners.count) - let losers = playedMatches.compactMap({ $0.losingTeamId }).filter({ ids.contains($0) == false }) - print("losers", losers.count) - if winners.isEmpty { - let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false }) - if disabledIds.isEmpty == false { - _removeStrings(from: &teams, stringsToRemove: disabledIds) - teams[interval.last] = disabledIds - let teamNames : [String] = disabledIds.compactMap { - let t : TeamRegistration? = Store.main.findById($0) - return t - }.map { $0.canonicalName } - print("winners.isEmpty", "\(interval.last) : ", teamNames) - disabledIds.forEach { - ids.insert($0) - } - } - } else { - if winners.isEmpty == false { - _removeStrings(from: &teams, stringsToRemove: winners) - teams[interval.first + winners.count - 1] = winners - let teamNames : [String] = winners.compactMap { - let t: TeamRegistration? = Store.main.findById($0) - return t - }.map { $0.canonicalName } - print("winners", "\(interval.last + winners.count - 1) : ", teamNames) - winners.forEach { ids.insert($0) } - } - - if losers.isEmpty == false { - _removeStrings(from: &teams, stringsToRemove: losers) - teams[interval.last] = losers - let loserTeamNames : [String] = losers.compactMap { - let t: TeamRegistration? = Store.main.findById($0) - return t - }.map { $0.canonicalName } - print("losers", "\(interval.last) : ", loserTeamNames) - losers.forEach { ids.insert($0) } - } - } - } - } - - let groupStages = groupStages() - let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified - - groupStages.forEach { groupStage in - let groupStageTeams = groupStage.teams(true) - for (index, team) in groupStageTeams.enumerated() { - if team.qualified == false { - let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0) - - let _index = baseRank + groupStageWidth + 1 - if let existingTeams = teams[_index] { - teams[_index] = existingTeams + [team.id] - } else { - teams[_index] = [team.id] - } - } - } - } - - return teams - } - func lockRegistration() { closedRegistrationDate = Date() let count = selectedSortedTeams().count @@ -1310,7 +1106,6 @@ defer { } } - func updateWeights() { let teams = self.unsortedTeams() teams.forEach { team in @@ -1330,40 +1125,6 @@ defer { } } - func updateRank(to newDate: Date?) async throws { - guard let newDate else { return } - rankSourceDate = newDate - - 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 formatted: String = URL.importDateFormatter.string(from: newDate) - let monthData: MonthData = MonthData(monthKey: formatted) - monthData.maleUnrankedValue = lastRankMan - monthData.femaleUnrankedValue = lastRankWoman - do { - try DataStore.shared.monthData.addOrUpdate(instance: monthData) - } catch { - Logger.error(error) - } - } - } - - let lastRankMan = currentMonthData()?.maleUnrankedValue - let lastRankWoman = currentMonthData()?.femaleUnrankedValue - let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate } - let sources = dataURLs.map { CSVParser(url: $0) } - - try await unsortedPlayers().concurrentForEach { player in - try await player.updateRank(from: sources, lastRank: (player.sex == .female ? lastRankWoman : lastRankMan) ?? 0) - } - } - - func missingUnrankedValue() -> Bool { - return maleUnrankedValue == nil || femaleUnrankedValue == nil - } - func findTeam(_ players: [PlayerRegistration]) -> TeamRegistration? { return unsortedTeams().first(where: { $0.includes(players: players) }) } @@ -1455,19 +1216,6 @@ defer { //return qualifiedTeams().count == qualifiedFromGroupStage() + groupStageAdditionalQualified } - fileprivate func _paymentMethodMessage() -> String? { - return DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods - } - - var entryFeeMessage: String { - if let entryFee { - let message: String = "Inscription: \(entryFee.formatted(.currency(code: "EUR"))) par joueur." - return [message, self._paymentMethodMessage()].compactMap { $0 }.joined(separator: "\n") - } else { - return "Inscription: gratuite." - } - } - func umpireMail() -> [String]? { return [DataStore.shared.user.email] } @@ -1525,71 +1273,6 @@ defer { return TournamentStatus(label: label, completion: completionLabel) } - func bracketStatus() async -> (status: String, description: String?, cut: TeamRegistration.TeamRange?) { - let availableSeeds = availableSeeds() - var description: String? = nil - if availableSeeds.isEmpty == false { - description = "placer \(availableSeeds.count) équipe\(availableSeeds.count.pluralSuffix)" - } - if description == nil { - let availableQualifiedTeams = availableQualifiedTeams() - if availableQualifiedTeams.isEmpty == false { - description = "placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix - } - } - - var cut: TeamRegistration.TeamRange? = nil - if description == nil && isAnimation() == false { - cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last) - } - - if let round = getActiveRound() { - return ([round.roundTitle(.short), round.roundStatus()].joined(separator: " ").lowercased(), description, cut) - } else { - return ("", description, nil) - } - } - - func groupStageStatus() async -> (status: String, cut: TeamRegistration.TeamRange?) { - let groupStageTeams = groupStageTeams() - let groupStageTeamsCount = groupStageTeams.count - if groupStageTeamsCount == 0 || groupStageTeamsCount != groupStageSpots() { - return ("à compléter", nil) - } - - let cut : TeamRegistration.TeamRange? = isAnimation() ? nil : TeamRegistration.TeamRange(groupStageTeams.first, groupStageTeams.last) - - let runningGroupStages = groupStages().filter({ $0.isRunning() }) - if groupStagesAreOver() { return ("terminées", cut) } - if runningGroupStages.isEmpty { - - let ongoingGroupStages = runningGroupStages.filter({ $0.hasStarted() && $0.hasEnded() == false }) - if ongoingGroupStages.isEmpty == false { - return ("Poule" + ongoingGroupStages.count.pluralSuffix + " " + ongoingGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours", cut) - } - return (groupStages().count.formatted() + " poule" + groupStages().count.pluralSuffix, cut) - } else { - return ("Poule" + runningGroupStages.count.pluralSuffix + " " + runningGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours", cut) - } - } - - func settingsDescriptionLocalizedLabel() -> String { - [courtCount.formatted() + " terrain\(courtCount.pluralSuffix)", entryFeeMessage].joined(separator: ", ") - } - - func structureDescriptionLocalizedLabel() -> String { - let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil - return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ") - } - - func deleteAndBuildEverything() { - resetBracketPosition() - deleteStructure() - deleteGroupStages() - buildGroupStages() - buildBracket() - } - func buildGroupStages() { guard groupStages().isEmpty else { return @@ -1615,42 +1298,6 @@ defer { return bracketTeamCount } - func buildBracket() { - guard rounds().isEmpty else { return } - let roundCount = RoundRule.numberOfRounds(forTeams: bracketTeamCount()) - - let rounds = (0.. Match? { guard let bracketPosition else { return nil } let matchIndex = bracketPosition / 2 @@ -1911,20 +1558,6 @@ defer { return self._matchSchedulers().first } - func currentMonthData() -> MonthData? { - guard let rankSourceDate else { return nil } - let dateString = URL.importDateFormatter.string(from: rankSourceDate) - return DataStore.shared.monthData.first(where: { $0.monthKey == dateString }) - } - - var maleUnrankedValue: Int? { - return currentMonthData()?.maleUnrankedValue - } - - var femaleUnrankedValue: Int? { - return currentMonthData()?.femaleUnrankedValue - } - func courtNameIfAvailable(atIndex courtIndex: Int) -> String? { return club()?.customizedCourts.first(where: { $0.index == courtIndex })?.name } @@ -1941,6 +1574,33 @@ defer { return finals?.playedMatches().first?.winner() } + func currentMonthData() -> MonthData? { + guard let rankSourceDate else { return nil } + let dateString = URL.importDateFormatter.string(from: rankSourceDate) + return DataStore.shared.monthData.first(where: { $0.monthKey == dateString }) + } + + var maleUnrankedValue: Int? { + return currentMonthData()?.maleUnrankedValue + } + + var femaleUnrankedValue: Int? { + return currentMonthData()?.femaleUnrankedValue + } + + func unrankValue(for malePlayer: Bool) -> Int? { + switch tournamentCategory { + case .unlisted: + return nil + case .men: + return maleUnrankedValue + case .women: + return femaleUnrankedValue + case .mix: + return malePlayer ? maleUnrankedValue : femaleUnrankedValue + } + } + func getGroupStageChunkValue() -> Int { if groupStageCount > 0 && teamsPerGroupStage >= 2 { let result = courtCount / (teamsPerGroupStage / 2) @@ -2099,24 +1759,6 @@ extension Tournament: Hashable { } } -extension Tournament: FederalTournamentHolder { - var holderId: String { id } - - func clubLabel() -> String { - locationLabel() - } - - func subtitleLabel() -> String { - subtitle() - } - - var tournaments: [any TournamentBuildHolder] { - [ - self - ] - } -} - extension Tournament: TournamentBuildHolder { func buildHolderTitle() -> String { tournamentTitle(.short) @@ -2134,26 +1776,3 @@ extension Tournament: TournamentBuildHolder { federalTournamentAge } } - -extension Tournament { - static func newEmptyInstance() -> Tournament { - let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource - var _mostRecentDateAvailable: Date? { - guard let lastDataSource else { return nil } - return URL.importDateFormatter.date(from: lastDataSource) - } - - let rankSourceDate = _mostRecentDateAvailable - let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil && $0.isDeleted == false } - let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments) - let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments) - let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments) -//creator: DataStore.shared.user?.id - return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge) - } - - static func fake() -> Tournament { - return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil) - } - -} diff --git a/PadelClubData/PadelClubData/Model/User.swift b/PadelClubData/PadelClubData/Model/User.swift new file mode 100644 index 0000000..fd348d9 --- /dev/null +++ b/PadelClubData/PadelClubData/Model/User.swift @@ -0,0 +1,256 @@ +// +// User.swift +// PadelClub +// +// Created by Laurent Morvillier on 21/02/2024. +// + +import Foundation +import LeStorage + +enum UserRight: Int, Codable { + case none = 0 + case edition = 1 + case creation = 2 +} + +class User: ModelObject, UserBase, Storable { + + static func resourceName() -> String { "users" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] } + static func filterByStoreIdentifier() -> Bool { return false } + static var relationshipNames: [String] = [] + + public var id: String = Store.randomId() + public var username: String + public var email: String + var clubs: [String] = [] + var umpireCode: String? + var licenceId: String? + var firstName: String + var lastName: String + var phone: String? + var country: String? + + var summonsMessageBody : String? = nil + var summonsMessageSignature: String? = nil + var summonsAvailablePaymentMethods: String? = nil + var summonsDisplayFormat: Bool = false + var summonsDisplayEntryFee: Bool = false + var summonsUseFullCustomMessage: Bool = false + var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil + var bracketMatchFormatPreference: MatchFormat? + var groupStageMatchFormatPreference: MatchFormat? + var loserBracketMatchFormatPreference: MatchFormat? + + var deviceId: String? + + init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?) { + self.username = username + self.firstName = firstName + self.lastName = lastName + self.email = email + self.phone = phone + self.country = country + } + + public func uuid() throws -> UUID { + if let uuid = UUID(uuidString: self.id) { + return uuid + } + throw UUIDError.cantConvertString(string: self.id) + } + + func defaultSignature() -> String { + return "Sportivement,\n\(firstName) \(lastName), votre JAP." + } + + func hasTenupClubs() -> Bool { + self.clubsObjects().filter({ $0.code != nil }).isEmpty == false + } + + func hasFavoriteClubsAndCreatedClubs() -> Bool { + clubsObjects(includeCreated: true).isEmpty == false + } + + func setUserClub(_ userClub: Club) { + self.clubs.insert(userClub.id, at: 0) + } + + func clubsObjects(includeCreated: Bool = false) -> [Club] { + return DataStore.shared.clubs.filter({ (includeCreated && $0.creator == id) || clubs.contains($0.id) }) + } + + func createdClubsObjectsNotFavorite() -> [Club] { + return DataStore.shared.clubs.filter({ ($0.creator == id) && clubs.contains($0.id) == false }) + } + + func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) { + if estimatedDuration == matchFormat.defaultEstimatedDuration { + matchFormatsDefaultDuration?.removeValue(forKey: matchFormat) + } else { + matchFormatsDefaultDuration = matchFormatsDefaultDuration ?? [MatchFormat: Int]() + matchFormatsDefaultDuration?[matchFormat] = estimatedDuration + } + } + + func addClub(_ club: Club) { + if !self.clubs.contains(where: { $0 == club.id }) { + self.clubs.append(club.id) + } + } + +// enum CodingKeys: String, CodingKey { +// case _id = "id" +// case _username = "username" +// case _email = "email" +// case _clubs = "clubs" +// case _umpireCode = "umpireCode" +// case _licenceId = "licenceId" +// case _firstName = "firstName" +// case _lastName = "lastName" +// case _phone = "phone" +// case _country = "country" +// case _summonsMessageBody = "summonsMessageBody" +// case _summonsMessageSignature = "summonsMessageSignature" +// case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods" +// case _summonsDisplayFormat = "summonsDisplayFormat" +// case _summonsDisplayEntryFee = "summonsDisplayEntryFee" +// case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage" +// case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration" +// case _bracketMatchFormatPreference = "bracketMatchFormatPreference" +// case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" +// case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" +// case _deviceId = "deviceId" +// } + +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// +// try container.encode(id, forKey: ._id) +// try container.encode(username, forKey: ._username) +// try container.encode(email, forKey: ._email) +// try container.encode(clubs, forKey: ._clubs) +// +// if let umpireCode = umpireCode { +// try container.encode(umpireCode, forKey: ._umpireCode) +// } else { +// try container.encodeNil(forKey: ._umpireCode) +// } +// +// if let licenceId = licenceId { +// try container.encode(licenceId, forKey: ._licenceId) +// } else { +// try container.encodeNil(forKey: ._licenceId) +// } +// +// try container.encode(firstName, forKey: ._firstName) +// try container.encode(lastName, forKey: ._lastName) +// +// if let phone = phone { +// try container.encode(phone, forKey: ._phone) +// } else { +// try container.encodeNil(forKey: ._phone) +// } +// +// if let country = country { +// try container.encode(country, forKey: ._country) +// } else { +// try container.encodeNil(forKey: ._country) +// } +// +// if let summonsMessageBody = summonsMessageBody { +// try container.encode(summonsMessageBody, forKey: ._summonsMessageBody) +// } else { +// try container.encodeNil(forKey: ._summonsMessageBody) +// } +// +// if let summonsMessageSignature = summonsMessageSignature { +// try container.encode(summonsMessageSignature, forKey: ._summonsMessageSignature) +// } else { +// try container.encodeNil(forKey: ._summonsMessageSignature) +// } +// +// if let summonsAvailablePaymentMethods = summonsAvailablePaymentMethods { +// try container.encode(summonsAvailablePaymentMethods, forKey: ._summonsAvailablePaymentMethods) +// } else { +// try container.encodeNil(forKey: ._summonsAvailablePaymentMethods) +// } +// +// try container.encode(summonsDisplayFormat, forKey: ._summonsDisplayFormat) +// try container.encode(summonsDisplayEntryFee, forKey: ._summonsDisplayEntryFee) +// try container.encode(summonsUseFullCustomMessage, forKey: ._summonsUseFullCustomMessage) +// +// if let matchFormatsDefaultDuration = matchFormatsDefaultDuration { +// try container.encode(matchFormatsDefaultDuration, forKey: ._matchFormatsDefaultDuration) +// } else { +// try container.encodeNil(forKey: ._matchFormatsDefaultDuration) +// } +// +// if let bracketMatchFormatPreference = bracketMatchFormatPreference { +// try container.encode(bracketMatchFormatPreference, forKey: ._bracketMatchFormatPreference) +// } else { +// try container.encodeNil(forKey: ._bracketMatchFormatPreference) +// } +// +// if let groupStageMatchFormatPreference = groupStageMatchFormatPreference { +// try container.encode(groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference) +// } else { +// try container.encodeNil(forKey: ._groupStageMatchFormatPreference) +// } +// +// if let loserBracketMatchFormatPreference = loserBracketMatchFormatPreference { +// try container.encode(loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference) +// } else { +// try container.encodeNil(forKey: ._loserBracketMatchFormatPreference) +// } +// +// if let deviceId { +// try container.encode(deviceId, forKey: ._deviceId) +// } else { +// try container.encodeNil(forKey: ._deviceId) +// } +// +// } + + static func placeHolder() -> User { + return User(username: "", email: "", firstName: "", lastName: "", phone: nil, country: nil) + } + +} + +class UserCreationForm: User, UserPasswordBase { + + init(user: User, username: String, password: String, firstName: String, lastName: String, email: String, phone: String?, country: String?) { + self.password = password + super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country) + + self.summonsMessageBody = user.summonsMessageBody + self.summonsMessageSignature = user.summonsMessageSignature + self.summonsAvailablePaymentMethods = user.summonsAvailablePaymentMethods + self.summonsDisplayFormat = user.summonsDisplayFormat + self.summonsDisplayEntryFee = user.summonsDisplayEntryFee + self.summonsUseFullCustomMessage = user.summonsUseFullCustomMessage + self.matchFormatsDefaultDuration = user.matchFormatsDefaultDuration + self.bracketMatchFormatPreference = user.bracketMatchFormatPreference + self.groupStageMatchFormatPreference = user.groupStageMatchFormatPreference + self.loserBracketMatchFormatPreference = user.loserBracketMatchFormatPreference + + } + + required init(from decoder: Decoder) throws { + fatalError("init(from:) has not been implemented") + } + + public var password: String + + private enum CodingKeys: String, CodingKey { + case password + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.password, forKey: .password) + } +} diff --git a/PadelClub/Extensions/MySortDescriptor.swift b/PadelClubData/PadelClubData/MySortDescriptor.swift similarity index 100% rename from PadelClub/Extensions/MySortDescriptor.swift rename to PadelClubData/PadelClubData/MySortDescriptor.swift diff --git a/PadelClub/Utils/PListReader.swift b/PadelClubData/PadelClubData/PListReader.swift similarity index 100% rename from PadelClub/Utils/PListReader.swift rename to PadelClubData/PadelClubData/PListReader.swift diff --git a/PadelClubData/PadelClubData/PadelClubData.docc/PadelClubData.md b/PadelClubData/PadelClubData/PadelClubData.docc/PadelClubData.md new file mode 100755 index 0000000..23dbee6 --- /dev/null +++ b/PadelClubData/PadelClubData/PadelClubData.docc/PadelClubData.md @@ -0,0 +1,13 @@ +# ``PadelClubData`` + +Summary + +## Overview + +Text + +## Topics + +### Group + +- ``Symbol`` \ No newline at end of file diff --git a/PadelClubData/PadelClubData/PadelClubData.h b/PadelClubData/PadelClubData/PadelClubData.h new file mode 100644 index 0000000..918c89d --- /dev/null +++ b/PadelClubData/PadelClubData/PadelClubData.h @@ -0,0 +1,18 @@ +// +// PadelClubData.h +// PadelClubData +// +// Created by Laurent Morvillier on 27/08/2024. +// + +#import + +//! Project version number for PadelClubData. +FOUNDATION_EXPORT double PadelClubDataVersionNumber; + +//! Project version string for PadelClubData. +FOUNDATION_EXPORT const unsigned char PadelClubDataVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClubData/PadelClubData/PadelRule.swift similarity index 96% rename from PadelClub/Utils/PadelRule.swift rename to PadelClubData/PadelClubData/PadelRule.swift index 394d72a..84b27a4 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClubData/PadelClubData/PadelRule.swift @@ -12,7 +12,7 @@ enum RankSource: Hashable { case ligue case club(assimilation: Bool) - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + func localizedLabel() -> String { switch self { case .national: return "Classement National" @@ -33,6 +33,7 @@ protocol TournamentBuildHolder: Identifiable { } struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { + var id: String { identifier } let category: TournamentCategory let level: TournamentLevel @@ -40,26 +41,17 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { // var japIdentifier: Int? = nil // var japFirstName: String? = nil // var japLastName: String? = nil - - func buildHolderTitle() -> String { - localizedLabel() - } var identifier: String { level.localizedLabel()+":"+category.localizedLabel()+":"+age.localizedLabel() } - var computedLabel: String { - if age == .senior { return localizedLabel() } - return localizedLabel() + " " + localizedAge - } - - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + func localizedLabel() -> String { level.localizedLabel() + category.localizedLabel(.short) } - var localizedTitle: String { - level.localizedLabel() + " " + category.localizedLabel() + func buildHolderTitle() -> String { + localizedLabel() } var localizedAge: String { @@ -688,6 +680,40 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { self.init(rawValue: value) } + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + switch self { + case .unlisted: + return displayStyle == .title ? "Aucune" : "" + case .men: + switch displayStyle { + case .title: + return "DH" + case .wide: + return "Hommes" + case .short: + return "H" + } + case .women: + switch displayStyle { + case .title: + return "DD" + case .wide: + return "Dames" + case .short: + return "D" + } + case .mix: + switch displayStyle { + case .title: + return "MX" + case .wide: + return "Mixte" + case .short: + return "MX" + } + } + } + func mandatoryPlayerType() -> [Int] { switch self { case .unlisted: @@ -803,40 +829,6 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { } } - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { - switch self { - case .unlisted: - return displayStyle == .title ? "Aucune" : "" - case .men: - switch displayStyle { - case .title: - return "DH" - case .wide: - return "Hommes" - case .short: - return "H" - } - case .women: - switch displayStyle { - case .title: - return "DD" - case .wide: - return "Dames" - case .short: - return "D" - } - case .mix: - switch displayStyle { - case .title: - return "MX" - case .wide: - return "Mixte" - case .short: - return "MX" - } - } - } - var playerFilterOption: PlayerFilterOption { switch self { case .men, .unlisted: @@ -889,14 +881,6 @@ enum TournamentType: Int, Hashable, Codable, CaseIterable, Identifiable { case doubleBrackets var id: Int { self.rawValue } - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { - switch self { - case .classic: - return "Classique" - case .doubleBrackets: - return "Double Poules" - } - } } enum TeamPosition: Int, Identifiable, Hashable, Codable, CaseIterable { @@ -914,23 +898,6 @@ enum TeamPosition: Int, Identifiable, Hashable, Codable, CaseIterable { } } - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { - var shortName: String { - switch self { - case .one: - return "#1" - case .two: - return "#2" - } - } - - switch displayStyle { - case .wide, .title: - return "Équipe " + shortName - case .short: - return shortName - } - } } enum SetFormat: Int, Hashable, Codable { @@ -1125,17 +1092,6 @@ enum MatchFormat: Int, Hashable, Codable, CaseIterable { } } - static func defaultFormatForMatchType(_ matchType: MatchType) -> MatchFormat { - switch matchType { - case .bracket: - DataStore.shared.user.bracketMatchFormatPreference ?? .nineGamesDecisivePoint - case .groupStage: - DataStore.shared.user.groupStageMatchFormatPreference ?? .nineGamesDecisivePoint - case .loserBracket: - DataStore.shared.user.loserBracketMatchFormatPreference ?? .nineGamesDecisivePoint - } - } - static var allCases: [MatchFormat] { [.twoSets, .twoSetsDecisivePoint, .twoSetsSuperTie, .twoSetsDecisivePointSuperTie, .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint, .nineGames, .nineGamesDecisivePoint, .superTie, .megaTie] } @@ -1169,15 +1125,6 @@ enum MatchFormat: Int, Hashable, Codable, CaseIterable { Duration.seconds((estimatedDuration + additionalDuration) * 60).formatted(.units(allowed: [.minutes])) } - func formattedEstimatedBreakDuration() -> String { - var label = Duration.seconds(breakTime.breakTime * 60).formatted(.units(allowed: [.minutes])) - if breakTime.matchCount > 1 { - label += " après \(breakTime.matchCount) match" - label += breakTime.matchCount.pluralSuffix - } - return label - } - var defaultEstimatedDuration: Int { switch self { case .twoSets: @@ -1348,6 +1295,18 @@ enum MatchFormat: Int, Hashable, Codable, CaseIterable { return .megaTieBreak } } + + static func defaultFormatForMatchType(_ matchType: MatchType) -> MatchFormat { + switch matchType { + case .bracket: + DataStore.shared.user.bracketMatchFormatPreference ?? .nineGamesDecisivePoint + case .groupStage: + DataStore.shared.user.groupStageMatchFormatPreference ?? .nineGamesDecisivePoint + case .loserBracket: + DataStore.shared.user.loserBracketMatchFormatPreference ?? .nineGamesDecisivePoint + } + } + } enum Format: Int, Hashable, Codable { diff --git a/PadelClub/Utils/Patcher.swift b/PadelClubData/PadelClubData/Patcher.swift similarity index 97% rename from PadelClub/Utils/Patcher.swift rename to PadelClubData/PadelClubData/Patcher.swift index 4b7e07b..0b6368c 100644 --- a/PadelClub/Utils/Patcher.swift +++ b/PadelClubData/PadelClubData/Patcher.swift @@ -68,7 +68,7 @@ class Patcher { guard devServices.hasToken() else { return } - guard URLs.api.rawValue == "https://padelclub.app/roads/" else { + guard StoreCenter.main.synchronizationApiURL == "https://padelclub.app/roads/" else { return } diff --git a/PadelClubData/PadelClubData/PlayerFilterOption.swift b/PadelClubData/PadelClubData/PlayerFilterOption.swift new file mode 100644 index 0000000..6c793f3 --- /dev/null +++ b/PadelClubData/PadelClubData/PlayerFilterOption.swift @@ -0,0 +1,37 @@ +// +// PlayerFilterOption.swift +// PadelClubData +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation + +enum PlayerFilterOption: Int, Hashable, CaseIterable, Identifiable { + case all = -1 + case male = 1 + case female = 0 + + var id: Int { rawValue } + + func icon() -> String { + switch self { + case .all: + return "Tous" + case .male: + return "Homme" + case .female: + return "Femme" + } + } + + var localizedPlayerLabel: String { + switch self { + case .female: + return "joueuse" + default: + return "joueur" + } + } + +} diff --git a/PadelClub/ViewModel/Screen.swift b/PadelClubData/PadelClubData/Screen.swift similarity index 100% rename from PadelClub/ViewModel/Screen.swift rename to PadelClubData/PadelClubData/Screen.swift diff --git a/PadelClub/ViewModel/SeedInterval.swift b/PadelClubData/PadelClubData/SeedInterval.swift similarity index 85% rename from PadelClub/ViewModel/SeedInterval.swift rename to PadelClubData/PadelClubData/SeedInterval.swift index e34f620..056a2eb 100644 --- a/PadelClub/ViewModel/SeedInterval.swift +++ b/PadelClubData/PadelClubData/SeedInterval.swift @@ -50,11 +50,4 @@ struct SeedInterval: Hashable, Comparable { } } - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { - if dimension < 3 { - return "\(first)\(first.ordinalFormattedSuffix()) place" - } else { - return "Place \(first) à \(last)" - } - } } diff --git a/PadelClubData/PadelClubData/Sequence+Extensions.swift b/PadelClubData/PadelClubData/Sequence+Extensions.swift new file mode 100644 index 0000000..94976c7 --- /dev/null +++ b/PadelClubData/PadelClubData/Sequence+Extensions.swift @@ -0,0 +1,81 @@ +// +// Sequence+Extensions.swift +// PadelClubData +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation + +enum SortOrder { + case ascending + case descending +} + +extension Sequence { + func sorted(by keyPath: KeyPath) -> [Element] { + return sorted { a, b in + return a[keyPath: keyPath] < b[keyPath: keyPath] + } + } + + func sorted(using descriptors: [MySortDescriptor], + order: SortOrder) -> [Element] { + sorted { valueA, valueB in + for descriptor in descriptors { + let result = descriptor.comparator(valueA, valueB) + + switch result { + case .orderedSame: + // Keep iterating if the two elements are equal, + // since that'll let the next descriptor determine + // the sort order: + break + case .orderedAscending: + return order == .ascending + case .orderedDescending: + return order == .descending + } + } + + // If no descriptor was able to determine the sort + // order, we'll default to false (similar to when + // using the '<' operator with the built-in API): + return false + } + } + + func sorted(using descriptors: MySortDescriptor...) -> [Element] { + sorted(using: descriptors, order: .ascending) + } +} + +extension Array { + func chunked(into size: Int) -> [[Element]] { + return stride(from: 0, to: count, by: size).map { + Array(self[$0 ..< Swift.min($0 + size, count)]) + } + } + + func anySatisfy(_ p: (Element) -> Bool) -> Bool { + return first(where: { p($0) }) != nil + //return !self.allSatisfy { !p($0) } + } + + func sorted(by keyPath: KeyPath, order: SortOrder) -> [Element] { + switch order { + case .ascending: + return self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] } + case .descending: + return self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] } + } + } + +} + +extension Collection { + /// Returns the element at the specified index if it is within bounds, otherwise nil. + subscript (safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/PadelClub/Extensions/String+Crypto.swift b/PadelClubData/PadelClubData/String+Crypto.swift similarity index 100% rename from PadelClub/Extensions/String+Crypto.swift rename to PadelClubData/PadelClubData/String+Crypto.swift diff --git a/PadelClubData/PadelClubData/String+Extensions.swift b/PadelClubData/PadelClubData/String+Extensions.swift new file mode 100644 index 0000000..9d244ee --- /dev/null +++ b/PadelClubData/PadelClubData/String+Extensions.swift @@ -0,0 +1,63 @@ +// +// String+Extensions.swift +// PadelClubData +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import Foundation + +extension String { + + func replaceCharactersFromSet(characterSet: CharacterSet, replacementString: String = "") -> String { + components(separatedBy: characterSet).joined(separator:replacementString) + } + + var removingFirstCharacter: String { + String(dropFirst()) + } + + var trimmed: String { + trimmingCharacters(in: .whitespacesAndNewlines) + } + + var canonicalVersion: String { + self.trimmed.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ").folding(options: .diacriticInsensitive, locale: .current).lowercased() + } + + var canonicalVersionWithPunctuation: String { + trimmed.folding(options: .diacriticInsensitive, locale: .current).lowercased() + } + + func acronym() -> String { + let acronym = canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines) + if acronym.count > 10 { + return concatenateFirstLetters().uppercased() + } else { + return acronym.uppercased() + } + } + + func concatenateFirstLetters() -> String { + // Split the input into sentences + let sentences = self.components(separatedBy: .whitespacesAndNewlines) + if sentences.count == 1 { + return String(self.prefix(10)) + } + // Extract the first character of each sentence + let firstLetters = sentences.compactMap { sentence -> Character? in + let trimmedSentence = sentence.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmedSentence.count > 2 { + if let firstCharacter = trimmedSentence.first { + return firstCharacter + } + } + return nil + } + + // Join the first letters together into a string + let result = String(firstLetters) + return result + } + +} diff --git a/PadelClub/Subscription/Guard.swift b/PadelClubData/PadelClubData/Subscription/Guard.swift similarity index 100% rename from PadelClub/Subscription/Guard.swift rename to PadelClubData/PadelClubData/Subscription/Guard.swift diff --git a/PadelClub/Subscription/StoreItem.swift b/PadelClubData/PadelClubData/Subscription/StoreItem.swift similarity index 100% rename from PadelClub/Subscription/StoreItem.swift rename to PadelClubData/PadelClubData/Subscription/StoreItem.swift diff --git a/PadelClub/Subscription/StoreManager.swift b/PadelClubData/PadelClubData/Subscription/StoreManager.swift similarity index 100% rename from PadelClub/Subscription/StoreManager.swift rename to PadelClubData/PadelClubData/Subscription/StoreManager.swift diff --git a/PadelClub/Data/TournamentStore.swift b/PadelClubData/PadelClubData/TournamentStore.swift similarity index 100% rename from PadelClub/Data/TournamentStore.swift rename to PadelClubData/PadelClubData/TournamentStore.swift diff --git a/PadelClub/Extensions/URL+Extensions.swift b/PadelClubData/PadelClubData/URL+Extensions.swift similarity index 100% rename from PadelClub/Extensions/URL+Extensions.swift rename to PadelClubData/PadelClubData/URL+Extensions.swift diff --git a/PadelClubData/PadelClubDataTests/PadelClubDataTests.swift b/PadelClubData/PadelClubDataTests/PadelClubDataTests.swift new file mode 100644 index 0000000..76dcecb --- /dev/null +++ b/PadelClubData/PadelClubDataTests/PadelClubDataTests.swift @@ -0,0 +1,36 @@ +// +// PadelClubDataTests.swift +// PadelClubDataTests +// +// Created by Laurent Morvillier on 27/08/2024. +// + +import XCTest +@testable import PadelClubData + +final class PadelClubDataTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +}