Compare commits

...

2 Commits
main ... compil

  1. 186
      PadelClub.xcodeproj/project.pbxproj
  2. 3
      PadelClub.xcworkspace/contents.xcworkspacedata
  3. 86
      PadelClub/Data/Club+Extensions.swift
  4. 248
      PadelClub/Data/Club.swift
  5. 73
      PadelClub/Data/Enum+Extensions.swift
  6. 55
      PadelClub/Data/GroupStage+Extensions.swift
  7. 89
      PadelClub/Data/Match+Extensions.swift
  8. 13
      PadelClub/Data/MatchScheduler+Extensions.swift
  9. 43
      PadelClub/Data/MonthData+Extensions.swift
  10. 101
      PadelClub/Data/MonthData.swift
  11. 230
      PadelClub/Data/PlayerRegistration+Extensions.swift
  12. 574
      PadelClub/Data/PlayerRegistration.swift
  13. 155
      PadelClub/Data/Round+Extensions.swift
  14. 20
      PadelClub/Data/SeedInterval+Extensions.swift
  15. 109
      PadelClub/Data/TeamRegistration+Extensions.swift
  16. 395
      PadelClub/Data/Tournament+Extensions.swift
  17. 21
      PadelClub/Data/User+Extensions.swift
  18. 266
      PadelClub/Data/User.swift
  19. 13
      PadelClub/Extensions/Array+Extensions.swift
  20. 56
      PadelClub/Extensions/Date+Extensions.swift
  21. 54
      PadelClub/Extensions/Sequence+Extensions.swift
  22. 56
      PadelClub/Extensions/String+Extensions.swift
  23. 6
      PadelClub/Utils/DisplayContext.swift
  24. 29
      PadelClub/ViewModel/SearchViewModel.swift
  25. 20
      PadelClub/Views/Cashier/CashierView.swift
  26. 695
      PadelClubData/PadelClubData.xcodeproj/project.pbxproj
  27. 7
      PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  28. 8
      PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  29. BIN
      PadelClubData/PadelClubData.xcodeproj/project.xcworkspace/xcuserdata/laurentmorvillier.xcuserdatad/UserInterfaceState.xcuserstate
  30. 80
      PadelClubData/PadelClubData.xcodeproj/xcshareddata/xcschemes/PadelClubData.xcscheme
  31. 27
      PadelClubData/PadelClubData.xcodeproj/xcuserdata/laurentmorvillier.xcuserdatad/xcschemes/xcschememanagement.plist
  32. 0
      PadelClubData/PadelClubData/AppSettings.swift
  33. 2
      PadelClubData/PadelClubData/DataStore.swift
  34. 65
      PadelClubData/PadelClubData/Date+Extensions.swift
  35. 14
      PadelClubData/PadelClubData/DisplayStyle.swift
  36. 0
      PadelClubData/PadelClubData/Key.swift
  37. 171
      PadelClubData/PadelClubData/Model/Club.swift
  38. 49
      PadelClubData/PadelClubData/Model/Court.swift
  39. 15
      PadelClubData/PadelClubData/Model/DateInterval.swift
  40. 81
      PadelClubData/PadelClubData/Model/Event.swift
  41. 117
      PadelClubData/PadelClubData/Model/GroupStage.swift
  42. 266
      PadelClubData/PadelClubData/Model/Match.swift
  43. 167
      PadelClubData/PadelClubData/Model/MatchScheduler.swift
  44. 0
      PadelClubData/PadelClubData/Model/MockData.swift
  45. 70
      PadelClubData/PadelClubData/Model/MonthData.swift
  46. 363
      PadelClubData/PadelClubData/Model/PlayerRegistration.swift
  47. 0
      PadelClubData/PadelClubData/Model/Purchase.swift
  48. 217
      PadelClubData/PadelClubData/Model/Round.swift
  49. 347
      PadelClubData/PadelClubData/Model/TeamRegistration.swift
  50. 83
      PadelClubData/PadelClubData/Model/TeamScore.swift
  51. 599
      PadelClubData/PadelClubData/Model/Tournament.swift
  52. 256
      PadelClubData/PadelClubData/Model/User.swift
  53. 0
      PadelClubData/PadelClubData/MySortDescriptor.swift
  54. 0
      PadelClubData/PadelClubData/PListReader.swift
  55. 13
      PadelClubData/PadelClubData/PadelClubData.docc/PadelClubData.md
  56. 18
      PadelClubData/PadelClubData/PadelClubData.h
  57. 143
      PadelClubData/PadelClubData/PadelRule.swift
  58. 2
      PadelClubData/PadelClubData/Patcher.swift
  59. 37
      PadelClubData/PadelClubData/PlayerFilterOption.swift
  60. 0
      PadelClubData/PadelClubData/Screen.swift
  61. 7
      PadelClubData/PadelClubData/SeedInterval.swift
  62. 81
      PadelClubData/PadelClubData/Sequence+Extensions.swift
  63. 0
      PadelClubData/PadelClubData/String+Crypto.swift
  64. 63
      PadelClubData/PadelClubData/String+Extensions.swift
  65. 0
      PadelClubData/PadelClubData/Subscription/Guard.swift
  66. 0
      PadelClubData/PadelClubData/Subscription/StoreItem.swift
  67. 0
      PadelClubData/PadelClubData/Subscription/StoreManager.swift
  68. 0
      PadelClubData/PadelClubData/TournamentStore.swift
  69. 0
      PadelClubData/PadelClubData/URL+Extensions.swift
  70. 36
      PadelClubData/PadelClubDataTests/PadelClubDataTests.swift

@ -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 = "<group>"; };
C411C9CC2BF21DAF003017AD /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
C411C9CF2BF38F41003017AD /* TokenExemptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenExemptionTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
C42330AC2C7E1553001DABF5 /* GroupStage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GroupStage+Extensions.swift"; sourceTree = "<group>"; };
C42330AE2C7E159F001DABF5 /* Match+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Match+Extensions.swift"; sourceTree = "<group>"; };
C42330B02C7E16C1001DABF5 /* MonthData+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MonthData+Extensions.swift"; sourceTree = "<group>"; };
C42330B22C7E16F9001DABF5 /* PlayerRegistration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlayerRegistration+Extensions.swift"; sourceTree = "<group>"; };
C42330B42C7E1724001DABF5 /* Round+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Round+Extensions.swift"; sourceTree = "<group>"; };
C42330B82C7E189A001DABF5 /* Enum+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Enum+Extensions.swift"; sourceTree = "<group>"; };
C42330C22C7E21EC001DABF5 /* SeedInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SeedInterval+Extensions.swift"; sourceTree = "<group>"; };
C42330C72C7E2542001DABF5 /* TeamRegistration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TeamRegistration+Extensions.swift"; sourceTree = "<group>"; };
C42330CF2C7E296B001DABF5 /* MatchScheduler+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MatchScheduler+Extensions.swift"; sourceTree = "<group>"; };
C42330D72C7E2AB4001DABF5 /* Tournament+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tournament+Extensions.swift"; sourceTree = "<group>"; };
C42330E72C7F155C001DABF5 /* User+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "User+Extensions.swift"; sourceTree = "<group>"; };
C425D3FD2B6D249D002A7B48 /* PadelClub.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PadelClub.app; sourceTree = BUILT_PRODUCTS_DIR; };
C425D4002B6D249D002A7B48 /* PadelClubApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubApp.swift; sourceTree = "<group>"; };
C425D4042B6D249E002A7B48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -324,35 +318,22 @@
C4489BE12C05BF5000043F3D /* DebugSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugSettingsView.swift; sourceTree = "<group>"; };
C44B79102BBDA63A00906534 /* Locale+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Extensions.swift"; sourceTree = "<group>"; };
C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = SyncedProducts.storekit; sourceTree = "<group>"; };
C45BAE432BCA753E002EEC8A /* Purchase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Purchase.swift; sourceTree = "<group>"; };
C4607A7C2C04DDE2004CB781 /* APICallsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICallsListView.swift; sourceTree = "<group>"; };
C493B37D2C10AD3600862481 /* LoadingViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewModifier.swift; sourceTree = "<group>"; };
C49EF0182BD694290077B5AA /* PurchaseListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseListView.swift; sourceTree = "<group>"; };
C49EF01A2BD6A1E80077B5AA /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = "<group>"; };
C49EF0252BD80AE80077B5AA /* SubscriptionInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionInfoView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
C49EF0412BE23BF50077B5AA /* PaymentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentTests.swift; sourceTree = "<group>"; };
C49EF0432BE286780077B5AA /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = "<group>"; };
C4A47D592B6D383C00ADC637 /* Tournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tournament.swift; sourceTree = "<group>"; };
C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
C4A47D622B6D3D6500ADC637 /* Club.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Club.swift; sourceTree = "<group>"; };
C4A47D862B7BA36D00ADC637 /* UserCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCreationView.swift; sourceTree = "<group>"; };
C4A47D892B7BBB6500ADC637 /* SubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionView.swift; sourceTree = "<group>"; };
C4A47D8D2B7BBBEC00ADC637 /* StoreManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreManager.swift; sourceTree = "<group>"; };
C4A47D8E2B7BBBEC00ADC637 /* Guard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Guard.swift; sourceTree = "<group>"; };
C4A47D8F2B7BBBEC00ADC637 /* StoreItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreItem.swift; sourceTree = "<group>"; };
C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepperView.swift; sourceTree = "<group>"; };
C4A47DA52B83948E00ADC637 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
C4A47DA82B85F82100ADC637 /* ChangePasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangePasswordView.swift; sourceTree = "<group>"; };
C4A47DAC2B85FCCD00ADC637 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
C4A47DB22B86387500ADC637 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = "<group>"; };
C4B3A1542C2581DA0078EAA8 /* Patcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Patcher.swift; sourceTree = "<group>"; };
C4C01D972C481C0C0059087C /* CapsuleViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleViewModifier.swift; sourceTree = "<group>"; };
C4EC6F562BE92CAC000CEAB4 /* local.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = local.plist; sourceTree = "<group>"; };
C4EC6F582BE92D88000CEAB4 /* PListReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PListReader.swift; sourceTree = "<group>"; };
C4FC2E262C2AABC90021F3BF /* PasswordField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordField.swift; sourceTree = "<group>"; };
C4FC2E2A2C2C0E4D0021F3BF /* TournamentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStore.swift; sourceTree = "<group>"; };
FF025AD72BD0C10F00A86CF8 /* TeamHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamHeaderView.swift; sourceTree = "<group>"; };
FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchTeamDetailView.swift; sourceTree = "<group>"; };
FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterButtonView.swift; sourceTree = "<group>"; };
@ -361,7 +342,6 @@
FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentMatchFormatsSettingsView.swift; sourceTree = "<group>"; };
FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentGeneralSettingsView.swift; sourceTree = "<group>"; };
FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSettingsView.swift; sourceTree = "<group>"; };
FF025AE82BD1307E00A86CF8 /* MonthData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthData.swift; sourceTree = "<group>"; };
FF025AEC2BD1513700A86CF8 /* AppScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppScreen.swift; sourceTree = "<group>"; };
FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DurationSettingsView.swift; sourceTree = "<group>"; };
FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchFormatStorageView.swift; sourceTree = "<group>"; };
@ -431,7 +411,6 @@
FF1CBC202BB53E590036DAAB /* ClubHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubHolder.swift; sourceTree = "<group>"; };
FF1CBC212BB53E590036DAAB /* FederalTournamentHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournamentHolder.swift; sourceTree = "<group>"; };
FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubDetailView.swift; sourceTree = "<group>"; };
FF1DC5522BAB354A00FD8220 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = "<group>"; };
FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateClubView.swift; sourceTree = "<group>"; };
FF1DC5562BAB3AED00FD8220 /* ClubsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubsView.swift; sourceTree = "<group>"; };
FF1DC5582BAB767000FD8220 /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = "<group>"; };
@ -457,7 +436,6 @@
FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = "<group>"; };
FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduler.swift; sourceTree = "<group>"; };
FF3F74F52B919E45004CFE0E /* UmpireView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UmpireView.swift; sourceTree = "<group>"; };
FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDestination.swift; sourceTree = "<group>"; };
FF4AB6B42B9248200002987F /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = "<group>"; };
@ -493,7 +471,6 @@
FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFilterView.swift; sourceTree = "<group>"; };
FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = "<group>"; };
FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = "<group>"; };
FF6EC8FD2B94792300EA7F5A /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationContext.swift; sourceTree = "<group>"; };
FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Extensions.swift"; sourceTree = "<group>"; };
FF6EC9052B947A1000EA7F5A /* NetworkManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManagerError.swift; sourceTree = "<group>"; };
@ -508,10 +485,8 @@
FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizedTournamentView.swift; sourceTree = "<group>"; };
FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = "<group>"; };
FF8E1CE52C006E0200184680 /* Alphabet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alphabet.swift; sourceTree = "<group>"; };
FF8F26352BAD523300650388 /* PadelRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelRule.swift; sourceTree = "<group>"; };
FF8F263A2BAD528600650388 /* EventCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCreationView.swift; sourceTree = "<group>"; };
FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentConfiguratorView.swift; sourceTree = "<group>"; };
FF8F263E2BAD7D5C00650388 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = "<group>"; };
FF8F26402BADFC8700650388 /* TournamentInitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentInitView.swift; sourceTree = "<group>"; };
FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSettingsView.swift; sourceTree = "<group>"; };
FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentDurationManagerView.swift; sourceTree = "<group>"; };
@ -534,12 +509,6 @@
FF9268082BCEDC2C0080F940 /* CallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallView.swift; sourceTree = "<group>"; };
FF92680A2BCEE3E10080F940 /* ContactManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactManager.swift; sourceTree = "<group>"; };
FF92680C2BCEE5EA0080F940 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
FF967CE72BAEC70100A9A3BD /* GroupStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStage.swift; sourceTree = "<group>"; };
FF967CEB2BAECB9900A9A3BD /* Match.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Match.swift; sourceTree = "<group>"; };
FF967CED2BAECBD700A9A3BD /* Round.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Round.swift; sourceTree = "<group>"; };
FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamScore.swift; sourceTree = "<group>"; };
FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRegistration.swift; sourceTree = "<group>"; };
FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRegistration.swift; sourceTree = "<group>"; };
FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentRunningView.swift; sourceTree = "<group>"; };
FF967CF72BAEDF0000A9A3BD /* Labels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Labels.swift; sourceTree = "<group>"; };
FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageView.swift; sourceTree = "<group>"; };
@ -559,7 +528,6 @@
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentBroadcastRowView.swift; sourceTree = "<group>"; };
FFB9C8702BBADDE200A0EF4F /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = "<group>"; };
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = "<group>"; };
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamView.swift; sourceTree = "<group>"; };
FFBF065D2BBD8040009D6715 /* MatchListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchListView.swift; sourceTree = "<group>"; };
FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = "<group>"; };
@ -575,7 +543,6 @@
FFC83D4E2BB807D100750834 /* RoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundsView.swift; sourceTree = "<group>"; };
FFC83D502BB8087E00750834 /* RoundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundView.swift; sourceTree = "<group>"; };
FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FortuneWheelView.swift; sourceTree = "<group>"; };
FFC91B002BD85C2F00B29808 /* Court.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Court.swift; sourceTree = "<group>"; };
FFC91B022BD85E2400B29808 /* CourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtView.swift; sourceTree = "<group>"; };
FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageSettingsView.swift; sourceTree = "<group>"; };
FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyPasteButtonView.swift; sourceTree = "<group>"; };
@ -592,8 +559,6 @@
FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDescriptor.swift; sourceTree = "<group>"; };
FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = "<group>"; };
FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = "<group>"; };
FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -604,13 +569,11 @@
FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
FFF0241F2BF48B1A001F14B4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonValidateView.swift; sourceTree = "<group>"; };
FFF116E02BD2A9B600A33B06 /* DateInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateInterval.swift; sourceTree = "<group>"; };
FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtAvailabilitySettingsView.swift; sourceTree = "<group>"; };
FFF1D2CA2C4A22B200C8D33D /* ExportFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportFormat.swift; sourceTree = "<group>"; };
FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduleEditorView.swift; sourceTree = "<group>"; };
FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalPlayer.swift; sourceTree = "<group>"; };
FFF8ACD32B92392C008466FA /* SourceFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceFileManager.swift; sourceTree = "<group>"; };
FFF8ACD52B923960008466FA /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
FFF8ACD82B923F3C008466FA /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
FFF9644F2BC25E3700EEF017 /* PlanningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanningView.swift; sourceTree = "<group>"; };
@ -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;
};
@ -728,6 +691,7 @@
C425D4592B6D255B002A7B48 /* Frameworks */ = {
isa = PBXGroup;
children = (
C423307B2C7E1290001DABF5 /* PadelClubData.framework */,
C49EF0372BDFF3000077B5AA /* LeStorage.framework */,
);
name = Frameworks;
@ -737,26 +701,20 @@
isa = PBXGroup;
children = (
C411C9CC2BF21DAF003017AD /* README.md */,
C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */,
C4FC2E2A2C2C0E4D0021F3BF /* TournamentStore.swift */,
C4A47DAC2B85FCCD00ADC637 /* User.swift */,
C4A47D592B6D383C00ADC637 /* Tournament.swift */,
FF967CE72BAEC70100A9A3BD /* GroupStage.swift */,
FF967CED2BAECBD700A9A3BD /* Round.swift */,
FF967CEB2BAECB9900A9A3BD /* Match.swift */,
FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */,
FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */,
FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */,
C4A47D622B6D3D6500ADC637 /* Club.swift */,
FF8F263E2BAD7D5C00650388 /* Event.swift */,
FF025AE82BD1307E00A86CF8 /* MonthData.swift */,
FF1DC5522BAB354A00FD8220 /* MockData.swift */,
FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */,
FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */,
FFC91B002BD85C2F00B29808 /* Court.swift */,
FFF116E02BD2A9B600A33B06 /* DateInterval.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 = "<group>";
@ -799,11 +757,7 @@
C4A47D882B7BBB5000ADC637 /* Subscription */ = {
isa = PBXGroup;
children = (
C45BAE432BCA753E002EEC8A /* Purchase.swift */,
C4A47D892B7BBB6500ADC637 /* SubscriptionView.swift */,
C4A47D8E2B7BBBEC00ADC637 /* Guard.swift */,
C4A47D8D2B7BBBEC00ADC637 /* StoreManager.swift */,
C4A47D8F2B7BBBEC00ADC637 /* StoreItem.swift */,
C49EF0182BD694290077B5AA /* PurchaseListView.swift */,
C49EF0252BD80AE80077B5AA /* SubscriptionInfoView.swift */,
);
@ -1063,7 +1017,6 @@
FF3F74FD2B91A087004CFE0E /* ViewModel */ = {
isa = PBXGroup;
children = (
FF6EC8FD2B94792300EA7F5A /* Screen.swift */,
FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */,
FF7091652B90F0B000AB08DA /* TabDestination.swift */,
FF025AEC2BD1513700A86CF8 /* AppScreen.swift */,
@ -1072,7 +1025,6 @@
FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */,
FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */,
FFB9C8702BBADDE200A0EF4F /* Selectable.swift */,
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */,
FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */,
FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */,
FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */,
@ -1331,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 */,
@ -1355,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 = "<group>";
@ -1393,7 +1338,7 @@
C425D3F92B6D249D002A7B48 /* Sources */,
C425D3FA2B6D249D002A7B48 /* Frameworks */,
C425D3FB2B6D249D002A7B48 /* Resources */,
FF2BE4892B85E27400592328 /* Embed Frameworks */,
C423307E2C7E1290001DABF5 /* Embed Frameworks */,
);
buildRules = (
);
@ -1541,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 */,
@ -1558,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 */,
@ -1566,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 */,
@ -1593,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 */,
@ -1611,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 */,
@ -1647,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 */,
@ -1680,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 */,
@ -1689,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 */,
@ -1697,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 */,
@ -1714,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 */,
@ -1739,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 */,
@ -1760,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 */,
@ -1771,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;

@ -4,6 +4,9 @@
<FileRef
location = "group:../LeStorage/LeStorage.xcodeproj">
</FileRef>
<FileRef
location = "group:PadelClubData/PadelClubData.xcodeproj">
</FileRef>
<FileRef
location = "group:PadelClub.xcodeproj">
</FileRef>

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

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

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

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

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

@ -0,0 +1,13 @@
//
// MatchScheduler+Extensions.swift
// PadelClub
//
// Created by Laurent Morvillier on 27/08/2024.
//
import Foundation
import PadelClubData
extension MatchScheduler {
}

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

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

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

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

@ -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..<roundCount).map { //index 0 is the final
let round = Round(tournament: tournament, index: $0, matchFormat: loserBracketMatchFormat)
round.parent = id //parent
return round
}
do {
try self.tournamentStore.rounds.addOrUpdate(contentOfs: rounds)
} catch {
Logger.error(error)
}
let matchCount = RoundRule.numberOfMatches(forTeams: currentRoundMatchCount)
let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: loserBracketMatchFormat, name: round.roundTitle(initialMode: true))
//initial mode let the roundTitle give a name without considering the playable match
}
do {
try self.tournamentStore.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
loserRounds().forEach { round in
round.buildLoserBracket()
}
}
}
extension Round: Selectable, Equatable {
static func == (lhs: Round, rhs: Round) -> 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
}
}

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

@ -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<String> = Set<String>(arrayOfIds.sorted())
let searchedIds = Set<String>(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: " ")
}
}

@ -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 = ["", "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<String> = Set<String>()
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..<roundCount).map { //index 0 is the final
return Round(tournament: id, index: $0, matchFormat: roundSmartMatchFormat($0))
}
do {
try self.tournamentStore.rounds.addOrUpdate(contentOfs: rounds)
} catch {
Logger.error(error)
}
let matchCount = RoundRule.numberOfMatches(forTeams: bracketTeamCount())
let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: round.matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: RoundRule.matchIndexWithinRound(fromMatchIndex: $0)))
}
print(matches.map {
(RoundRule.roundName(fromMatchIndex: $0.index), RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index))
})
do {
try self.tournamentStore.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
rounds.forEach { round in
round.buildLoserBracket()
}
}
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 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)
}
}

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

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

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

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

@ -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<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [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<Element>],
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>...) -> [Element] {
sorted(using: descriptors, order: .ascending)
}
}

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

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

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

@ -42,16 +42,16 @@ extension ShareableObject: Transferable {
}
}
extension Array {
func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>, 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<T: Comparable>(by keyPath: KeyPath<Element, T>, 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()

@ -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 = "<group>"; };
C42330602C7E118B001DABF5 /* PadelClubData.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = PadelClubData.docc; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
C42330812C7E1330001DABF5 /* Court.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Court.swift; sourceTree = "<group>"; };
C42330832C7E1344001DABF5 /* TeamScore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamScore.swift; sourceTree = "<group>"; };
C42330842C7E1344001DABF5 /* Purchase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Purchase.swift; sourceTree = "<group>"; };
C42330852C7E1344001DABF5 /* MonthData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthData.swift; sourceTree = "<group>"; };
C42330872C7E1344001DABF5 /* Round.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Round.swift; sourceTree = "<group>"; };
C42330882C7E1344001DABF5 /* PlayerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRegistration.swift; sourceTree = "<group>"; };
C423308A2C7E1344001DABF5 /* TeamRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRegistration.swift; sourceTree = "<group>"; };
C423308B2C7E1344001DABF5 /* Match.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Match.swift; sourceTree = "<group>"; };
C423308C2C7E1344001DABF5 /* Tournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tournament.swift; sourceTree = "<group>"; };
C423308E2C7E1344001DABF5 /* GroupStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStage.swift; sourceTree = "<group>"; };
C423308F2C7E1345001DABF5 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = "<group>"; };
C42330902C7E1345001DABF5 /* MatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduler.swift; sourceTree = "<group>"; };
C42330912C7E1345001DABF5 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = "<group>"; };
C42330922C7E1345001DABF5 /* DateInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateInterval.swift; sourceTree = "<group>"; };
C42330932C7E1345001DABF5 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
C42330A72C7E1432001DABF5 /* PadelRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelRule.swift; sourceTree = "<group>"; };
C42330AA2C7E151A001DABF5 /* PlayerFilterOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerFilterOption.swift; sourceTree = "<group>"; };
C42330B62C7E17ED001DABF5 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
C42330BA2C7E1D1D001DABF5 /* DisplayStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayStyle.swift; sourceTree = "<group>"; };
C42330BE2C7E20AA001DABF5 /* Sequence+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Extensions.swift"; sourceTree = "<group>"; };
C42330C02C7E2172001DABF5 /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = "<group>"; };
C42330C92C7E2589001DABF5 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
C42330CB2C7E2643001DABF5 /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
C42330CD2C7E2669001DABF5 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = "<group>"; };
C42330D92C7E2B3D001DABF5 /* TournamentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStore.swift; sourceTree = "<group>"; };
C42330DD2C7F1098001DABF5 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
C42330DE2C7F1098001DABF5 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
C42330E12C7F12FA001DABF5 /* PListReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PListReader.swift; sourceTree = "<group>"; };
C42330E32C7F1347001DABF5 /* Guard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Guard.swift; sourceTree = "<group>"; };
C42330E42C7F1347001DABF5 /* StoreManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreManager.swift; sourceTree = "<group>"; };
C42330E52C7F1347001DABF5 /* StoreItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreItem.swift; sourceTree = "<group>"; };
C42330EC2C7F17B8001DABF5 /* Patcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Patcher.swift; sourceTree = "<group>"; };
C42330EE2C80610D001DABF5 /* String+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Crypto.swift"; sourceTree = "<group>"; };
C42330F02C8061D9001DABF5 /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = "<group>"; };
C42330F22C8068D9001DABF5 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
/* 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 = "<group>";
};
C423305D2C7E118B001DABF5 /* Products */ = {
isa = PBXGroup;
children = (
C423305C2C7E118B001DABF5 /* PadelClubData.framework */,
C42330662C7E118B001DABF5 /* PadelClubDataTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
C423306A2C7E118B001DABF5 /* PadelClubDataTests */ = {
isa = PBXGroup;
children = (
C423306B2C7E118B001DABF5 /* PadelClubDataTests.swift */,
);
path = PadelClubDataTests;
sourceTree = "<group>";
};
C42330762C7E126B001DABF5 /* Frameworks */ = {
isa = PBXGroup;
children = (
C42330772C7E126B001DABF5 /* LeStorage.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
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 = "<group>";
};
C42330E62C7F1347001DABF5 /* Subscription */ = {
isa = PBXGroup;
children = (
C42330E32C7F1347001DABF5 /* Guard.swift */,
C42330E42C7F1347001DABF5 /* StoreManager.swift */,
C42330E52C7F1347001DABF5 /* StoreItem.swift */,
);
path = Subscription;
sourceTree = "<group>";
};
/* 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 */;
}

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C423305B2C7E118B001DABF5"
BuildableName = "PadelClubData.framework"
BlueprintName = "PadelClubData"
ReferencedContainer = "container:PadelClubData.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C42330652C7E118B001DABF5"
BuildableName = "PadelClubDataTests.xctest"
BlueprintName = "PadelClubDataTests"
ReferencedContainer = "container:PadelClubData.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C423305B2C7E118B001DABF5"
BuildableName = "PadelClubData.framework"
BlueprintName = "PadelClubData"
ReferencedContainer = "container:PadelClubData.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>PadelClubData.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>C423305B2C7E118B001DABF5</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>C42330652C7E118B001DABF5</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

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

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

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

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

@ -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"
}
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)
}
// enum CodingKeys: String, CodingKey {
// case _id = "id"
// case _index = "index"
// case _club = "club"
// case _name = "name"
// case _exitAllowed = "exitAllowed"
// case _indoor = "indoor"
// }
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)
// }
}

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

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

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

@ -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,38 +94,13 @@ defer {
#endif
if groupStage != nil {
return index
} else if let index = (matches ?? roundObject?.playedMatches().sorted(by: \.index))?.firstIndex(where: { $0.id == id }) {
} 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)"
}
return RoundRule.matchIndexWithinRound(fromMatchIndex: index)
}
func isSeedLocked(atTeamPosition teamPosition: TeamPosition) -> Bool {
@ -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)
}
// 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"
// }
try container.encode(confirmed, forKey: ._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)

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

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

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

@ -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,51 +515,18 @@ 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..<roundCount).map { //index 0 is the final
let round = Round(tournament: tournament, index: $0, matchFormat: loserBracketMatchFormat)
round.parent = id //parent
return round
}
do {
try self.tournamentStore.rounds.addOrUpdate(contentOfs: rounds)
} catch {
Logger.error(error)
}
let matchCount = RoundRule.numberOfMatches(forTeams: currentRoundMatchCount)
let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: loserBracketMatchFormat, name: round.roundTitle(initialMode: true))
//initial mode let the roundTitle give a name without considering the playable match
}
do {
try self.tournamentStore.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
var parentRound: Round? {
guard let parent = parent else { return nil }
return self.tournamentStore.rounds.findById(parent)
}
loserRounds().forEach { round in
round.buildLoserBracket()
var matchFormat: MatchFormat {
get {
format ?? .defaultFormatForMatchType(.bracket)
}
set {
format = newValue
}
var parentRound: Round? {
guard let parent = parent else { return nil }
return self.tournamentStore.rounds.findById(parent)
}
func updateMatchFormat(_ updatedMatchFormat: MatchFormat, checkIfPossible: Bool, andLoserBracket: Bool) {
@ -663,40 +573,40 @@ defer {
self.tournamentStore.rounds.deleteDependencies(loserRounds)
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _tournament = "tournament"
case _index = "index"
case _parent = "parent"
case _format = "format"
case _startDate = "startDate"
}
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)
if let parent = parent {
try container.encode(parent, forKey: ._parent)
} else {
try container.encodeNil(forKey: ._parent)
}
if let format = format {
try container.encode(format, forKey: ._format)
} else {
try container.encodeNil(forKey: ._format)
}
// enum CodingKeys: String, CodingKey {
// case _id = "id"
// case _tournament = "tournament"
// case _index = "index"
// case _parent = "parent"
// case _format = "format"
// case _startDate = "startDate"
// }
if let startDate = startDate {
try container.encode(startDate, forKey: ._startDate)
} else {
try container.encodeNil(forKey: ._startDate)
}
}
// 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)
//
// if let parent = parent {
// try container.encode(parent, forKey: ._parent)
// } else {
// try container.encodeNil(forKey: ._parent)
// }
//
// 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)
// }
// }
func insertOnServer() {
self.tournamentStore.rounds.writeChangeAndInsertOnServer(instance: self)
@ -707,49 +617,4 @@ defer {
}
extension Round: Selectable, Equatable {
static func == (lhs: Round, rhs: Round) -> 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
}
}

@ -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<String> = Set<String>(arrayOfIds.sorted())
let searchedIds = Set<String>(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<PlayerRegistration>, 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"
}
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)
}
}
// 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 insertOnServer() {
self.tournamentStore.teamRegistrations.writeChangeAndInsertOnServer(instance: self)

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

@ -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,88 +222,92 @@ 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<CodingKeys>) throws {
@ -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 = ["", "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<String> = Set<String>()
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..<roundCount).map { //index 0 is the final
return Round(tournament: id, index: $0, matchFormat: roundSmartMatchFormat($0))
}
do {
try self.tournamentStore.rounds.addOrUpdate(contentOfs: rounds)
} catch {
Logger.error(error)
}
let matchCount = RoundRule.numberOfMatches(forTeams: bracketTeamCount())
let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: round.matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: RoundRule.matchIndexWithinRound(fromMatchIndex: $0)))
}
print(matches.map {
(RoundRule.roundName(fromMatchIndex: $0.index), RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index))
})
do {
try self.tournamentStore.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
rounds.forEach { round in
round.buildLoserBracket()
}
}
func match(for bracketPosition: Int?) -> Match? {
guard let bracketPosition else { return nil }
let matchIndex = bracketPosition / 2
@ -1911,6 +1558,22 @@ defer {
return self._matchSchedulers().first
}
func courtNameIfAvailable(atIndex courtIndex: Int) -> String? {
return club()?.customizedCourts.first(where: { $0.index == courtIndex })?.name
}
func courtName(atIndex courtIndex: Int) -> String {
return courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex)
}
func tournamentWinner() -> TeamRegistration? {
let finals: Round? = self.tournamentStore.rounds.first(where: { $0.index == 0 && $0.parent == nil })
// let rounds: [Round] = Store.main.filter(isIncluded: { $0.index == 0 && $0.tournament == id && $0.parent == nil })
// let final: Round? = .first
return finals?.playedMatches().first?.winner()
}
func currentMonthData() -> MonthData? {
guard let rankSourceDate else { return nil }
let dateString = URL.importDateFormatter.string(from: rankSourceDate)
@ -1925,20 +1588,17 @@ defer {
return currentMonthData()?.femaleUnrankedValue
}
func courtNameIfAvailable(atIndex courtIndex: Int) -> String? {
return club()?.customizedCourts.first(where: { $0.index == courtIndex })?.name
}
func courtName(atIndex courtIndex: Int) -> String {
return courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex)
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 tournamentWinner() -> TeamRegistration? {
let finals: Round? = self.tournamentStore.rounds.first(where: { $0.index == 0 && $0.parent == nil })
// let rounds: [Round] = Store.main.filter(isIncluded: { $0.index == 0 && $0.tournament == id && $0.parent == nil })
// let final: Round? = .first
return finals?.playedMatches().first?.winner()
}
func getGroupStageChunkValue() -> Int {
@ -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)
}
}

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

@ -0,0 +1,13 @@
# ``PadelClubData``
<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->
## Overview
<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@-->
## Topics
### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->
- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->

@ -0,0 +1,18 @@
//
// PadelClubData.h
// PadelClubData
//
// Created by Laurent Morvillier on 27/08/2024.
//
#import <Foundation/Foundation.h>
//! 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 <PadelClubData/PublicHeader.h>

@ -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
@ -41,25 +42,16 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable {
// 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 {

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

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

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

@ -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<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
return sorted { a, b in
return a[keyPath: keyPath] < b[keyPath: keyPath]
}
}
func sorted(using descriptors: [MySortDescriptor<Element>],
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>...) -> [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<T: Comparable>(by keyPath: KeyPath<Element, T>, 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
}
}

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

@ -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.
}
}
}
Loading…
Cancel
Save