diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index cac1ff1..ea45443 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -123,6 +123,268 @@ FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */; }; FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */; }; FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4C7F012BBBD7150031B6A3 /* TabItemModifier.swift */; }; + FF4CBF442C996C0600151637 /* UserCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D862B7BA36D00ADC637 /* UserCreationView.swift */; }; + FF4CBF452C996C0600151637 /* TabDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091652B90F0B000AB08DA /* TabDestination.swift */; }; + FF4CBF462C996C0600151637 /* CashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267F72BCE78C70080F940 /* CashierView.swift */; }; + FF4CBF472C996C0600151637 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263E2BAD7D5C00650388 /* Event.swift */; }; + FF4CBF482C996C0600151637 /* PlayerHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */; }; + FF4CBF492C996C0600151637 /* LoserRoundStepScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11628B2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift */; }; + FF4CBF4A2C996C0600151637 /* ClubCourtSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF53FBB72BFB302B0051D4C3 /* ClubCourtSetupView.swift */; }; + FF4CBF4B2C996C0600151637 /* Patcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B3A1542C2581DA0078EAA8 /* Patcher.swift */; }; + FF4CBF4C2C996C0600151637 /* FileImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBE2BB0B14600F0AEC7 /* FileImportView.swift */; }; + FF4CBF4D2C996C0600151637 /* StepperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */; }; + FF4CBF4E2C996C0600151637 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.swift */; }; + FF4CBF4F2C996C0600151637 /* FederalTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */; }; + FF4CBF502C996C0600151637 /* TournamentInitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26402BADFC8700650388 /* TournamentInitView.swift */; }; + FF4CBF512C996C0600151637 /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D892B7BBB6500ADC637 /* SubscriptionView.swift */; }; + FF4CBF522C996C0600151637 /* TournamentLookUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD655D72C8DE27400E5B35E /* TournamentLookUpView.swift */; }; + FF4CBF532C996C0600151637 /* ClubsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5562BAB3AED00FD8220 /* ClubsView.swift */; }; + FF4CBF542C996C0600151637 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103112C366E5900684FC9 /* ImagePickerView.swift */; }; + FF4CBF552C996C0600151637 /* MatchTypeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264E2BAE0B9600650388 /* MatchTypeSelectionView.swift */; }; + FF4CBF562C996C0600151637 /* MatchSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D052BAF3C4200A9A3BD /* MatchSetupView.swift */; }; + FF4CBF572C996C0600151637 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6B42B9248200002987F /* NetworkManager.swift */; }; + FF4CBF582C996C0600151637 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; }; + FF4CBF592C996C0600151637 /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; }; + FF4CBF5A2C996C0600151637 /* TournamentClubSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */; }; + FF4CBF5B2C996C0600151637 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; }; + FF4CBF5C2C996C0600151637 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; }; + FF4CBF5D2C996C0600151637 /* SupportButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */; }; + FF4CBF5E2C996C0600151637 /* TournamentBroadcastRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */; }; + FF4CBF5F2C996C0600151637 /* TeamWeightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADE2BD0CE0A00A86CF8 /* TeamWeightView.swift */; }; + FF4CBF602C996C0600151637 /* SeedsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268002BCE94920080F940 /* SeedsCallingView.swift */; }; + FF4CBF612C996C0600151637 /* CallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268082BCEDC2C0080F940 /* CallView.swift */; }; + FF4CBF622C996C0600151637 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */; }; + FF4CBF632C996C0600151637 /* EditSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */; }; + FF4CBF642C996C0600151637 /* TournamentCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091672B90F79F00AB08DA /* TournamentCellView.swift */; }; + FF4CBF652C996C0600151637 /* Sequence+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */; }; + FF4CBF662C996C0600151637 /* CashierDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267F92BCE78EB0080F940 /* CashierDetailView.swift */; }; + FF4CBF672C996C0600151637 /* EventClubSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103072C353B7600684FC9 /* EventClubSettingsView.swift */; }; + FF4CBF682C996C0600151637 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DB22B86387500ADC637 /* AccountView.swift */; }; + FF4CBF692C996C0600151637 /* PlayersWithoutContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */; }; + FF4CBF6A2C996C0600151637 /* TeamsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */; }; + FF4CBF6B2C996C0600151637 /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */; }; + FF4CBF6C2C996C0600151637 /* TeamScore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */; }; + FF4CBF6D2C996C0600151637 /* EditablePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */; }; + FF4CBF6E2C996C0600151637 /* PlayerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162842BD00279000C4809 /* PlayerDetailView.swift */; }; + FF4CBF6F2C996C0600151637 /* ListRowViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */; }; + FF4CBF702C996C0600151637 /* PresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */; }; + FF4CBF712C996C0600151637 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; }; + FF4CBF722C996C0600151637 /* SwiftParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */; }; + FF4CBF732C996C0600151637 /* ChangePasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA82B85F82100ADC637 /* ChangePasswordView.swift */; }; + FF4CBF742C996C0600151637 /* TournamentSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */; }; + FF4CBF752C996C0600151637 /* RoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D502BB8087E00750834 /* RoundView.swift */; }; + FF4CBF762C996C0600151637 /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; + FF4CBF772C996C0600151637 /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; + FF4CBF782C996C0600151637 /* EventCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263A2BAD528600650388 /* EventCreationView.swift */; }; + FF4CBF792C996C0600151637 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; + FF4CBF7A2C996C0600151637 /* CapsuleViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C01D972C481C0C0059087C /* CapsuleViewModifier.swift */; }; + FF4CBF7B2C996C0600151637 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; }; + FF4CBF7C2C996C0600151637 /* SchedulerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF964542BC266CF00EEF017 /* SchedulerView.swift */; }; + FF4CBF7D2C996C0600151637 /* PadelClubButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */; }; + FF4CBF7E2C996C0600151637 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; }; + FF4CBF7F2C996C0600151637 /* TournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916B2B91005400AB08DA /* TournamentView.swift */; }; + FF4CBF802C996C0600151637 /* OngoingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30552BD95B1100F2B93D /* OngoingView.swift */; }; + FF4CBF812C996C0600151637 /* CreateClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */; }; + FF4CBF822C996C0600151637 /* APICallsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4607A7C2C04DDE2004CB781 /* APICallsListView.swift */; }; + FF4CBF832C996C0600151637 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; + FF4CBF842C996C0600151637 /* DurationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */; }; + FF4CBF852C996C0600151637 /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AEC2BD1513700A86CF8 /* AppScreen.swift */; }; + FF4CBF862C996C0600151637 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.swift */; }; + FF4CBF872C996C0600151637 /* PointSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */; }; + FF4CBF882C996C0600151637 /* TeamRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */; }; + FF4CBF892C996C0600151637 /* ContactManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF92680A2BCEE3E10080F940 /* ContactManager.swift */; }; + FF4CBF8A2C996C0600151637 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD2F22C412681000DBD9A /* AppDelegate.swift */; }; + FF4CBF8B2C996C0600151637 /* SubscriptionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0252BD80AE80077B5AA /* SubscriptionInfoView.swift */; }; + FF4CBF8C2C996C0600151637 /* EditScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0012BBC39A600B82851 /* EditScoreView.swift */; }; + FF4CBF8D2C996C0600151637 /* TournamentOrganizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091612B90F04300AB08DA /* TournamentOrganizerView.swift */; }; + FF4CBF8E2C996C0600151637 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF92680C2BCEE5EA0080F940 /* NetworkMonitor.swift */; }; + FF4CBF8F2C996C0600151637 /* TournamentRunningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */; }; + FF4CBF902C996C0600151637 /* TournamentDatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264A2BAE0B4100650388 /* TournamentDatePickerView.swift */; }; + FF4CBF912C996C0600151637 /* CourtAvailabilitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */; }; + FF4CBF922C996C0600151637 /* ConfirmButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */; }; + FF4CBF932C996C0600151637 /* PasswordField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E262C2AABC90021F3BF /* PasswordField.swift */; }; + FF4CBF942C996C0600151637 /* MatchRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFF2BAEEF6400A9A3BD /* MatchRowView.swift */; }; + FF4CBF952C996C0600151637 /* Locale+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B79102BBDA63A00906534 /* Locale+Extensions.swift */; }; + FF4CBF962C996C0600151637 /* HtmlService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B732BFA00FC000B4573 /* HtmlService.swift */; }; + FF4CBF972C996C0600151637 /* GroupStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CE72BAEC70100A9A3BD /* GroupStage.swift */; }; + FF4CBF982C996C0600151637 /* TournamentCashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162802BCF945C000C4809 /* TournamentCashierView.swift */; }; + FF4CBF992C996C0600151637 /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8D2B7BBBEC00ADC637 /* StoreManager.swift */; }; + FF4CBF9A2C996C0600151637 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */; }; + FF4CBF9B2C996C0600151637 /* PlayerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */; }; + FF4CBF9C2C996C0600151637 /* ImportedPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */; }; + FF4CBF9D2C996C0600151637 /* EditingTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162862BD004AD000C4809 /* EditingTeamView.swift */; }; + FF4CBF9E2C996C0600151637 /* NetworkManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9052B947A1000EA7F5A /* NetworkManagerError.swift */; }; + FF4CBF9F2C996C0600151637 /* Tournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D592B6D383C00ADC637 /* Tournament.swift */; }; + FF4CBFA02C996C0600151637 /* TournamentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E2A2C2C0E4D0021F3BF /* TournamentStore.swift */; }; + FF4CBFA12C996C0600151637 /* LoserRoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5647122C0B6F380081F995 /* LoserRoundSettingsView.swift */; }; + FF4CBFA22C996C0600151637 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; + FF4CBFA32C996C0600151637 /* CloseDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */; }; + FF4CBFA42C996C0600151637 /* BarButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */; }; + FF4CBFA52C996C0600151637 /* PlanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF9644F2BC25E3700EEF017 /* PlanningView.swift */; }; + FF4CBFA62C996C0600151637 /* Match.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEB2BAECB9900A9A3BD /* Match.swift */; }; + FF4CBFA72C996C0600151637 /* TournamentLevelPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */; }; + FF4CBFA82C996C0600151637 /* FederalTournamentHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC212BB53E590036DAAB /* FederalTournamentHolder.swift */; }; + FF4CBFA92C996C0600151637 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */; }; + FF4CBFAA2C996C0600151637 /* SetDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */; }; + FF4CBFAB2C996C0600151637 /* TeamHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AD72BD0C10F00A86CF8 /* TeamHeaderView.swift */; }; + FF4CBFAC2C996C0600151637 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; + FF4CBFAD2C996C0600151637 /* RoundScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF964562BC26B3400EEF017 /* RoundScheduleEditorView.swift */; }; + FF4CBFAE2C996C0600151637 /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */; }; + FF4CBFAF2C996C0600151637 /* TournamentConfiguratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */; }; + FF4CBFB02C996C0600151637 /* ClubImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */; }; + FF4CBFB12C996C0600151637 /* UnderlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF558C622C6CDD020071F9AE /* UnderlineView.swift */; }; + FF4CBFB22C996C0600151637 /* MatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */; }; + FF4CBFB32C996C0600151637 /* CallMessageCustomizationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */; }; + FF4CBFB42C996C0600151637 /* TournamentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */; }; + FF4CBFB52C996C0600151637 /* MatchTeamDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */; }; + FF4CBFB62C996C0600151637 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; }; + FF4CBFB72C996C0600151637 /* DateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E02BD2A9B600A33B06 /* DateInterval.swift */; }; + FF4CBFB82C996C0600151637 /* TableStructureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26532BAE1E4400650388 /* TableStructureView.swift */; }; + FF4CBFB92C996C0600151637 /* Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45BAE432BCA753E002EEC8A /* Purchase.swift */; }; + FF4CBFBA2C996C0600151637 /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; + FF4CBFBB2C996C0600151637 /* Round.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CED2BAECBD700A9A3BD /* Round.swift */; }; + FF4CBFBC2C996C0600151637 /* FederalDataViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */; }; + FF4CBFBD2C996C0600151637 /* AgendaDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */; }; + FF4CBFBE2C996C0600151637 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; + FF4CBFBF2C996C0600151637 /* SetInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0152BBC5A4C00B82851 /* SetInputView.swift */; }; + FF4CBFC02C996C0600151637 /* ButtonValidateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */; }; + FF4CBFC12C996C0600151637 /* ClubRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D882BB4935C005CB568 /* ClubRowView.swift */; }; + FF4CBFC22C996C0600151637 /* ClubDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */; }; + FF4CBFC32C996C0600151637 /* GroupStageCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268022BCE94A30080F940 /* GroupStageCallingView.swift */; }; + FF4CBFC42C996C0600151637 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0432BE286780077B5AA /* Key.swift */; }; + FF4CBFC52C996C0600151637 /* CashierSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */; }; + FF4CBFC62C996C0600151637 /* LoserRoundScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFCDE0D2BCC833600317DEF /* LoserRoundScheduleEditorView.swift */; }; + FF4CBFC72C996C0600151637 /* Club.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D622B6D3D6500ADC637 /* Club.swift */; }; + FF4CBFC82C996C0600151637 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC90A2B947AC000EA7F5A /* Array+Extensions.swift */; }; + FF4CBFC92C996C0600151637 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; }; + FF4CBFCA2C996C0600151637 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; }; + FF4CBFCB2C996C0600151637 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD82B923F3C008466FA /* String+Extensions.swift */; }; + FF4CBFCC2C996C0600151637 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */; }; + FF4CBFCD2C996C0600151637 /* TournamentGeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */; }; + FF4CBFCE2C996C0600151637 /* LoserRoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC2DCB12BBE75D40046DB9F /* LoserRoundView.swift */; }; + FF4CBFCF2C996C0600151637 /* AddTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF90FC1C2C44FB3E009339B2 /* AddTeamView.swift */; }; + FF4CBFD02C996C0600151637 /* PlayerPayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267FB2BCE84870080F940 /* PlayerPayView.swift */; }; + FF4CBFD12C996C0600151637 /* PlanningByCourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B51542C7A4DAF00FFF126 /* PlanningByCourtView.swift */; }; + FF4CBFD22C996C0600151637 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; }; + FF4CBFD32C996C0600151637 /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; + FF4CBFD42C996C0600151637 /* FederalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */; }; + FF4CBFD52C996C0600151637 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */; }; + FF4CBFD62C996C0600151637 /* FixedWidthInteger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9082B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift */; }; + FF4CBFD72C996C0600151637 /* LoserBracketFromGroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */; }; + FF4CBFD82C996C0600151637 /* ImportedPlayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */; }; + FF4CBFD92C996C0600151637 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; + FF4CBFDA2C996C0600151637 /* PlayerPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */; }; + FF4CBFDB2C996C0600151637 /* InscriptionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */; }; + FF4CBFDC2C996C0600151637 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; + FF4CBFDD2C996C0600151637 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; + FF4CBFDE2C996C0600151637 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */; }; + FF4CBFDF2C996C0600151637 /* FederalTournamentSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */; }; + FF4CBFE02C996C0600151637 /* TournamentFieldsManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26462BAE0ACB00650388 /* TournamentFieldsManagerView.swift */; }; + FF4CBFE12C996C0600151637 /* PrintSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B812BFA0124000B4573 /* PrintSettingsView.swift */; }; + FF4CBFE22C996C0600151637 /* TournamentMatchFormatsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */; }; + FF4CBFE32C996C0600151637 /* DateUpdateManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162892BD05247000C4809 /* DateUpdateManagerView.swift */; }; + FF4CBFE42C996C0600151637 /* MatchTypeSmallSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */; }; + FF4CBFE52C996C0600151637 /* MonthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE82BD1307E00A86CF8 /* MonthData.swift */; }; + FF4CBFE62C996C0600151637 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; + FF4CBFE72C996C0600151637 /* TournamentBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B6C2BF9E60B000B4573 /* TournamentBuildView.swift */; }; + FF4CBFE82C996C0600151637 /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; }; + FF4CBFE92C996C0600151637 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; + FF4CBFEA2C996C0600151637 /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; }; + FF4CBFEB2C996C0600151637 /* DisplayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */; }; + FF4CBFEC2C996C0600151637 /* TournamentCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268062BCE94D90080F940 /* TournamentCallView.swift */; }; + FF4CBFED2C996C0600151637 /* LoserRoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC2DCB32BBE9ECD0046DB9F /* LoserRoundsView.swift */; }; + FF4CBFEE2C996C0600151637 /* GroupStagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */; }; + FF4CBFEF2C996C0600151637 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; }; + FF4CBFF02C996C0600151637 /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF01A2BD6A1E80077B5AA /* URLs.swift */; }; + FF4CBFF12C996C0600151637 /* MatchDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */; }; + FF4CBFF22C996C0600151637 /* TournamentFormatSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */; }; + FF4CBFF32C996C0600151637 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; }; + FF4CBFF42C996C0600151637 /* PadelClubApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4002B6D249D002A7B48 /* PadelClubApp.swift */; }; + FF4CBFF52C996C0600151637 /* TournamentSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */; }; + FF4CBFF62C996C0600151637 /* String+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */; }; + FF4CBFF72C996C0600151637 /* GroupStageTeamReplacementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */; }; + FF4CBFF82C996C0600151637 /* TabItemModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4C7F012BBBD7150031B6A3 /* TabItemModifier.swift */; }; + FF4CBFF92C996C0600151637 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; }; + FF4CBFFA2C996C0600151637 /* TournamentScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0E0B6C2BC254C6005F00A9 /* TournamentScheduleView.swift */; }; + FF4CBFFB2C996C0600151637 /* MatchFormatStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */; }; + FF4CBFFC2C996C0600151637 /* UmpireView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74F52B919E45004CFE0E /* UmpireView.swift */; }; + FF4CBFFD2C996C0600151637 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* User.swift */; }; + FF4CBFFE2C996C0600151637 /* MatchSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D002BAEF0B400A9A3BD /* MatchSummaryView.swift */; }; + FF4CBFFF2C996C0600151637 /* TournamentDurationManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */; }; + FF4CC0002C996C0600151637 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5522BAB354A00FD8220 /* MockData.swift */; }; + FF4CC0012C996C0600151637 /* TeamDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D082BAF3D4000A9A3BD /* TeamDetailView.swift */; }; + FF4CC0022C996C0600151637 /* GroupStagesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */; }; + FF4CC0032C996C0600151637 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; + FF4CC0042C996C0600151637 /* HtmlGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B722BFA00FB000B4573 /* HtmlGenerator.swift */; }; + FF4CC0052C996C0600151637 /* PadelRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26352BAD523300650388 /* PadelRule.swift */; }; + FF4CC0062C996C0600151637 /* TeamRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */; }; + FF4CC0072C996C0600151637 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */; }; + FF4CC0082C996C0600151637 /* GroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */; }; + FF4CC0092C996C0600151637 /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5582BAB767000FD8220 /* Tips.swift */; }; + FF4CC00A2C996C0600151637 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; }; + FF4CC00B2C996C0600151637 /* MatchDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */; }; + FF4CC00C2C996C0600151637 /* PlanningSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF964522BC262B000EEF017 /* PlanningSettingsView.swift */; }; + FF4CC00D2C996C0600151637 /* MatchScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */; }; + FF4CC00E2C996C0600151637 /* FortuneWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */; }; + FF4CC00F2C996C0600151637 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD52B923960008466FA /* URL+Extensions.swift */; }; + FF4CC0102C996C0600151637 /* LoadingViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C493B37D2C10AD3600862481 /* LoadingViewModifier.swift */; }; + FF4CC0112C996C0600151637 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBC2BB0287D00F0AEC7 /* PlayerView.swift */; }; + FF4CC0122C996C0600151637 /* MatchDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D022BAEF0C000A9A3BD /* MatchDetailView.swift */; }; + FF4CC0132C996C0600151637 /* ExportFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF1D2CA2C4A22B200C8D33D /* ExportFormat.swift */; }; + FF4CC0142C996C0600151637 /* PlayerBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */; }; + FF4CC0152C996C0600151637 /* StoreItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8F2B7BBBEC00ADC637 /* StoreItem.swift */; }; + FF4CC0162C996C0600151637 /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; }; + FF4CC0172C996C0600151637 /* PointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0112BBC3E1A00B82851 /* PointView.swift */; }; + FF4CC0182C996C0600151637 /* ClubHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC202BB53E590036DAAB /* ClubHolder.swift */; }; + FF4CC0192C996C0600151637 /* EventSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */; }; + FF4CC01A2C996C0600151637 /* InscriptionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D772BB42C5B005CB568 /* InscriptionInfoView.swift */; }; + FF4CC01B2C996C0600151637 /* SelectablePlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */; }; + FF4CC01C2C996C0600151637 /* MatchFormatPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26502BAE0BAD00650388 /* MatchFormatPickerView.swift */; }; + FF4CC01D2C996C0600151637 /* TournamentRankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF712BE19274008B4B7E /* TournamentRankView.swift */; }; + FF4CC01E2C996C0600151637 /* NumberFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */; }; + FF4CC01F2C996C0600151637 /* SetLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0172BBC5A6800B82851 /* SetLabelView.swift */; }; + FF4CC0202C996C0600151637 /* DebugSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4489BE12C05BF5000043F3D /* DebugSettingsView.swift */; }; + FF4CC0212C996C0600151637 /* GroupStageScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF9645A2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift */; }; + FF4CC0222C996C0600151637 /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41812BF73EB3001B24CB /* EventView.swift */; }; + FF4CC0232C996C0600151637 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA52B83948E00ADC637 /* LoginView.swift */; }; + FF4CC0242C996C0600151637 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; }; + FF4CC0252C996C0600151637 /* Labels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF72BAEDF0000A9A3BD /* Labels.swift */; }; + FF4CC0262C996C0600151637 /* CopyPasteButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */; }; + FF4CC0272C996C0600151637 /* PlayerSexPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB32BB0020000F0AEC7 /* PlayerSexPickerView.swift */; }; + FF4CC0282C996C0600151637 /* TournamentInscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B702BF9EFE9000B4573 /* TournamentInscriptionView.swift */; }; + FF4CC0292C996C0600151637 /* CallSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267FE2BCE94830080F940 /* CallSettingsView.swift */; }; + FF4CC02A2C996C0600151637 /* FooterButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */; }; + FF4CC02B2C996C0600151637 /* RankCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */; }; + FF4CC02C2C996C0600151637 /* DateBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091692B90F95E00AB08DA /* DateBoxView.swift */; }; + FF4CC02D2C996C0600151637 /* LearnMoreSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */; }; + FF4CC02E2C996C0600151637 /* SourceFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD32B92392C008466FA /* SourceFileManager.swift */; }; + FF4CC02F2C996C0600151637 /* PListReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC6F582BE92D88000CEAB4 /* PListReader.swift */; }; + FF4CC0302C996C0600151637 /* UpdateSourceRankDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */; }; + FF4CC0312C996C0600151637 /* GlobalSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */; }; + FF4CC0322C996C0600151637 /* Guard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8E2B7BBBEC00ADC637 /* Guard.swift */; }; + FF4CC0332C996C0600151637 /* PurchaseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0182BD694290077B5AA /* PurchaseListView.swift */; }; + FF4CC0352C996C0600151637 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = FF4CBF3F2C996C0600151637 /* Algorithms */; }; + FF4CC0362C996C0600151637 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = FF4CBF412C996C0600151637 /* Zip */; }; + FF4CC0372C996C0600151637 /* LeStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; }; + FF4CC0392C996C0600151637 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4072B6D249E002A7B48 /* Preview Assets.xcassets */; }; + FF4CC03A2C996C0600151637 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */; }; + FF4CC03B2C996C0600151637 /* tournament-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7F2BFA0105000B4573 /* tournament-template.html */; }; + FF4CC03C2C996C0600151637 /* local.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = FF2B51602C7E302C00FFF126 /* local.sqlite */; }; + FF4CC03D2C996C0600151637 /* groupstagescore-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7B2BFA0105000B4573 /* groupstagescore-template.html */; }; + FF4CC03E2C996C0600151637 /* player-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7E2BFA0105000B4573 /* player-template.html */; }; + FF4CC03F2C996C0600151637 /* groupstagerow-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7A2BFA0105000B4573 /* groupstagerow-template.html */; }; + FF4CC0402C996C0600151637 /* hiddenplayer-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7C2BFA0105000B4573 /* hiddenplayer-template.html */; }; + FF4CC0412C996C0600151637 /* bracket-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B762BFA0105000B4573 /* bracket-template.html */; }; + FF4CC0422C996C0600151637 /* groupstagecol-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B782BFA0105000B4573 /* groupstagecol-template.html */; }; + FF4CC0432C996C0600151637 /* groupstage-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B772BFA0105000B4573 /* groupstage-template.html */; }; + FF4CC0442C996C0600151637 /* groupstageentrant-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B792BFA0105000B4573 /* groupstageentrant-template.html */; }; + FF4CC0452C996C0600151637 /* match-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7D2BFA0105000B4573 /* match-template.html */; }; + FF4CC0462C996C0600151637 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; }; + FF4CC0472C996C0600151637 /* SyncedProducts.storekit in Resources */ = {isa = PBXBuildFile; fileRef = C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */; }; + FF4CC0482C996C0600151637 /* local.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4EC6F562BE92CAC000CEAB4 /* local.plist */; }; + FF4CC0492C996C0600151637 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FF0CA5742BDA4AE10080E843 /* PrivacyInfo.xcprivacy */; }; + FF4CC04A2C996C0600151637 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4042B6D249E002A7B48 /* Assets.xcassets */; }; + FF4CC04C2C996C0600151637 /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; FF53FBB82BFB302B0051D4C3 /* ClubCourtSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF53FBB72BFB302B0051D4C3 /* ClubCourtSetupView.swift */; }; FF558C632C6CDD020071F9AE /* UnderlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF558C622C6CDD020071F9AE /* UnderlineView.swift */; }; FF5647132C0B6F390081F995 /* LoserRoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5647122C0B6F380081F995 /* LoserRoundSettingsView.swift */; }; @@ -571,6 +833,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + FF4CC04B2C996C0600151637 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + FF4CC04C2C996C0600151637 /* LeStorage.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; FF70FBCA2C90584900129CC2 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -745,6 +1018,7 @@ FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectablePlayerListView.swift; sourceTree = ""; }; FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportedPlayerView.swift; sourceTree = ""; }; FF4C7F012BBBD7150031B6A3 /* TabItemModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItemModifier.swift; sourceTree = ""; }; + FF4CC0502C996C0600151637 /* ProdTest PadelClub.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ProdTest PadelClub.app"; sourceTree = BUILT_PRODUCTS_DIR; }; FF53FBB72BFB302B0051D4C3 /* ClubCourtSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubCourtSetupView.swift; sourceTree = ""; }; FF558C622C6CDD020071F9AE /* UnderlineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnderlineView.swift; sourceTree = ""; }; FF5647122C0B6F380081F995 /* LoserRoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoserRoundSettingsView.swift; sourceTree = ""; }; @@ -930,6 +1204,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FF4CC0342C996C0600151637 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FF4CC0352C996C0600151637 /* Algorithms in Frameworks */, + FF4CC0362C996C0600151637 /* Zip in Frameworks */, + FF4CC0372C996C0600151637 /* LeStorage.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FF70FBB32C90584900129CC2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -961,6 +1245,7 @@ C425D40D2B6D249E002A7B48 /* PadelClubTests.xctest */, C425D4172B6D249E002A7B48 /* PadelClubUITests.xctest */, FF70FBCF2C90584900129CC2 /* PadelClub TestFlight.app */, + FF4CC0502C996C0600151637 /* ProdTest PadelClub.app */, ); name = Products; sourceTree = ""; @@ -1743,6 +2028,28 @@ productReference = C425D4172B6D249E002A7B48 /* PadelClubUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; + FF4CBF3E2C996C0600151637 /* ProdTest PadelClub */ = { + isa = PBXNativeTarget; + buildConfigurationList = FF4CC04D2C996C0600151637 /* Build configuration list for PBXNativeTarget "ProdTest PadelClub" */; + buildPhases = ( + FF4CBF432C996C0600151637 /* Sources */, + FF4CC0342C996C0600151637 /* Frameworks */, + FF4CC0382C996C0600151637 /* Resources */, + FF4CC04B2C996C0600151637 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ProdTest PadelClub"; + packageProductDependencies = ( + FF4CBF3F2C996C0600151637 /* Algorithms */, + FF4CBF412C996C0600151637 /* Zip */, + ); + productName = PadelClub; + productReference = FF4CC0502C996C0600151637 /* ProdTest PadelClub.app */; + productType = "com.apple.product-type.application"; + }; FF70FABD2C90584900129CC2 /* PadelClub TestFlight */ = { isa = PBXNativeTarget; buildConfigurationList = FF70FBCC2C90584900129CC2 /* Build configuration list for PBXNativeTarget "PadelClub TestFlight" */; @@ -1810,6 +2117,7 @@ C425D40C2B6D249E002A7B48 /* PadelClubTests */, C425D4162B6D249E002A7B48 /* PadelClubUITests */, FF70FABD2C90584900129CC2 /* PadelClub TestFlight */, + FF4CBF3E2C996C0600151637 /* ProdTest PadelClub */, ); }; /* End PBXProject section */ @@ -1854,6 +2162,31 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FF4CC0382C996C0600151637 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FF4CC0392C996C0600151637 /* Preview Assets.xcassets in Resources */, + FF4CC03A2C996C0600151637 /* Launch Screen.storyboard in Resources */, + FF4CC03B2C996C0600151637 /* tournament-template.html in Resources */, + FF4CC03C2C996C0600151637 /* local.sqlite in Resources */, + FF4CC03D2C996C0600151637 /* groupstagescore-template.html in Resources */, + FF4CC03E2C996C0600151637 /* player-template.html in Resources */, + FF4CC03F2C996C0600151637 /* groupstagerow-template.html in Resources */, + FF4CC0402C996C0600151637 /* hiddenplayer-template.html in Resources */, + FF4CC0412C996C0600151637 /* bracket-template.html in Resources */, + FF4CC0422C996C0600151637 /* groupstagecol-template.html in Resources */, + FF4CC0432C996C0600151637 /* groupstage-template.html in Resources */, + FF4CC0442C996C0600151637 /* groupstageentrant-template.html in Resources */, + FF4CC0452C996C0600151637 /* match-template.html in Resources */, + FF4CC0462C996C0600151637 /* Localizable.strings in Resources */, + FF4CC0472C996C0600151637 /* SyncedProducts.storekit in Resources */, + FF4CC0482C996C0600151637 /* local.plist in Resources */, + FF4CC0492C996C0600151637 /* PrivacyInfo.xcprivacy in Resources */, + FF4CC04A2C996C0600151637 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FF70FBB72C90584900129CC2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2151,6 +2484,253 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FF4CBF432C996C0600151637 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FF4CBF442C996C0600151637 /* UserCreationView.swift in Sources */, + FF4CBF452C996C0600151637 /* TabDestination.swift in Sources */, + FF4CBF462C996C0600151637 /* CashierView.swift in Sources */, + FF4CBF472C996C0600151637 /* Event.swift in Sources */, + FF4CBF482C996C0600151637 /* PlayerHolder.swift in Sources */, + FF4CBF492C996C0600151637 /* LoserRoundStepScheduleEditorView.swift in Sources */, + FF4CBF4A2C996C0600151637 /* ClubCourtSetupView.swift in Sources */, + FF4CBF4B2C996C0600151637 /* Patcher.swift in Sources */, + FF4CBF4C2C996C0600151637 /* FileImportView.swift in Sources */, + FF4CBF4D2C996C0600151637 /* StepperView.swift in Sources */, + FF4CBF4E2C996C0600151637 /* RoundsView.swift in Sources */, + FF4CBF4F2C996C0600151637 /* FederalTournament.swift in Sources */, + FF4CBF502C996C0600151637 /* TournamentInitView.swift in Sources */, + FF4CBF512C996C0600151637 /* SubscriptionView.swift in Sources */, + FF4CBF522C996C0600151637 /* TournamentLookUpView.swift in Sources */, + FF4CBF532C996C0600151637 /* ClubsView.swift in Sources */, + FF4CBF542C996C0600151637 /* ImagePickerView.swift in Sources */, + FF4CBF552C996C0600151637 /* MatchTypeSelectionView.swift in Sources */, + FF4CBF562C996C0600151637 /* MatchSetupView.swift in Sources */, + FF4CBF572C996C0600151637 /* NetworkManager.swift in Sources */, + FF4CBF582C996C0600151637 /* EventLinksView.swift in Sources */, + FF4CBF592C996C0600151637 /* SeedInterval.swift in Sources */, + FF4CBF5A2C996C0600151637 /* TournamentClubSettingsView.swift in Sources */, + FF4CBF5B2C996C0600151637 /* GroupStageTeamView.swift in Sources */, + FF4CBF5C2C996C0600151637 /* RoundSettingsView.swift in Sources */, + FF4CBF5D2C996C0600151637 /* SupportButtonView.swift in Sources */, + FF4CBF5E2C996C0600151637 /* TournamentBroadcastRowView.swift in Sources */, + FF4CBF5F2C996C0600151637 /* TeamWeightView.swift in Sources */, + FF4CBF602C996C0600151637 /* SeedsCallingView.swift in Sources */, + FF4CBF612C996C0600151637 /* CallView.swift in Sources */, + FF4CBF622C996C0600151637 /* Color+Extensions.swift in Sources */, + FF4CBF632C996C0600151637 /* EditSharingView.swift in Sources */, + FF4CBF642C996C0600151637 /* TournamentCellView.swift in Sources */, + FF4CBF652C996C0600151637 /* Sequence+Extensions.swift in Sources */, + FF4CBF662C996C0600151637 /* CashierDetailView.swift in Sources */, + FF4CBF672C996C0600151637 /* EventClubSettingsView.swift in Sources */, + FF4CBF682C996C0600151637 /* AccountView.swift in Sources */, + FF4CBF692C996C0600151637 /* PlayersWithoutContactView.swift in Sources */, + FF4CBF6A2C996C0600151637 /* TeamsCallingView.swift in Sources */, + FF4CBF6B2C996C0600151637 /* Calendar+Extensions.swift in Sources */, + FF4CBF6C2C996C0600151637 /* TeamScore.swift in Sources */, + FF4CBF6D2C996C0600151637 /* EditablePlayerView.swift in Sources */, + FF4CBF6E2C996C0600151637 /* PlayerDetailView.swift in Sources */, + FF4CBF6F2C996C0600151637 /* ListRowViewModifier.swift in Sources */, + FF4CBF702C996C0600151637 /* PresentationContext.swift in Sources */, + FF4CBF712C996C0600151637 /* AppSettings.swift in Sources */, + FF4CBF722C996C0600151637 /* SwiftParser.swift in Sources */, + FF4CBF732C996C0600151637 /* ChangePasswordView.swift in Sources */, + FF4CBF742C996C0600151637 /* TournamentSubscriptionView.swift in Sources */, + FF4CBF752C996C0600151637 /* RoundView.swift in Sources */, + FF4CBF762C996C0600151637 /* RowButtonView.swift in Sources */, + FF4CBF772C996C0600151637 /* SendToAllView.swift in Sources */, + FF4CBF782C996C0600151637 /* EventCreationView.swift in Sources */, + FF4CBF792C996C0600151637 /* LocationManager.swift in Sources */, + FF4CBF7A2C996C0600151637 /* CapsuleViewModifier.swift in Sources */, + FF4CBF7B2C996C0600151637 /* BroadcastView.swift in Sources */, + FF4CBF7C2C996C0600151637 /* SchedulerView.swift in Sources */, + FF4CBF7D2C996C0600151637 /* PadelClubButtonView.swift in Sources */, + FF4CBF7E2C996C0600151637 /* TournamentSeedEditing.swift in Sources */, + FF4CBF7F2C996C0600151637 /* TournamentView.swift in Sources */, + FF4CBF802C996C0600151637 /* OngoingView.swift in Sources */, + FF4CBF812C996C0600151637 /* CreateClubView.swift in Sources */, + FF4CBF822C996C0600151637 /* APICallsListView.swift in Sources */, + FF4CBF832C996C0600151637 /* NetworkFederalService.swift in Sources */, + FF4CBF842C996C0600151637 /* DurationSettingsView.swift in Sources */, + FF4CBF852C996C0600151637 /* AppScreen.swift in Sources */, + FF4CBF862C996C0600151637 /* CourtView.swift in Sources */, + FF4CBF872C996C0600151637 /* PointSelectionView.swift in Sources */, + FF4CBF882C996C0600151637 /* TeamRowView.swift in Sources */, + FF4CBF892C996C0600151637 /* ContactManager.swift in Sources */, + FF4CBF8A2C996C0600151637 /* AppDelegate.swift in Sources */, + FF4CBF8B2C996C0600151637 /* SubscriptionInfoView.swift in Sources */, + FF4CBF8C2C996C0600151637 /* EditScoreView.swift in Sources */, + FF4CBF8D2C996C0600151637 /* TournamentOrganizerView.swift in Sources */, + FF4CBF8E2C996C0600151637 /* NetworkMonitor.swift in Sources */, + FF4CBF8F2C996C0600151637 /* TournamentRunningView.swift in Sources */, + FF4CBF902C996C0600151637 /* TournamentDatePickerView.swift in Sources */, + FF4CBF912C996C0600151637 /* CourtAvailabilitySettingsView.swift in Sources */, + FF4CBF922C996C0600151637 /* ConfirmButtonView.swift in Sources */, + FF4CBF932C996C0600151637 /* PasswordField.swift in Sources */, + FF4CBF942C996C0600151637 /* MatchRowView.swift in Sources */, + FF4CBF952C996C0600151637 /* Locale+Extensions.swift in Sources */, + FF4CBF962C996C0600151637 /* HtmlService.swift in Sources */, + FF4CBF972C996C0600151637 /* GroupStage.swift in Sources */, + FF4CBF982C996C0600151637 /* TournamentCashierView.swift in Sources */, + FF4CBF992C996C0600151637 /* StoreManager.swift in Sources */, + FF4CBF9A2C996C0600151637 /* SearchViewModel.swift in Sources */, + FF4CBF9B2C996C0600151637 /* PlayerRegistration.swift in Sources */, + FF4CBF9C2C996C0600151637 /* ImportedPlayerView.swift in Sources */, + FF4CBF9D2C996C0600151637 /* EditingTeamView.swift in Sources */, + FF4CBF9E2C996C0600151637 /* NetworkManagerError.swift in Sources */, + FF4CBF9F2C996C0600151637 /* Tournament.swift in Sources */, + FF4CBFA02C996C0600151637 /* TournamentStore.swift in Sources */, + FF4CBFA12C996C0600151637 /* LoserRoundSettingsView.swift in Sources */, + FF4CBFA22C996C0600151637 /* Persistence.swift in Sources */, + FF4CBFA32C996C0600151637 /* CloseDatePicker.swift in Sources */, + FF4CBFA42C996C0600151637 /* BarButtonView.swift in Sources */, + FF4CBFA52C996C0600151637 /* PlanningView.swift in Sources */, + FF4CBFA62C996C0600151637 /* Match.swift in Sources */, + FF4CBFA72C996C0600151637 /* TournamentLevelPickerView.swift in Sources */, + FF4CBFA82C996C0600151637 /* FederalTournamentHolder.swift in Sources */, + FF4CBFA92C996C0600151637 /* DataStore.swift in Sources */, + FF4CBFAA2C996C0600151637 /* SetDescriptor.swift in Sources */, + FF4CBFAB2C996C0600151637 /* TeamHeaderView.swift in Sources */, + FF4CBFAC2C996C0600151637 /* OrganizedTournamentView.swift in Sources */, + FF4CBFAD2C996C0600151637 /* RoundScheduleEditorView.swift in Sources */, + FF4CBFAE2C996C0600151637 /* EventListView.swift in Sources */, + FF4CBFAF2C996C0600151637 /* TournamentConfiguratorView.swift in Sources */, + FF4CBFB02C996C0600151637 /* ClubImportView.swift in Sources */, + FF4CBFB12C996C0600151637 /* UnderlineView.swift in Sources */, + FF4CBFB22C996C0600151637 /* MatchScheduler.swift in Sources */, + FF4CBFB32C996C0600151637 /* CallMessageCustomizationView.swift in Sources */, + FF4CBFB42C996C0600151637 /* TournamentStatusView.swift in Sources */, + FF4CBFB52C996C0600151637 /* MatchTeamDetailView.swift in Sources */, + FF4CBFB62C996C0600151637 /* GenericDestinationPickerView.swift in Sources */, + FF4CBFB72C996C0600151637 /* DateInterval.swift in Sources */, + FF4CBFB82C996C0600151637 /* TableStructureView.swift in Sources */, + FF4CBFB92C996C0600151637 /* Purchase.swift in Sources */, + FF4CBFBA2C996C0600151637 /* Screen.swift in Sources */, + FF4CBFBB2C996C0600151637 /* Round.swift in Sources */, + FF4CBFBC2C996C0600151637 /* FederalDataViewModel.swift in Sources */, + FF4CBFBD2C996C0600151637 /* AgendaDestination.swift in Sources */, + FF4CBFBE2C996C0600151637 /* PadelClubApp.xcdatamodeld in Sources */, + FF4CBFBF2C996C0600151637 /* SetInputView.swift in Sources */, + FF4CBFC02C996C0600151637 /* ButtonValidateView.swift in Sources */, + FF4CBFC12C996C0600151637 /* ClubRowView.swift in Sources */, + FF4CBFC22C996C0600151637 /* ClubDetailView.swift in Sources */, + FF4CBFC32C996C0600151637 /* GroupStageCallingView.swift in Sources */, + FF4CBFC42C996C0600151637 /* Key.swift in Sources */, + FF4CBFC52C996C0600151637 /* CashierSettingsView.swift in Sources */, + FF4CBFC62C996C0600151637 /* LoserRoundScheduleEditorView.swift in Sources */, + FF4CBFC72C996C0600151637 /* Club.swift in Sources */, + FF4CBFC82C996C0600151637 /* Array+Extensions.swift in Sources */, + FF4CBFC92C996C0600151637 /* ToolboxView.swift in Sources */, + FF4CBFCA2C996C0600151637 /* Alphabet.swift in Sources */, + FF4CBFCB2C996C0600151637 /* String+Extensions.swift in Sources */, + FF4CBFCC2C996C0600151637 /* GroupStageSettingsView.swift in Sources */, + FF4CBFCD2C996C0600151637 /* TournamentGeneralSettingsView.swift in Sources */, + FF4CBFCE2C996C0600151637 /* LoserRoundView.swift in Sources */, + FF4CBFCF2C996C0600151637 /* AddTeamView.swift in Sources */, + FF4CBFD02C996C0600151637 /* PlayerPayView.swift in Sources */, + FF4CBFD12C996C0600151637 /* PlanningByCourtView.swift in Sources */, + FF4CBFD22C996C0600151637 /* FileImportManager.swift in Sources */, + FF4CBFD32C996C0600151637 /* TournamentButtonView.swift in Sources */, + FF4CBFD42C996C0600151637 /* FederalPlayer.swift in Sources */, + FF4CBFD52C996C0600151637 /* NavigationViewModel.swift in Sources */, + FF4CBFD62C996C0600151637 /* FixedWidthInteger+Extensions.swift in Sources */, + FF4CBFD72C996C0600151637 /* LoserBracketFromGroupStageView.swift in Sources */, + FF4CBFD82C996C0600151637 /* ImportedPlayer+Extensions.swift in Sources */, + FF4CBFD92C996C0600151637 /* ClubSearchView.swift in Sources */, + FF4CBFDA2C996C0600151637 /* PlayerPopoverView.swift in Sources */, + FF4CBFDB2C996C0600151637 /* InscriptionManagerView.swift in Sources */, + FF4CBFDC2C996C0600151637 /* ActivityView.swift in Sources */, + FF4CBFDD2C996C0600151637 /* MySortDescriptor.swift in Sources */, + FF4CBFDE2C996C0600151637 /* CalendarView.swift in Sources */, + FF4CBFDF2C996C0600151637 /* FederalTournamentSearchScope.swift in Sources */, + FF4CBFE02C996C0600151637 /* TournamentFieldsManagerView.swift in Sources */, + FF4CBFE12C996C0600151637 /* PrintSettingsView.swift in Sources */, + FF4CBFE22C996C0600151637 /* TournamentMatchFormatsSettingsView.swift in Sources */, + FF4CBFE32C996C0600151637 /* DateUpdateManagerView.swift in Sources */, + FF4CBFE42C996C0600151637 /* MatchTypeSmallSelectionView.swift in Sources */, + FF4CBFE52C996C0600151637 /* MonthData.swift in Sources */, + FF4CBFE62C996C0600151637 /* MenuWarningView.swift in Sources */, + FF4CBFE72C996C0600151637 /* TournamentBuildView.swift in Sources */, + FF4CBFE82C996C0600151637 /* TeamPickerView.swift in Sources */, + FF4CBFE92C996C0600151637 /* CloudConvert.swift in Sources */, + FF4CBFEA2C996C0600151637 /* EventTournamentsView.swift in Sources */, + FF4CBFEB2C996C0600151637 /* DisplayContext.swift in Sources */, + FF4CBFEC2C996C0600151637 /* TournamentCallView.swift in Sources */, + FF4CBFED2C996C0600151637 /* LoserRoundsView.swift in Sources */, + FF4CBFEE2C996C0600151637 /* GroupStagesView.swift in Sources */, + FF4CBFEF2C996C0600151637 /* PadelClubView.swift in Sources */, + FF4CBFF02C996C0600151637 /* URLs.swift in Sources */, + FF4CBFF12C996C0600151637 /* MatchDescriptor.swift in Sources */, + FF4CBFF22C996C0600151637 /* TournamentFormatSelectionView.swift in Sources */, + FF4CBFF32C996C0600151637 /* MatchListView.swift in Sources */, + FF4CBFF42C996C0600151637 /* PadelClubApp.swift in Sources */, + FF4CBFF52C996C0600151637 /* TournamentSettingsView.swift in Sources */, + FF4CBFF62C996C0600151637 /* String+Crypto.swift in Sources */, + FF4CBFF72C996C0600151637 /* GroupStageTeamReplacementView.swift in Sources */, + FF4CBFF82C996C0600151637 /* TabItemModifier.swift in Sources */, + FF4CBFF92C996C0600151637 /* DeferredViewModifier.swift in Sources */, + FF4CBFFA2C996C0600151637 /* TournamentScheduleView.swift in Sources */, + FF4CBFFB2C996C0600151637 /* MatchFormatStorageView.swift in Sources */, + FF4CBFFC2C996C0600151637 /* UmpireView.swift in Sources */, + FF4CBFFD2C996C0600151637 /* User.swift in Sources */, + FF4CBFFE2C996C0600151637 /* MatchSummaryView.swift in Sources */, + FF4CBFFF2C996C0600151637 /* TournamentDurationManagerView.swift in Sources */, + FF4CC0002C996C0600151637 /* MockData.swift in Sources */, + FF4CC0012C996C0600151637 /* TeamDetailView.swift in Sources */, + FF4CC0022C996C0600151637 /* GroupStagesSettingsView.swift in Sources */, + FF4CC0032C996C0600151637 /* TournamentFilterView.swift in Sources */, + FF4CC0042C996C0600151637 /* HtmlGenerator.swift in Sources */, + FF4CC0052C996C0600151637 /* PadelRule.swift in Sources */, + FF4CC0062C996C0600151637 /* TeamRegistration.swift in Sources */, + FF4CC0072C996C0600151637 /* Date+Extensions.swift in Sources */, + FF4CC0082C996C0600151637 /* GroupStageView.swift in Sources */, + FF4CC0092C996C0600151637 /* Tips.swift in Sources */, + FF4CC00A2C996C0600151637 /* MainView.swift in Sources */, + FF4CC00B2C996C0600151637 /* MatchDateView.swift in Sources */, + FF4CC00C2C996C0600151637 /* PlanningSettingsView.swift in Sources */, + FF4CC00D2C996C0600151637 /* MatchScheduleEditorView.swift in Sources */, + FF4CC00E2C996C0600151637 /* FortuneWheelView.swift in Sources */, + FF4CC00F2C996C0600151637 /* URL+Extensions.swift in Sources */, + FF4CC0102C996C0600151637 /* LoadingViewModifier.swift in Sources */, + FF4CC0112C996C0600151637 /* PlayerView.swift in Sources */, + FF4CC0122C996C0600151637 /* MatchDetailView.swift in Sources */, + FF4CC0132C996C0600151637 /* ExportFormat.swift in Sources */, + FF4CC0142C996C0600151637 /* PlayerBlockView.swift in Sources */, + FF4CC0152C996C0600151637 /* StoreItem.swift in Sources */, + FF4CC0162C996C0600151637 /* Selectable.swift in Sources */, + FF4CC0172C996C0600151637 /* PointView.swift in Sources */, + FF4CC0182C996C0600151637 /* ClubHolder.swift in Sources */, + FF4CC0192C996C0600151637 /* EventSettingsView.swift in Sources */, + FF4CC01A2C996C0600151637 /* InscriptionInfoView.swift in Sources */, + FF4CC01B2C996C0600151637 /* SelectablePlayerListView.swift in Sources */, + FF4CC01C2C996C0600151637 /* MatchFormatPickerView.swift in Sources */, + FF4CC01D2C996C0600151637 /* TournamentRankView.swift in Sources */, + FF4CC01E2C996C0600151637 /* NumberFormatter+Extensions.swift in Sources */, + FF4CC01F2C996C0600151637 /* SetLabelView.swift in Sources */, + FF4CC0202C996C0600151637 /* DebugSettingsView.swift in Sources */, + FF4CC0212C996C0600151637 /* GroupStageScheduleEditorView.swift in Sources */, + FF4CC0222C996C0600151637 /* EventView.swift in Sources */, + FF4CC0232C996C0600151637 /* LoginView.swift in Sources */, + FF4CC0242C996C0600151637 /* Court.swift in Sources */, + FF4CC0252C996C0600151637 /* Labels.swift in Sources */, + FF4CC0262C996C0600151637 /* CopyPasteButtonView.swift in Sources */, + FF4CC0272C996C0600151637 /* PlayerSexPickerView.swift in Sources */, + FF4CC0282C996C0600151637 /* TournamentInscriptionView.swift in Sources */, + FF4CC0292C996C0600151637 /* CallSettingsView.swift in Sources */, + FF4CC02A2C996C0600151637 /* FooterButtonView.swift in Sources */, + FF4CC02B2C996C0600151637 /* RankCalculatorView.swift in Sources */, + FF4CC02C2C996C0600151637 /* DateBoxView.swift in Sources */, + FF4CC02D2C996C0600151637 /* LearnMoreSheetView.swift in Sources */, + FF4CC02E2C996C0600151637 /* SourceFileManager.swift in Sources */, + FF4CC02F2C996C0600151637 /* PListReader.swift in Sources */, + FF4CC0302C996C0600151637 /* UpdateSourceRankDateView.swift in Sources */, + FF4CC0312C996C0600151637 /* GlobalSettingsView.swift in Sources */, + FF4CC0322C996C0600151637 /* Guard.swift in Sources */, + FF4CC0332C996C0600151637 /* PurchaseListView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; FF70FAC22C90584900129CC2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2552,7 +3132,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -2562,6 +3142,7 @@ INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; + INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi"; INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -2574,13 +3155,14 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.9; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2594,7 +3176,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -2603,6 +3185,7 @@ INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; + INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi"; INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -2615,13 +3198,14 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.9; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2699,6 +3283,90 @@ }; name = Release; }; + FF4CC04E2C996C0600151637 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 3; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; + DEVELOPMENT_TEAM = BQ3Y44M3Q6; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PadelClub/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (ProdTest)"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; + INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; + INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 17.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.9; + PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FF4CC04F2C996C0600151637 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 3; + DEFINES_MODULE = YES; + DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; + DEVELOPMENT_TEAM = BQ3Y44M3Q6; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PadelClub/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (ProdTest)"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; + INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; + INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 17.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.9; + PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = PRODTEST; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; FF70FBCD2C90584900129CC2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2707,7 +3375,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -2729,7 +3397,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.9; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2749,7 +3417,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -2770,7 +3438,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.8; + MARKETING_VERSION = 1.0.9; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2822,6 +3490,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + FF4CC04D2C996C0600151637 /* Build configuration list for PBXNativeTarget "ProdTest PadelClub" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FF4CC04E2C996C0600151637 /* Debug */, + FF4CC04F2C996C0600151637 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; FF70FBCC2C90584900129CC2 /* Build configuration list for PBXNativeTarget "PadelClub TestFlight" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2842,6 +3519,22 @@ minimumVersion = 1.2.0; }; }; + FF4CBF402C996C0600151637 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-algorithms.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.2.0; + }; + }; + FF4CBF422C996C0600151637 /* XCRemoteSwiftPackageReference "Zip" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/marmelroy/Zip"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.1.2; + }; + }; FF70FABF2C90584900129CC2 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/apple/swift-algorithms.git"; @@ -2869,6 +3562,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + FF4CBF3F2C996C0600151637 /* Algorithms */ = { + isa = XCSwiftPackageProductDependency; + package = FF4CBF402C996C0600151637 /* XCRemoteSwiftPackageReference "swift-algorithms" */; + productName = Algorithms; + }; + FF4CBF412C996C0600151637 /* Zip */ = { + isa = XCSwiftPackageProductDependency; + package = FF4CBF422C996C0600151637 /* XCRemoteSwiftPackageReference "Zip" */; + productName = Zip; + }; FF70FABE2C90584900129CC2 /* Algorithms */ = { isa = XCSwiftPackageProductDependency; package = FF70FABF2C90584900129CC2 /* XCRemoteSwiftPackageReference "swift-algorithms" */; diff --git a/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub ProdTest.xcscheme b/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub ProdTest.xcscheme new file mode 100644 index 0000000..a9ce218 --- /dev/null +++ b/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub ProdTest.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PadelClub/Data/AppSettings.swift b/PadelClub/Data/AppSettings.swift index 6f08933..60be3e3 100644 --- a/PadelClub/Data/AppSettings.swift +++ b/PadelClub/Data/AppSettings.swift @@ -16,8 +16,48 @@ final class AppSettings: MicroStorable { var didCreateAccount: Bool = false var didRegisterAccount: Bool = false + //search tournament stuff + var tournamentCategories: Set + var tournamentLevels: Set + var tournamentAges: Set + var tournamentTypes: Set + var startDate: Date + var endDate: Date + var city: String + var distance: Double + var sortingOption: String + var nationalCup: Bool + var dayDuration: Int? + var dayPeriod: DayPeriod + + func resetSearch() { + tournamentAges = Set() + tournamentTypes = Set() + tournamentLevels = Set() + tournamentCategories = Set() + city = "" + distance = 30 + startDate = Date() + endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! + sortingOption = "dateDebut+asc" + nationalCup = false + dayDuration = nil + dayPeriod = .all + } + required init() { - + tournamentAges = Set() + tournamentTypes = Set() + tournamentLevels = Set() + tournamentCategories = Set() + city = "" + distance = 30 + startDate = Date() + endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! + sortingOption = "dateDebut+asc" + nationalCup = false + dayDuration = nil + dayPeriod = .all } required init(from decoder: Decoder) throws { @@ -25,11 +65,35 @@ final class AppSettings: MicroStorable { lastDataSource = try container.decodeIfPresent(String.self, forKey: ._lastDataSource) didCreateAccount = try container.decodeIfPresent(Bool.self, forKey: ._didCreateAccount) ?? false didRegisterAccount = try container.decodeIfPresent(Bool.self, forKey: ._didRegisterAccount) ?? false + tournamentCategories = try container.decodeIfPresent(Set.self, forKey: ._tournamentCategories) ?? Set() + tournamentLevels = try container.decodeIfPresent(Set.self, forKey: ._tournamentLevels) ?? Set() + tournamentAges = try container.decodeIfPresent(Set.self, forKey: ._tournamentAges) ?? Set() + tournamentTypes = try container.decodeIfPresent(Set.self, forKey: ._tournamentTypes) ?? Set() + startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? Date() + endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? Calendar.current.date(byAdding: .month, value: 3, to: Date())! + city = try container.decodeIfPresent(String.self, forKey: ._city) ?? "" + distance = try container.decodeIfPresent(Double.self, forKey: ._distance) ?? 30 + sortingOption = try container.decodeIfPresent(String.self, forKey: ._sortingOption) ?? "dateDebut+asc" + nationalCup = try container.decodeIfPresent(Bool.self, forKey: ._nationalCup) ?? false + dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) + dayPeriod = try container.decodeIfPresent(DayPeriod.self, forKey: ._dayPeriod) ?? .all } enum CodingKeys: String, CodingKey { case _lastDataSource = "lastDataSource" case _didCreateAccount = "didCreateAccount" case _didRegisterAccount = "didRegisterAccount" + case _tournamentCategories = "tournamentCategories" + case _tournamentLevels = "tournamentLevels" + case _tournamentAges = "tournamentAges" + case _tournamentTypes = "tournamentTypes" + case _startDate = "startDate" + case _endDate = "endDate" + case _city = "city" + case _distance = "distance" + case _sortingOption = "sortingOption" + case _nationalCup = "nationalCup" + case _dayDuration = "dayDuration" + case _dayPeriod = "dayPeriod" } } diff --git a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift index 1745db9..ab7d66f 100644 --- a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift +++ b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift @@ -70,6 +70,10 @@ extension ImportedPlayer: PlayerHolder { } } + func contains(_ searchField: String) -> Bool { + firstName?.localizedCaseInsensitiveContains(searchField) == true || lastName?.localizedCaseInsensitiveContains(searchField) == true + } + func hitForSearch(_ searchText: String) -> Int { var trimmedSearchText = searchText.lowercased().trimmingCharacters(in: .whitespaces).folding(options: .diacriticInsensitive, locale: .current) trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ") diff --git a/PadelClub/Data/Federal/FederalTournament.swift b/PadelClub/Data/Federal/FederalTournament.swift index a535417..63595b5 100644 --- a/PadelClub/Data/Federal/FederalTournament.swift +++ b/PadelClub/Data/Federal/FederalTournament.swift @@ -7,8 +7,8 @@ import Foundation import CoreLocation import LeStorage -enum DayPeriod: CaseIterable, Identifiable { - var id: Self { self } +enum DayPeriod: Int, CaseIterable, Identifiable, Codable { + var id: Int { self.rawValue } case all case weekend @@ -151,6 +151,19 @@ struct FederalTournament: Identifiable, Codable { [libelle, dateDebut?.formatted(date: .complete, time: .omitted)].compactMap({$0}).joined(separator: "\n") + "\n" } + var sharePartnerMessage: String { + ["Je nous ai inscris au tournoi suivant : ", + libelle, + dateDebut?.formatted(date: .complete, time: .omitted), + "message preparé par Padel Club", + URLs.appStore.rawValue + ].compactMap({$0}).joined(separator: "\n") + "\n" + } + + func calendarNoteMessage() -> String { + [jugeArbitre?.nom, jugeArbitre?.prenom, courrielEngagement, installation?.telephone].compactMap({$0}).joined(separator: "\n") + } + var japMessage: String { [nomClub, jugeArbitre?.nom, jugeArbitre?.prenom, courrielEngagement, installation?.telephone].compactMap({$0}).joined(separator: ";") } diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index 3e7c92c..6c71fe4 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -93,9 +93,12 @@ final class MatchScheduler : ModelObject, Storable { } @discardableResult - func updateGroupStageSchedule(tournament: Tournament) -> Date { + func updateGroupStageSchedule(tournament: Tournament, specificGroupStage: GroupStage? = nil) -> Date { let computedGroupStageChunkCount = groupStageChunkCount ?? tournament.getGroupStageChunkValue() - let groupStages: [GroupStage] = tournament.groupStages() + var groupStages: [GroupStage] = tournament.groupStages() + if let specificGroupStage { + groupStages = [specificGroupStage] + } let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount let matches = groupStages.flatMap { $0._matches() } diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index b53db2f..da256cf 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -160,8 +160,8 @@ final class PlayerRegistration: ModelObject, Storable { } func isSameAs(_ player: PlayerRegistration) -> Bool { - firstName.localizedCaseInsensitiveCompare(player.firstName) == .orderedSame && - lastName.localizedCaseInsensitiveCompare(player.lastName) == .orderedSame + firstName.trimmedMultiline.localizedCaseInsensitiveCompare(player.firstName.trimmedMultiline) == .orderedSame && + lastName.trimmedMultiline.localizedCaseInsensitiveCompare(player.lastName.trimmedMultiline) == .orderedSame } func tournament() -> Tournament? { diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 0cc15be..6f73258 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -23,14 +23,16 @@ final class Round: ModelObject, Storable { private(set) var format: MatchFormat? var startDate: Date? var groupStageLoserBracket: Bool = false - - internal init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil, startDate: Date? = nil, groupStageLoserBracket: Bool = false) { + var loserBracketMode: LoserBracketMode = .automatic + + internal init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil, startDate: Date? = nil, groupStageLoserBracket: Bool = false, loserBracketMode: LoserBracketMode = .automatic) { self.tournament = tournament self.index = index self.parent = parent self.format = matchFormat self.startDate = startDate self.groupStageLoserBracket = groupStageLoserBracket + self.loserBracketMode = loserBracketMode } // MARK: - Computed dependencies @@ -153,7 +155,12 @@ final class Round: ModelObject, Storable { let teamIds: [String] = self._matches().compactMap { $0.losingTeamId } return teamIds.compactMap { self.tournamentStore.teamRegistrations.findById($0) } } - + + func winners() -> [TeamRegistration] { + let teamIds: [String] = self._matches().compactMap { $0.winningTeamId } + return teamIds.compactMap { self.tournamentStore.teamRegistrations.findById($0) } + } + func teams() -> [TeamRegistration] { return playedMatches().flatMap({ $0.teams() }) } @@ -186,13 +193,13 @@ defer { case .two: if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 + 1 }) { return luckyLoser.team - } else if groupStageLoserBracket == false, let previousMatch = bottomPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { + } else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { if let teamId = previousMatch.winningTeamId { return self.tournamentStore.teamRegistrations.findById(teamId) } else if previousMatch.disabled { return previousMatch.teams().first } - } else if groupStageLoserBracket == false, let parent = upperBracketBottomMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId { + } else if let parent = upperBracketBottomMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId { return tournamentStore.findById(parent) } } @@ -208,8 +215,13 @@ defer { print("func upperBracketTopMatch", matchIndex, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) } #endif + let parentRound = parentRound + if let parentRound, parentRound.parent == nil, groupStageLoserBracket == false, parentRound.loserBracketMode != .automatic { + return nil + } + let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) - if isLoserBracket(), previousRound == nil, let parentRound = parentRound, let upperBracketTopMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2) { + if isLoserBracket(), previousRound == nil, let upperBracketTopMatch = parentRound?.getMatch(atMatchIndexInRound: indexInRound * 2) { return upperBracketTopMatch } return nil @@ -224,8 +236,13 @@ defer { } #endif + let parentRound = parentRound + if let parentRound, parentRound.parent == nil, groupStageLoserBracket == false, parentRound.loserBracketMode != .automatic { + return nil + } + let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) - if isLoserBracket(), previousRound == nil, let parentRound = parentRound, let upperBracketBottomMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2 + 1) { + if isLoserBracket(), previousRound == nil, let upperBracketBottomMatch = parentRound?.getMatch(atMatchIndexInRound: indexInRound * 2 + 1) { return upperBracketBottomMatch } return nil @@ -420,6 +437,10 @@ defer { return _matches().filter({ $0.disabled }) } + func allLoserRoundMatches() -> [Match] { + loserRoundsAndChildren().flatMap({ $0._matches() }) + } + var theoryCumulativeMatchCount: Int { var totalMatches = RoundRule.numberOfMatches(forRoundIndex: index) if let parentRound { @@ -576,7 +597,11 @@ defer { func deleteLoserBracket() { do { - try self.tournamentStore.rounds.delete(contentOfs: loserRounds()) + let loserRounds = loserRounds() + for loserRound in loserRounds { + try loserRound.deleteDependencies() + } + try self.tournamentStore.rounds.delete(contentOfs: loserRounds) } catch { Logger.error(error) } @@ -681,6 +706,7 @@ defer { case _format = "format" case _startDate = "startDate" case _groupStageLoserBracket = "groupStageLoserBracket" + case _loserBracketMode = "loserBracketMode" } required init(from decoder: Decoder) throws { @@ -692,6 +718,7 @@ defer { format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) groupStageLoserBracket = try container.decodeIfPresent(Bool.self, forKey: ._groupStageLoserBracket) ?? false + loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic } func encode(to encoder: Encoder) throws { @@ -701,7 +728,8 @@ defer { try container.encode(tournament, forKey: ._tournament) try container.encode(index, forKey: ._index) try container.encode(groupStageLoserBracket, forKey: ._groupStageLoserBracket) - + try container.encode(loserBracketMode, forKey: ._loserBracketMode) + if let parent = parent { try container.encode(parent, forKey: ._parent) } else { @@ -776,3 +804,29 @@ extension Round: Selectable, Equatable { return hasEnded() ? .checkmark : nil } } + + +enum LoserBracketMode: Int, CaseIterable, Identifiable, Codable { + var id: Int { self.rawValue } + + case automatic + case manual + + func localizedLoserBracketMode() -> String { + switch self { + case .automatic: + "Automatique" + case .manual: + "Manuelle" + } + } + + func localizedLoserBracketModeDescription() -> String { + switch self { + case .automatic: + "Les perdants du tableau principal sont placés à leur place prévue." + case .manual: + "Aucun placement automatique n'est fait. Vous devez choisir les perdants qui se jouent." + } + } +} diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 54632ac..6586c06 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -57,7 +57,8 @@ final class Tournament : ModelObject, Storable { var publishTournament: Bool = false var hidePointsEarned: Bool = false var publishRankings: Bool = false - + var loserBracketMode: LoserBracketMode = .automatic + @ObservationIgnored var navigationPath: [Screen] = [] @@ -105,9 +106,10 @@ final class Tournament : ModelObject, Storable { case _publishTournament = "publishTournament" case _hidePointsEarned = "hidePointsEarned" case _publishRankings = "publishRankings" + case _loserBracketMode = "loserBracketMode" } - internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false) { + internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic) { self.event = event self.name = name self.startDate = startDate @@ -145,6 +147,7 @@ final class Tournament : ModelObject, Storable { self.publishTournament = publishTournament self.hidePointsEarned = hidePointsEarned self.publishRankings = publishRankings + self.loserBracketMode = loserBracketMode } required init(from decoder: Decoder) throws { @@ -189,6 +192,7 @@ final class Tournament : ModelObject, Storable { publishTournament = try container.decodeIfPresent(Bool.self, forKey: ._publishTournament) ?? false hidePointsEarned = try container.decodeIfPresent(Bool.self, forKey: ._hidePointsEarned) ?? false publishRankings = try container.decodeIfPresent(Bool.self, forKey: ._publishRankings) ?? false + loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic } fileprivate static let _numberFormatter: NumberFormatter = NumberFormatter() @@ -1633,7 +1637,7 @@ defer { let roundCount = RoundRule.numberOfRounds(forTeams: bracketTeamCount()) let rounds = (0.. Tournament { @@ -2189,3 +2193,4 @@ extension Tournament { } } + diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift index d40e126..59e3e1f 100644 --- a/PadelClub/Data/User.swift +++ b/PadelClub/Data/User.swift @@ -43,16 +43,18 @@ class User: ModelObject, UserBase, Storable { var bracketMatchFormatPreference: MatchFormat? var groupStageMatchFormatPreference: MatchFormat? var loserBracketMatchFormatPreference: MatchFormat? - + var loserBracketMode: LoserBracketMode = .automatic + var deviceId: String? - init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?) { + init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?, loserBracketMode: LoserBracketMode = .automatic) { self.username = username self.firstName = firstName self.lastName = lastName self.email = email self.phone = phone self.country = country + self.loserBracketMode = loserBracketMode } public func uuid() throws -> UUID { @@ -139,8 +141,42 @@ class User: ModelObject, UserBase, Storable { case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" case _deviceId = "deviceId" + case _loserBracketMode = "loserBracketMode" } + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + // Required properties + id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + username = try container.decode(String.self, forKey: ._username) + email = try container.decode(String.self, forKey: ._email) + firstName = try container.decode(String.self, forKey: ._firstName) + lastName = try container.decode(String.self, forKey: ._lastName) + + // Optional properties + clubs = try container.decodeIfPresent([String].self, forKey: ._clubs) ?? [] + umpireCode = try container.decodeIfPresent(String.self, forKey: ._umpireCode) + licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) + phone = try container.decodeIfPresent(String.self, forKey: ._phone) + country = try container.decodeIfPresent(String.self, forKey: ._country) + + // Summons-related properties + summonsMessageBody = try container.decodeIfPresent(String.self, forKey: ._summonsMessageBody) + summonsMessageSignature = try container.decodeIfPresent(String.self, forKey: ._summonsMessageSignature) + summonsAvailablePaymentMethods = try container.decodeIfPresent(String.self, forKey: ._summonsAvailablePaymentMethods) + summonsDisplayFormat = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayFormat) ?? false + summonsDisplayEntryFee = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayEntryFee) ?? false + summonsUseFullCustomMessage = try container.decodeIfPresent(Bool.self, forKey: ._summonsUseFullCustomMessage) ?? false + + // Match-related properties + matchFormatsDefaultDuration = try container.decodeIfPresent([MatchFormat: Int].self, forKey: ._matchFormatsDefaultDuration) + bracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._bracketMatchFormatPreference) + groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) + loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) + loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic + } + func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -228,6 +264,7 @@ class User: ModelObject, UserBase, Storable { try container.encodeNil(forKey: ._deviceId) } + try container.encode(loserBracketMode, forKey: ._loserBracketMode) } static func placeHolder() -> User { diff --git a/PadelClub/PadelClubApp.swift b/PadelClub/PadelClubApp.swift index 73d63bd..befb5d3 100644 --- a/PadelClub/PadelClubApp.swift +++ b/PadelClub/PadelClubApp.swift @@ -52,6 +52,8 @@ struct PadelClubApp: App { return "\(version) (\(build)) Debug" #elseif TESTFLIGHT return "\(version) (\(build)) TestFlight" +#elseif PRODTEST + return "\(version) (\(build)) ProdTest" #else return "\(version) (\(build))" #endif @@ -84,6 +86,8 @@ struct PadelClubApp: App { print("Running in Debug mode") #elseif TESTFLIGHT print("Running in TestFlight mode") +#elseif PRODTEST +print("Running in ProdTest mode") #else print("Running in Release mode") #endif diff --git a/PadelClub/Utils/ContactManager.swift b/PadelClub/Utils/ContactManager.swift index 07b23a2..88898bf 100644 --- a/PadelClub/Utils/ContactManager.swift +++ b/PadelClub/Utils/ContactManager.swift @@ -15,6 +15,9 @@ enum ContactManagerError: LocalizedError { case mailNotSent //no network no error case messageFailed case messageNotSent //no network no error + case calendarAccessDenied + case calendarEventSaveFailed + case noCalendarAvailable } enum ContactType: Identifiable { diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index 954d632..16e0fba 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -17,6 +17,10 @@ enum URLs: String, Identifiable { case activationHost = "xlr.alwaysdata.net" case main = "https://xlr.alwaysdata.net/" case api = "https://xlr.alwaysdata.net/roads/" +#elseif PRODTEST + case activationHost = "padelclub.app" + case main = "https://padelclub.app/" + case api = "https://padelclub.app/roads/" #else case activationHost = "padelclub.app" case main = "https://padelclub.app/" diff --git a/PadelClub/Views/Club/ClubSearchView.swift b/PadelClub/Views/Club/ClubSearchView.swift index cda6c72..fc022f1 100644 --- a/PadelClub/Views/Club/ClubSearchView.swift +++ b/PadelClub/Views/Club/ClubSearchView.swift @@ -140,7 +140,6 @@ struct ClubSearchView: View { RowButtonView("D'accord") { locationManager.lastError = nil } - .padding(.horizontal) } } else if clubMarkers.isEmpty == false && searching == false && _filteredClubs().isEmpty { ContentUnavailableView.search(text: searchedCity) @@ -172,7 +171,6 @@ struct ClubSearchView: View { locationManager.requestLocation() } } - .padding(.horizontal) } if error != nil { @@ -184,13 +182,11 @@ struct ClubSearchView: View { RowButtonView("Chercher une ville ou un code postal") { searchPresented = true } - .padding(.horizontal) if searchAttempted { RowButtonView("Créer un club manuellement") { newClub = club ?? Club.newEmptyInstance() } - .padding(.horizontal) } } } diff --git a/PadelClub/Views/Club/ClubsView.swift b/PadelClub/Views/Club/ClubsView.swift index 041f4ca..b5ea93e 100644 --- a/PadelClub/Views/Club/ClubsView.swift +++ b/PadelClub/Views/Club/ClubsView.swift @@ -159,11 +159,9 @@ struct ClubsView: View { RowButtonView("Créer un nouveau club", systemImage: "plus.circle.fill") { newClub = Club.newEmptyInstance() } - .padding(.horizontal) RowButtonView("Chercher un club", systemImage: "magnifyingglass.circle.fill") { presentClubSearchView = true } - .padding(.horizontal) } } diff --git a/PadelClub/Views/Components/Labels.swift b/PadelClub/Views/Components/Labels.swift index 5d2ed00..7049334 100644 --- a/PadelClub/Views/Components/Labels.swift +++ b/PadelClub/Views/Components/Labels.swift @@ -21,7 +21,7 @@ struct LabelStructure: View { struct LabelSettings: View { var body: some View { - Label("Réglages", systemImage: "slider.horizontal.3").labelStyle(.titleOnly) + Label("Réglages du tournoi", systemImage: "slider.horizontal.3").labelStyle(.titleOnly) } } diff --git a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift index 08efa0e..776e75f 100644 --- a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift +++ b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift @@ -81,7 +81,6 @@ struct LoserBracketFromGroupStageView: View { isEditingLoserBracketGroupStage = true _addNewMatch() } - .padding(.horizontal) } } } diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 1998374..094dc80 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -63,7 +63,7 @@ struct MatchSetupView: View { } HStack { let luckyLosers = walkOutSpot ? match.luckyLosers() : [] - TeamPickerView(shouldConfirm: shouldConfirm, groupStagePosition: nil, matchTypeContext: matchTypeContext, luckyLosers: luckyLosers, teamPicked: { team in + TeamPickerView(shouldConfirm: shouldConfirm, round: match.roundObject, matchTypeContext: matchTypeContext, luckyLosers: luckyLosers, teamPicked: { team in print(team.pasteData()) if walkOutSpot || team.bracketPosition != nil || matchTypeContext == .loserBracket { match.setLuckyLoser(team: team, teamPosition: teamPosition) @@ -86,6 +86,7 @@ struct MatchSetupView: View { } } }) + .environment(match) if matchTypeContext == .bracket, let tournament = match.currentTournament() { let availableQualifiedTeams = tournament.availableQualifiedTeams() let availableSeedGroups = tournament.availableSeedGroups() diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 596b360..b7a243f 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -122,7 +122,6 @@ struct ActivityView: View { RowButtonView("D'accord.") { self.error = nil } - .padding(.horizontal) } } else if isGatheringFederalTournaments { ProgressView() @@ -139,11 +138,9 @@ struct ActivityView: View { FooterButtonView("supprimer vos filtres") { federalDataViewModel.removeFilters() } - .padding(.horizontal) FooterButtonView("modifier vos filtres") { presentFilterView = true } - .padding(.horizontal) } } else { _dataEmptyView() @@ -153,8 +150,8 @@ struct ActivityView: View { } } //.searchable(text: $searchText) - .onAppear { presentToolbar = true } - .onDisappear { presentToolbar = false } +// .onAppear { presentToolbar = true } +// .onDisappear { presentToolbar = false } .refreshable { if navigation.agendaDestination == .tenup { federalDataViewModel.federalTournaments.removeAll() @@ -234,7 +231,7 @@ struct ActivityView: View { } } - if presentToolbar, tournaments.isEmpty == false, federalDataViewModel.areFiltersEnabled() || navigation.agendaDestination == .around { + if tournaments.isEmpty == false, federalDataViewModel.areFiltersEnabled() || navigation.agendaDestination == .around { ToolbarItemGroup(placement: .bottomBar) { VStack(spacing: 0) { let searchStatus = _searchStatus() @@ -403,13 +400,10 @@ struct ActivityView: View { RowButtonView("Créer un nouvel événement") { newTournament = Tournament.newEmptyInstance() } - .padding(.horizontal) RowButtonView("Importer via Tenup") { navigation.agendaDestination = .tenup } - .padding(.horizontal) SupportButtonView(contentIsUnavailable: true) - .padding(.horizontal) } } diff --git a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift index d4e8ca7..2f64f44 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift @@ -10,6 +10,7 @@ import CoreLocation import CoreLocationUI struct TournamentLookUpView: View { + @EnvironmentObject var dataStore: DataStore @Environment(FederalDataViewModel.self) var federalDataViewModel: FederalDataViewModel @StateObject var locationManager = LocationManager() @Environment(\.dismiss) private var dismiss @@ -18,19 +19,8 @@ struct TournamentLookUpView: View { @State var page: Int = 0 @State var total: Int = 0 - @State private var tournamentCategories = Set() - @State private var tournamentLevels = Set() - @State private var tournamentAges = Set() - @State private var tournamentTypes = Set() @State private var searching: Bool = false - @State private var startDate: Date = Date() - @State private var endDate: Date = Calendar.current.date(byAdding: .month, value: 3, to: Date())! - @AppStorage("lastCity") private var city: String = "" - @State private var ligue: String = "" - @State private var distance: Double = 30 - @State private var sortingOption: String = "dateDebut+asc" @State private var requestedToGetAllPages: Bool = false - @State private var nationalCup: Bool = false @State private var revealSearchParameters: Bool = true @State private var presentAlert: Bool = false @@ -61,14 +51,18 @@ struct TournamentLookUpView: View { presentAlert = false } }, message: { - Text("Il y a beacoup de tournois pour cette requête, êtes-vous sûr de vouloir tout récupérer ? Sinon essayez d'affiner votre recherche.") + if dataStore.appSettings.city.isEmpty { + Text("Il est préférable de se localiser ou d'indiquer une ville pour réduire le nombre de résultat.") + } else { + Text("Il y a beacoup de tournois pour cette requête, êtes-vous sûr de vouloir tout récupérer ? Sinon essayez d'affiner votre recherche.") + } }) .toolbarBackground(.visible, for: .bottomBar, .navigationBar) .navigationTitle("Chercher un tournoi") .navigationBarTitleDisplayMode(.inline) .onChange(of: locationManager.city) { - if let newValue = locationManager.city, city.isEmpty { - city = newValue + if let newValue = locationManager.city, dataStore.appSettings.city.isEmpty { + dataStore.appSettings.city = newValue } } .toolbarTitleDisplayMode(.large) @@ -113,15 +107,9 @@ struct TournamentLookUpView: View { #endif Button(role: .destructive) { - tournamentLevels = Set() - tournamentCategories = Set() - city = "" + dataStore.appSettings.resetSearch() locationManager.location = nil locationManager.city = nil - distance = 30 - startDate = Date() - endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! - sortingOption = "dateDebut+asc" revealSearchParameters = true federalDataViewModel.searchedFederalTournaments = [] federalDataViewModel.searchAttemptCount = 0 @@ -151,6 +139,7 @@ struct TournamentLookUpView: View { } private func runSearch() { + dataStore.appSettingsStorage.write() revealSearchParameters = false total = 0 page = 0 @@ -158,6 +147,9 @@ struct TournamentLookUpView: View { searching = true requestedToGetAllPages = false federalDataViewModel.searchAttemptCount += 1 + federalDataViewModel.dayPeriod = dataStore.appSettings.dayPeriod + federalDataViewModel.dayDuration = dataStore.appSettings.dayDuration + Task { await getNewPage() searching = false @@ -170,7 +162,7 @@ struct TournamentLookUpView: View { } private var distanceLimit: Measurement { - distanceLimit(distance: distance) + distanceLimit(distance: dataStore.appSettings.distance) } private func distanceLimit(distance: Double) -> Measurement { @@ -178,19 +170,19 @@ struct TournamentLookUpView: View { } private var categories: [TournamentCategory] { - tournamentCategories.compactMap { TournamentCategory(rawValue: $0) } + dataStore.appSettings.tournamentCategories.compactMap { TournamentCategory(rawValue: $0) } } private var levels: [TournamentLevel] { - tournamentLevels.compactMap { TournamentLevel(rawValue: $0) } + dataStore.appSettings.tournamentLevels.compactMap { TournamentLevel(rawValue: $0) } } private var ages: [FederalTournamentAge] { - tournamentAges.compactMap { FederalTournamentAge(rawValue: $0) } + dataStore.appSettings.tournamentAges.compactMap { FederalTournamentAge(rawValue: $0) } } private var types: [FederalTournamentType] { - tournamentTypes.compactMap { FederalTournamentType(rawValue: $0) } + dataStore.appSettings.tournamentTypes.compactMap { FederalTournamentType(rawValue: $0) } } func getNewPage() async { @@ -198,7 +190,7 @@ struct TournamentLookUpView: View { if NetworkFederalService.shared.formId.isEmpty { await getNewBuildForm() } else { - let commands = try await NetworkFederalService.shared.getAllFederalTournaments(sortingOption: sortingOption, page: page, startDate: startDate, endDate: endDate, city: city, distance: distance, categories: categories, levels: levels, lat: locationManager.location?.coordinate.latitude.formatted(.number.locale(Locale(identifier: "us"))), lng: locationManager.location?.coordinate.longitude.formatted(.number.locale(Locale(identifier: "us"))), ages: ages, types: types, nationalCup: nationalCup) + let commands = try await NetworkFederalService.shared.getAllFederalTournaments(sortingOption: dataStore.appSettings.sortingOption, page: page, startDate: dataStore.appSettings.startDate, endDate: dataStore.appSettings.endDate, city: dataStore.appSettings.city, distance: dataStore.appSettings.distance, categories: categories, levels: levels, lat: locationManager.location?.coordinate.latitude.formatted(.number.locale(Locale(identifier: "us"))), lng: locationManager.location?.coordinate.longitude.formatted(.number.locale(Locale(identifier: "us"))), ages: ages, types: types, nationalCup: dataStore.appSettings.nationalCup) let resultCommand = commands.first(where: { $0.results != nil }) if let newTournaments = resultCommand?.results?.items { newTournaments.forEach { ft in @@ -253,15 +245,9 @@ struct TournamentLookUpView: View { } } Button { - tournamentLevels = Set() - tournamentCategories = Set() - city = "" + dataStore.appSettings.resetSearch() locationManager.location = nil locationManager.city = nil - distance = 30 - startDate = Date() - endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! - sortingOption = "dateDebut+asc" revealSearchParameters = true } label: { Label("Ré-initialiser la recherche", systemImage: "xmark.circle") @@ -271,12 +257,12 @@ struct TournamentLookUpView: View { @ViewBuilder var searchParametersView: some View { - @Bindable var federalDataViewModel = federalDataViewModel + @Bindable var appSettings = dataStore.appSettings Section { - DatePicker("Début", selection: $startDate, displayedComponents: .date) - DatePicker("Fin", selection: $endDate, displayedComponents: .date) - Picker(selection: $federalDataViewModel.dayDuration) { - Text("aucune").tag(nil as Int?) + DatePicker("Début", selection: $appSettings.startDate, displayedComponents: .date) + DatePicker("Fin", selection: $appSettings.endDate, displayedComponents: .date) + Picker(selection: $appSettings.dayDuration) { + Text("Aucune").tag(nil as Int?) Text(1.formatted()).tag(1 as Int?) Text(2.formatted()).tag(2 as Int?) Text(3.formatted()).tag(3 as Int?) @@ -284,16 +270,16 @@ struct TournamentLookUpView: View { Text("Durée max (en jours)") } - Picker(selection: $federalDataViewModel.dayPeriod) { + Picker(selection: $appSettings.dayPeriod) { ForEach(DayPeriod.allCases) { - Text($0.localizedDayPeriodLabel()).tag($0) + Text($0.localizedDayPeriodLabel().capitalized).tag($0) } } label: { Text("En semaine ou week-end") } HStack { - TextField("Ville", text: $city) + TextField("Ville", text: $appSettings.city) if let city = locationManager.city { Divider() Text(city).italic() @@ -311,7 +297,7 @@ struct TournamentLookUpView: View { } } - Picker(selection: $distance) { + Picker(selection: $appSettings.distance) { Text(distanceLimit(distance:30).formatted()).tag(30.0) Text(distanceLimit(distance:50).formatted()).tag(50.0) Text(distanceLimit(distance:60).formatted()).tag(60.0) @@ -324,7 +310,7 @@ struct TournamentLookUpView: View { Text("Distance max") } - Picker(selection: $sortingOption) { + Picker(selection: $appSettings.sortingOption) { Text("Distance").tag("_DIST_") Text("Date de début").tag("dateDebut+asc") Text("Date de fin").tag("dateFin+asc") @@ -333,7 +319,7 @@ struct TournamentLookUpView: View { } NavigationLink { - List(TournamentCategory.allCases, selection: $tournamentCategories) { type in + List([TournamentCategory.men, TournamentCategory.women, TournamentCategory.mix], selection: $appSettings.tournamentCategories) { type in Text(type.localizedLabel()) } .navigationTitle("Catégories") @@ -348,7 +334,7 @@ struct TournamentLookUpView: View { } NavigationLink { - List(TournamentLevel.allCases, selection: $tournamentLevels) { type in + List([TournamentLevel.p25, TournamentLevel.p100, TournamentLevel.p250, TournamentLevel.p500, TournamentLevel.p1000, TournamentLevel.p1500, TournamentLevel.p2000], selection: $appSettings.tournamentLevels) { type in Text(type.localizedLabel()) } .navigationTitle("Niveaux") @@ -363,7 +349,7 @@ struct TournamentLookUpView: View { } NavigationLink { - List(FederalTournamentAge.allCases, selection: $tournamentAges) { type in + List([FederalTournamentAge.senior, FederalTournamentAge.a45, FederalTournamentAge.a55, FederalTournamentAge.a17_18, FederalTournamentAge.a15_16, FederalTournamentAge.a13_14, FederalTournamentAge.a11_12], selection: $appSettings.tournamentAges) { type in Text(type.localizedLabel()) } .navigationTitle("Limites d'âge") @@ -372,7 +358,7 @@ struct TournamentLookUpView: View { HStack { Text("Limite d'âge") Spacer() - if tournamentAges.isEmpty || tournamentAges.count == FederalTournamentAge.allCases.count { + if dataStore.appSettings.tournamentAges.isEmpty || dataStore.appSettings.tournamentAges.count == FederalTournamentAge.allCases.count { Text("Tous les âges") .foregroundStyle(.secondary) } else { @@ -383,7 +369,7 @@ struct TournamentLookUpView: View { } NavigationLink { - List(FederalTournamentType.allCases, selection: $tournamentTypes) { type in + List(FederalTournamentType.allCases, selection: $appSettings.tournamentTypes) { type in Text(type.localizedLabel()) } .navigationTitle("Types de tournoi") @@ -392,7 +378,7 @@ struct TournamentLookUpView: View { HStack { Text("Type de tournoi") Spacer() - if tournamentTypes.isEmpty || tournamentTypes.count == FederalTournamentType.allCases.count { + if dataStore.appSettings.tournamentTypes.isEmpty || dataStore.appSettings.tournamentTypes.count == FederalTournamentType.allCases.count { Text("Tous les types") .foregroundStyle(.secondary) } else { @@ -402,7 +388,7 @@ struct TournamentLookUpView: View { } } - Picker(selection: $nationalCup) { + Picker(selection: $appSettings.nationalCup) { Text("N'importe").tag(false) Text("Uniquement").tag(true) } label: { @@ -416,7 +402,7 @@ struct TournamentLookUpView: View { } var categoriesLabel: some View { - if tournamentCategories.isEmpty || tournamentCategories.count == TournamentCategory.allCases.count { + if dataStore.appSettings.tournamentCategories.isEmpty || dataStore.appSettings.tournamentCategories.count == TournamentCategory.allCases.count { Text("Toutes les catégories") } else { Text(categories.map({ $0.localizedLabel() }).joined(separator: ", ")) @@ -424,7 +410,7 @@ struct TournamentLookUpView: View { } var levelsLabel: some View { - if tournamentLevels.isEmpty || tournamentLevels.count == TournamentLevel.allCases.count { + if dataStore.appSettings.tournamentLevels.isEmpty || dataStore.appSettings.tournamentLevels.count == TournamentLevel.allCases.count { Text("Tous les niveaux") } else { Text(levels.map({ $0.localizedLabel() }).joined(separator: ", ")) @@ -437,8 +423,8 @@ struct TournamentLookUpView: View { HStack { Text("Lieu") Spacer() - Text(city) - if distance >= 3000 { + Text(dataStore.appSettings.city) + if dataStore.appSettings.distance >= 3000 { Text("sans limite de distance") } else { Text("à moins de " + distanceLimit.formatted()) @@ -448,9 +434,9 @@ struct TournamentLookUpView: View { Text("Période") Spacer() Text("Du") - Text(startDate.twoDigitsYearFormatted) + Text(dataStore.appSettings.startDate.twoDigitsYearFormatted) Text("Au") - Text(endDate.twoDigitsYearFormatted) + Text(dataStore.appSettings.endDate.twoDigitsYearFormatted) } HStack { Text("Niveau") @@ -472,7 +458,7 @@ struct TournamentLookUpView: View { } var sortingOptionLabel: String { - switch sortingOption { + switch dataStore.appSettings.sortingOption { case "_DIST_": return "Distance" case "dateDebut+asc": return "Date de début" case "dateFin+asc": return "Date de fin" diff --git a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift index 41e586e..9fc0961 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import EventKit struct TournamentSubscriptionView: View { @EnvironmentObject var networkMonitor: NetworkMonitor @@ -17,6 +18,8 @@ struct TournamentSubscriptionView: View { @State private var selectedPlayers: [ImportedPlayer] @State private var contactType: ContactType? = nil @State private var sentError: ContactManagerError? = nil + @State private var didSendMessage: Bool = false + @State private var didSaveInCalendar: Bool = false init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: User) { self.federalTournament = federalTournament @@ -25,6 +28,64 @@ struct TournamentSubscriptionView: View { _selectedPlayers = .init(wrappedValue: [user.currentPlayerData()].compactMap({ $0 })) } + func hasPartner() -> Bool { + selectedPlayers.count == 2 + } + + func inscriptionSent() -> Bool { + didSendMessage + } + + func addEvent() { + let eventStore = EKEventStore() + + eventStore.requestWriteOnlyAccessToEvents { (granted, error) in + if granted && error == nil { + print("Access granted") + let startDate = federalTournament.startDate + let endDate = federalTournament.dateFin ?? federalTournament.startDate.endOfDay() + addEventToCalendar(title: messageSubject, startDate: startDate, endDate: endDate) + didSaveInCalendar = true + } else { + print("Access denied or error occurred: \(String(describing: error?.localizedDescription))") + sentError = .calendarAccessDenied + } + } + + } + + func addEventToCalendar(title: String, startDate: Date, endDate: Date) { + let eventStore = EKEventStore() + if eventStore.defaultCalendarForNewEvents == nil { + sentError = .noCalendarAvailable + return + } + eventStore.requestWriteOnlyAccessToEvents { (granted, error) in + if granted && error == nil { + let event = EKEvent(eventStore: eventStore) + event.title = title + event.isAllDay = true + event.startDate = startDate + event.endDate = endDate + event.calendar = eventStore.defaultCalendarForNewEvents + event.notes = noteCalendar + event.location = federalTournament.clubLabel() + do { + try eventStore.save(event, span: .thisEvent) + didSaveInCalendar = true + print("Event saved") + } catch let error { + print("Failed to save event: \(error.localizedDescription)") + sentError = .calendarEventSaveFailed + } + } else { + print("Access denied or error occurred: \(String(describing: error?.localizedDescription))") + sentError = .calendarAccessDenied + } + } + } + + var body: some View { List { Section { @@ -86,54 +147,91 @@ struct TournamentSubscriptionView: View { } } - if let courrielEngagement = federalTournament.courrielEngagement { - Section { - RowButtonView("S'inscrire par email") { - contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: messageBody, subject: messageSubject, tournamentBuild: build as? TournamentBuild) - } - } + Section { + Text(messageBody) + } header: { + Text("Message preparé par Padel Club") + } footer: { + CopyPasteButtonView(pasteValue: messageBody) } - - if let installation = federalTournament.installation, let telephone = installation.telephone { - if telephone.isMobileNumber() { - Section { - RowButtonView("S'inscrire par message") { - contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild) + } + .toolbarBackground(.visible, for: .bottomBar) + .toolbarBackground(.visible, for: .navigationBar) + .overlay(alignment: .bottom) { + if didSaveInCalendar { + Label("Ajouté dans votre calendrier par défaut", systemImage: "checkmark") + .toastFormatted() + .deferredRendering(for: .seconds(3)) + } + } + .toolbar(content: { + ToolbarItem(placement: .status) { + Menu { + if let courrielEngagement = federalTournament.courrielEngagement { + Section { + RowButtonView("S'inscrire par email", systemImage: "envelope") { + contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: messageBody, subject: messageSubject, tournamentBuild: build as? TournamentBuild) + } } } - } - let number = telephone.replacingOccurrences(of: " ", with: "") - if let url = URL(string: "tel:\(number)") { - Link(destination: url) { - Label("Appeler", systemImage: "phone") + + if let installation = federalTournament.installation, let telephone = installation.telephone { + if telephone.isMobileNumber() { + Section { + RowButtonView("S'inscrire par message", systemImage: "message") { + contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild) + } + } + } + let number = telephone.replacingOccurrences(of: " ", with: "") + if let url = URL(string: "tel:\(number)") { + Link(destination: url) { + Label("Appeler", systemImage: "phone") + } + } } + } label: { + Text("Contact et inscription") } - - Section { - Text(messageBody) - } header: { - Text("Message preparé par Padel Club") - } footer: { - CopyPasteButtonView(pasteValue: messageBody) - } + .menuStyle(.button) + .buttonStyle(.borderedProminent) + .offset(y:-2) } - } - .toolbar(content: { - Menu { - Link(destination: URL(string:"https://tenup.fft.fr/tournoi/\(federalTournament.id)")!) { - Label("Voir sur Tenup", systemImage: "tennisball") - } - ShareLink(item: federalTournament.shareMessage) { - Label("Partager les infos", systemImage: "info") + ToolbarItem(placement: .topBarTrailing) { + Menu { + ShareLink(item: federalTournament.sharePartnerMessage) { + Label("Prévenir votre partenaire", systemImage: "person.2") + } + + Button("Ajouter à votre agenda") { + addEvent() + } + + ShareLink(item: federalTournament.shareMessage) { + Label("Partager les infos", systemImage: "info") + } + Link(destination: URL(string:"https://tenup.fft.fr/tournoi/\(federalTournament.id)")!) { + Label("Voir sur Tenup", systemImage: "tennisball") + } + ShareLink(item: federalTournament.shareMessage) { + Label("Partager les infos", systemImage: "info") + } + } label: { + LabelOptions() } - } label: { - LabelOptions() } }) .alert("Un problème est survenu", isPresented: messageSentFailed) { Button("OK") { } + + if sentError == .calendarAccessDenied || sentError == .noCalendarAvailable { + Button("Voir vos réglages") { + openAppSettings() + } + } + } message: { Text(_networkErrorMessage) } @@ -150,6 +248,8 @@ struct TournamentSubscriptionView: View { case .sent: if networkMonitor.connected == false { self.sentError = .messageNotSent + } else { + self.didSendMessage = true } @unknown default: break @@ -167,6 +267,8 @@ struct TournamentSubscriptionView: View { if networkMonitor.connected == false { self.contactType = nil self.sentError = .mailNotSent + } else { + self.didSendMessage = true } @unknown default: break @@ -188,12 +290,19 @@ struct TournamentSubscriptionView: View { var messageBody: String { let bonjourOuBonsoir = Date().timeOfDay.hello let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye - let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !\nVotre tournoi n'est pas encore dessus ? \(URLs.main.rawValue)", "Téléchargez l'app : \(URLs.appStore.rawValue)", "En savoir plus : \(URLs.appDescription.rawValue)"].compactMap { $0 }.joined(separator: "\n") + "\n" + let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !"].compactMap { $0 }.joined(separator: "\n") + "\n" return body } var messageBodyShort: String { - let body = [[build.buildHolderTitle(), federalTournament.clubLabel()].compacted().joined(separator: " "), federalTournament.computedStartDate, teamsString].compacted().joined(separator: "\n") + "\n" + let bonjourOuBonsoir = Date().timeOfDay.hello + let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye + let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee].compactMap { $0 }.joined(separator: "\n") + "\n" + return body + } + + var noteCalendar: String { + let body = [[build.buildHolderTitle(), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, federalTournament.calendarNoteMessage()].compactMap { $0 }.joined(separator: "\n") + "\n" return body } @@ -227,6 +336,29 @@ struct TournamentSubscriptionView: View { if sentError == .mailFailed { errors.append("Le mail n'a pas été envoyé") } + + if sentError == .calendarAccessDenied { + errors.append("Padel Club n'a pas accès à votre calendrier") + } + + if sentError == .calendarEventSaveFailed { + errors.append("Padel Club n'a pas réussi à sauver ce tournoi dans votre calendrier") + } + + if sentError == .noCalendarAvailable { + errors.append("Padel Club n'a pas réussi à trouver un calendrier pour y inscrire ce tournoi") + } + + return errors.joined(separator: "\n") } + + func openAppSettings() { + if let appSettings = URL(string: UIApplication.openSettingsURLString) { + if UIApplication.shared.canOpenURL(appSettings) { + UIApplication.shared.open(appSettings, options: [:], completionHandler: nil) + } + } + } + } diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index 36a50fa..5345e4f 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -122,6 +122,22 @@ struct UmpireView: View { } + Section { + @Bindable var user = dataStore.user + Picker(selection: $user.loserBracketMode) { + ForEach(LoserBracketMode.allCases) { + Text($0.localizedLoserBracketMode()).tag($0) + } + } label: { + Text("Position des perdants") + } + .onChange(of: user.loserBracketMode) { + dataStore.saveUser() + } + } header: { + Text("Matchs de classement") + } + Section { NavigationLink { GlobalSettingsView() diff --git a/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift b/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift index 3151d65..3cdb16d 100644 --- a/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift +++ b/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift @@ -93,6 +93,7 @@ struct DatePickingView: View { } struct MatchFormatPickingView: View { + var title: String? = nil @Binding var matchFormat: MatchFormat var validateAction: (() async -> ()) @@ -110,6 +111,10 @@ struct MatchFormatPickingView: View { confirmScheduleUpdate = false } } + } header: { + if let title { + Text(title) + } } footer: { if confirmScheduleUpdate && updatingInProgress == false { FooterButtonView("non, ne pas modifier les horaires") { @@ -123,3 +128,188 @@ struct MatchFormatPickingView: View { } } } + + +struct DatePickingViewWithFormat: View { + @Binding var matchFormat: MatchFormat + let title: String + @Binding var startDate: Date + @Binding var currentDate: Date? + var duration: Int? + var validateAction: ((Bool) async -> ()) + + @State private var confirmScheduleUpdate: Bool = false + @State private var updatingInProgress : Bool = false + + var body: some View { + Section { + MatchFormatPickerView(headerLabel: "Format", matchFormat: $matchFormat) + DatePicker(selection: $startDate) { + Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline) + } + if confirmScheduleUpdate { + RowButtonView("Sauver et modifier la suite") { + updatingInProgress = true + await validateAction(true) + updatingInProgress = false + confirmScheduleUpdate = false + } + } + } header: { + Text(title) + } footer: { + if confirmScheduleUpdate && updatingInProgress == false { + HStack { + FooterButtonView("sauver sans modifier la suite") { + Task { + await validateAction(false) + confirmScheduleUpdate = false + } + } + Text("ou") + FooterButtonView("annuler") { + confirmScheduleUpdate = false + } + } + } else { + HStack { + Menu { + Button("de 30 minutes") { + startDate = startDate.addingTimeInterval(1800) + } + + Button("d'une heure") { + startDate = startDate.addingTimeInterval(3600) + } + + Button("à 9h") { + startDate = startDate.atNine() + } + + Button("à demain 9h") { + startDate = startDate.tomorrowAtNine + } + + if let duration { + Button("à la prochaine rotation") { + startDate = startDate.addingTimeInterval(Double(duration) * 60) + } + Button("à la précédente rotation") { + startDate = startDate.addingTimeInterval(Double(duration) * -60) + } + } + } label: { + Text("décaler") + .underline() + } + .buttonStyle(.borderless) + Spacer() + + if currentDate != nil { + FooterButtonView("retirer l'horaire bloqué") { + currentDate = nil + } + } else { + FooterButtonView("bloquer l'horaire") { + currentDate = startDate + } + } + } + .buttonStyle(.borderless) + } + + } + .headerProminence(.increased) + .onChange(of: matchFormat) { + confirmScheduleUpdate = true + } + .onChange(of: startDate) { + confirmScheduleUpdate = true + } + } +} + +struct GroupStageDatePickingView: View { + let title: String + @Binding var startDate: Date + @Binding var currentDate: Date? + var duration: Int? + var validateAction: (() async -> ()) + + @State private var confirmFollowingScheduleUpdate: Bool = false + @State private var updatingInProgress: Bool = false + + var body: some View { + Section { + DatePicker(selection: $startDate) { + Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline) + } + if confirmFollowingScheduleUpdate { + RowButtonView("Confirmer et modifier les matchs") { + updatingInProgress = true + await validateAction() + updatingInProgress = false + confirmFollowingScheduleUpdate = false + } + } + } header: { + Text(title) + } footer: { + if confirmFollowingScheduleUpdate && updatingInProgress == false { + FooterButtonView("Modifier juste l'horaire de la poule") { + currentDate = startDate + confirmFollowingScheduleUpdate = false + } + } else { + HStack { + Menu { + Button("de 30 minutes") { + startDate = startDate.addingTimeInterval(1800) + } + + Button("d'une heure") { + startDate = startDate.addingTimeInterval(3600) + } + + Button("à 9h") { + startDate = startDate.atNine() + } + + Button("à demain 9h") { + startDate = startDate.tomorrowAtNine + } + + if let duration { + Button("à la prochaine rotation") { + startDate = startDate.addingTimeInterval(Double(duration) * 60) + } + Button("à la précédente rotation") { + startDate = startDate.addingTimeInterval(Double(duration) * -60) + } + } + } label: { + Text("décaler") + .underline() + } + .buttonStyle(.borderless) + Spacer() + + if currentDate != nil { + FooterButtonView("retirer l'horaire bloqué") { + currentDate = nil + } + } else { + FooterButtonView("bloquer l'horaire") { + currentDate = startDate + } + } + } + .buttonStyle(.borderless) + } + } + .onChange(of: startDate) { + confirmFollowingScheduleUpdate = true + } + .headerProminence(.increased) + } +} diff --git a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift index 31b14ea..9ade115 100644 --- a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift +++ b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift @@ -114,7 +114,6 @@ struct CourtAvailabilitySettingsView: View { endDate = tournament.startDate.addingTimeInterval(5400) showingPopover = true } - .padding(.horizontal) } } } diff --git a/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift b/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift index d0245c7..32d2347 100644 --- a/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift +++ b/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift @@ -27,8 +27,9 @@ struct GroupStageScheduleEditorView: View { } var body: some View { - DatePickingView(title: groupStage.groupStageTitle(), startDate: $startDate, currentDate: $groupStage.startDate, duration: groupStage.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) { + GroupStageDatePickingView(title: groupStage.groupStageTitle(), startDate: $startDate, currentDate: $groupStage.startDate, duration: groupStage.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) { groupStage.startDate = startDate + tournament.matchScheduler()?.updateGroupStageSchedule(tournament: tournament, specificGroupStage: groupStage) _save() } } diff --git a/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift b/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift index 33b2175..f280dd2 100644 --- a/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift +++ b/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift @@ -33,7 +33,7 @@ struct LoserRoundScheduleEditorView: View { var body: some View { List { - MatchFormatPickingView(matchFormat: $matchFormat) { + MatchFormatPickingView(title: "Format des tours par défault", matchFormat: $matchFormat) { await _updateSchedule() } diff --git a/PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift b/PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift index d497e7e..6ee09ec 100644 --- a/PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift +++ b/PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift @@ -38,8 +38,11 @@ struct LoserRoundStepScheduleEditorView: View { var body: some View { @Bindable var round = round - DatePickingView(title: "Tour #\(stepIndex + 1)", startDate: $startDate, currentDate: .constant(nil), duration: round.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) { - await _updateSchedule() + DatePickingViewWithFormat(matchFormat: $round.matchFormat, title: "Tour #\(stepIndex + 1)", startDate: $startDate, currentDate: .constant(nil), duration: round.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) { update in + for match in matches { + match.matchFormat = round.matchFormat + } + await _updateSchedule(update: update) } Section { @@ -64,8 +67,10 @@ struct LoserRoundStepScheduleEditorView: View { } } - private func _updateSchedule() async { - tournament.matchScheduler()?.updateBracketSchedule(tournament: tournament, fromRoundId: round.id, fromMatchId: nil, startDate: startDate) + private func _updateSchedule(update: Bool) async { + if update { + tournament.matchScheduler()?.updateBracketSchedule(tournament: tournament, fromRoundId: round.id, fromMatchId: nil, startDate: startDate) + } upperRound.loserRounds(forRoundIndex: round.index).forEach({ round in round.startDate = startDate }) diff --git a/PadelClub/Views/Planning/MatchScheduleEditorView.swift b/PadelClub/Views/Planning/MatchScheduleEditorView.swift index 695adf7..95a1bce 100644 --- a/PadelClub/Views/Planning/MatchScheduleEditorView.swift +++ b/PadelClub/Views/Planning/MatchScheduleEditorView.swift @@ -27,6 +27,10 @@ struct MatchScheduleEditorView: View { } var body: some View { + MatchFormatPickingView(matchFormat: $match.matchFormat) { + await _updateSchedule() + } + DatePickingView(title: title, startDate: $startDate, currentDate: .constant(nil), duration: match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) { await _updateSchedule() } diff --git a/PadelClub/Views/Planning/PlanningByCourtView.swift b/PadelClub/Views/Planning/PlanningByCourtView.swift index ba1bdef..96302fd 100644 --- a/PadelClub/Views/Planning/PlanningByCourtView.swift +++ b/PadelClub/Views/Planning/PlanningByCourtView.swift @@ -51,7 +51,6 @@ struct PlanningByCourtView: View { RowButtonView("Horaire intelligent") { selectedScheduleDestination = nil } - .padding(.horizontal) } } } diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index 3528cc1..1286f4d 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -21,7 +21,9 @@ struct PlanningSettingsView: View { @State private var showOptions: Bool = false @State private var issueFound: Bool = false @State private var parallelType: Bool = false - + @State private var deletingDateMatchesDone: Bool = false + @State private var deletingDone: Bool = false + var tournamentStore: TournamentStore { return self.tournament.tournamentStore } @@ -121,11 +123,13 @@ struct PlanningSettingsView: View { Section { RowButtonView("Supprimer les horaires des matches", role: .destructive) { do { + deletingDateMatchesDone = false allMatches.forEach({ $0.startDate = nil $0.confirmed = false }) try self.tournamentStore.matches.addOrUpdate(contentOfs: allMatches) + deletingDateMatchesDone = true } catch { Logger.error(error) } @@ -137,7 +141,8 @@ struct PlanningSettingsView: View { Section { RowButtonView("Supprimer tous les horaires", role: .destructive) { do { - allMatches.forEach({ + deletingDone = false + allMatches.forEach({ $0.startDate = nil $0.confirmed = false }) @@ -148,6 +153,7 @@ struct PlanningSettingsView: View { allRounds.forEach({ $0.startDate = nil }) try self.tournamentStore.rounds.addOrUpdate(contentOfs: allRounds) + deletingDone = true } catch { Logger.error(error) } @@ -197,6 +203,18 @@ struct PlanningSettingsView: View { .deferredRendering(for: .seconds(2)) } } + + if deletingDone { + Label("Tous les horaires ont été supprimés", systemImage: "clock.badge.xmark") + .toastFormatted() + .deferredRendering(for: .seconds(2)) + } + + if deletingDateMatchesDone { + Label("Horaires des matchs supprimés", systemImage: "clock.badge.xmark") + .toastFormatted() + .deferredRendering(for: .seconds(2)) + } } .onChange(of: tournament.startDate) { _save() diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index 1e0c0fc..e490a08 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -40,7 +40,6 @@ struct PlanningView: View { RowButtonView("Horaire intelligent") { selectedScheduleDestination = nil } - .padding(.horizontal) } } } diff --git a/PadelClub/Views/Player/Components/PlayerPopoverView.swift b/PadelClub/Views/Player/Components/PlayerPopoverView.swift index 55ff56c..f1dbe6e 100644 --- a/PadelClub/Views/Player/Components/PlayerPopoverView.swift +++ b/PadelClub/Views/Player/Components/PlayerPopoverView.swift @@ -99,7 +99,7 @@ struct PlayerPopoverView: View { .textInputAutocapitalization(.words) .focused($firstNameIsFocused) .onSubmit { - firstName = firstName.trimmed + firstName = firstName.trimmedMultiline lastNameIsFocused = true } .fixedSize() @@ -114,7 +114,7 @@ struct PlayerPopoverView: View { .keyboardType(.alphabet) .focused($lastNameIsFocused) .onSubmit { - lastName = lastName.trimmed + lastName = lastName.trimmedMultiline licenseIsFocused = true } .fixedSize() @@ -128,7 +128,7 @@ struct PlayerPopoverView: View { .keyboardType(.numberPad) .submitLabel(.next) .onSubmit { - license = license.trimmed + license = license.trimmedMultiline if requiredField.contains(.license) { if license.isLicenseNumber { amountIsFocused = true @@ -206,7 +206,7 @@ struct PlayerPopoverView: View { Spacer() Button("Confirmer") { if licenseIsFocused { - license = license.trimmed + license = license.trimmedMultiline if requiredField.contains(.license) { if license.isLicenseNumber { amountIsFocused = true @@ -251,7 +251,7 @@ struct PlayerPopoverView: View { } func createManualPlayer() { - let playerRegistration = PlayerRegistration(firstName: firstName, lastName: lastName, licenceId: license.trimmed.isEmpty ? nil : license, rank: rank, sex: PlayerRegistration.PlayerSexType(rawValue: sex)) + let playerRegistration = PlayerRegistration(firstName: firstName.trimmedMultiline, lastName: lastName.trimmedMultiline, licenceId: license.trimmedMultiline.isEmpty ? nil : license, rank: rank, sex: PlayerRegistration.PlayerSexType(rawValue: sex)) self.creationCompletionHandler(playerRegistration) } diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift index 938a7ac..3d65b72 100644 --- a/PadelClub/Views/Player/PlayerDetailView.swift +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -41,6 +41,7 @@ struct PlayerDetailView: View { .multilineTextAlignment(.trailing) .frame(maxWidth: .infinity) .onSubmit(of: .text) { + player.lastName = player.lastName.trimmedMultiline _save() } } label: { @@ -53,6 +54,7 @@ struct PlayerDetailView: View { .multilineTextAlignment(.trailing) .frame(maxWidth: .infinity) .onSubmit(of: .text) { + player.firstName = player.firstName.trimmedMultiline _save() } } label: { diff --git a/PadelClub/Views/Round/LoserRoundSettingsView.swift b/PadelClub/Views/Round/LoserRoundSettingsView.swift index 2e24481..08952fe 100644 --- a/PadelClub/Views/Round/LoserRoundSettingsView.swift +++ b/PadelClub/Views/Round/LoserRoundSettingsView.swift @@ -22,6 +22,28 @@ struct LoserRoundSettingsView: View { } } + Section { + @Bindable var round: Round = upperBracketRound.round + Picker(selection: $round.loserBracketMode) { + ForEach(LoserBracketMode.allCases) { + Text($0.localizedLoserBracketMode()).tag($0) + } + } label: { + Text("Position des perdants") + } + .onChange(of: round.loserBracketMode) { + do { + try self.tournament.tournamentStore.rounds.addOrUpdate(instance: upperBracketRound.round) + } catch { + Logger.error(error) + } + } + } header: { + Text("Matchs de classement") + } footer: { + Text(upperBracketRound.round.loserBracketMode.localizedLoserBracketModeDescription()) + } + Section { RowButtonView("Synchroniser les noms des matchs") { let allRoundMatches = upperBracketRound.loserRounds.flatMap({ $0.allMatches @@ -48,6 +70,11 @@ struct LoserRoundSettingsView: View { upperBracketRound.round.disabledMatches().forEach { match in match.disableMatch() } + do { + try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: upperBracketRound.round.allLoserRoundMatches()) + } catch { + Logger.error(error) + } } } .disabled(upperBracketRound.round.loserRounds().isEmpty == false) diff --git a/PadelClub/Views/Shared/SelectablePlayerListView.swift b/PadelClub/Views/Shared/SelectablePlayerListView.swift index b5d9a2d..7ba50a3 100644 --- a/PadelClub/Views/Shared/SelectablePlayerListView.swift +++ b/PadelClub/Views/Shared/SelectablePlayerListView.swift @@ -172,7 +172,7 @@ struct SelectablePlayerListView: View { } .scrollDismissesKeyboard(.immediately) .navigationBarBackButtonHidden(searchViewModel.allowMultipleSelection) - .toolbarBackground(.visible, for: .bottomBar) + //.toolbarBackground(.visible, for: .bottomBar) // .toolbarRole(searchViewModel.allowMultipleSelection ? .navigationStack : .editor) .interactiveDismissDisabled(searchViewModel.selectedPlayers.isEmpty == false) .navigationTitle(searchViewModel.label(forDataSet: searchViewModel.dataSet)) @@ -358,13 +358,109 @@ struct MySearchView: View { @ViewBuilder var playersView: some View { + let showProgression = true + let showFemaleInMaleAssimilation = searchViewModel.showFemaleInMaleAssimilation if searchViewModel.allowMultipleSelection { List(selection: $searchViewModel.selectedPlayers) { if searchViewModel.filterSelectionEnabled { let array = Array(searchViewModel.selectedPlayers) Section { ForEach(array) { player in - ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) + let index : Int? = nil + VStack(alignment: .leading) { + HStack { + if player.isAnonymous() { + Text("Joueur Anonyme") + } else { + Text(player.getLastName().capitalized) + Text(player.getFirstName().capitalized) + } + if index == nil { + Text(player.male ? "♂︎" : "♀︎") + } + Spacer() + if let index { + HStack(alignment: .top, spacing: 0) { + Text(index.formatted()) + .foregroundStyle(.secondary) + .font(.title3) + Text(index.ordinalFormattedSuffix()) + .foregroundStyle(.secondary) + .font(.caption) + } + } + } + .font(.title3) + .lineLimit(1) + HStack { + HStack(alignment: .top, spacing: 0) { + Text(player.formattedRank()).italic(player.isAssimilated) + .font(.title3) + .background { + if player.isNotFromCurrentDate() { + UnderlineView() + } + } + if let rank = player.getRank() { + Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated) + .font(.caption) + } + } + + if showProgression, player.getProgression() != 0 { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(player.getProgression().formatted(.number.sign(strategy: .always()))) + .foregroundStyle(player.getProgressionColor(progression: player.getProgression())) + Text(")") + }.font(.title3) + } + + if let pts = player.getPoints(), pts > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(pts.formatted()).font(.title3) + Text(" pts").font(.caption) + } + } + + if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(tournamentPlayed.formatted()).font(.title3) + Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption) + } + } + } + .lineLimit(1) + + if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(assimilatedAsMaleRank.formatted()) + VStack(alignment: .leading, spacing: 0) { + Text("équivalence") + Text("messieurs") + } + .font(.caption) + Text(")").font(.title3) + } + } + + HStack { + Text(player.formattedLicense()) + if let computedAge = player.computedAge { + Text(computedAge.formatted() + " ans") + } + } + .font(.caption) + if let clubName = player.clubName { + Text(clubName) + .font(.caption) + } + if let ligueName = player.ligueName { + Text(ligueName) + .font(.caption) + } + } } .onDelete { indexSet in for index in indexSet { @@ -379,7 +475,102 @@ struct MySearchView: View { } else { Section { ForEach(players, id: \.self) { player in - ImportedPlayerView(player: player, index: nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) + let index : Int? = nil + + VStack(alignment: .leading) { + HStack { + if player.isAnonymous() { + Text("Joueur Anonyme") + } else { + Text(player.getLastName().capitalized) + Text(player.getFirstName().capitalized) + } + if index == nil { + Text(player.male ? "♂︎" : "♀︎") + } + Spacer() + if let index { + HStack(alignment: .top, spacing: 0) { + Text(index.formatted()) + .foregroundStyle(.secondary) + .font(.title3) + Text(index.ordinalFormattedSuffix()) + .foregroundStyle(.secondary) + .font(.caption) + } + } + } + .font(.title3) + .lineLimit(1) + HStack { + HStack(alignment: .top, spacing: 0) { + Text(player.formattedRank()).italic(player.isAssimilated) + .font(.title3) + .background { + if player.isNotFromCurrentDate() { + UnderlineView() + } + } + if let rank = player.getRank() { + Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated) + .font(.caption) + } + } + + if showProgression, player.getProgression() != 0 { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(player.getProgression().formatted(.number.sign(strategy: .always()))) + .foregroundStyle(player.getProgressionColor(progression: player.getProgression())) + Text(")") + }.font(.title3) + } + + if let pts = player.getPoints(), pts > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(pts.formatted()).font(.title3) + Text(" pts").font(.caption) + } + } + + if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(tournamentPlayed.formatted()).font(.title3) + Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption) + } + } + } + .lineLimit(1) + + if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(assimilatedAsMaleRank.formatted()) + VStack(alignment: .leading, spacing: 0) { + Text("équivalence") + Text("messieurs") + } + .font(.caption) + Text(")").font(.title3) + } + } + + HStack { + Text(player.formattedLicense()) + if let computedAge = player.computedAge { + Text(computedAge.formatted() + " ans") + } + } + .font(.caption) + if let clubName = player.clubName { + Text(clubName) + .font(.caption) + } + if let ligueName = player.ligueName { + Text(ligueName) + .font(.caption) + } + } } } header: { if players.isEmpty == false { @@ -395,10 +586,105 @@ struct MySearchView: View { if searchViewModel.allowSingleSelection { Section { ForEach(players) { player in + let index : Int? = nil + Button { searchViewModel.selectedPlayers.insert(player) } label: { - ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) + VStack(alignment: .leading) { + HStack { + if player.isAnonymous() { + Text("Joueur Anonyme") + } else { + Text(player.getLastName().capitalized) + Text(player.getFirstName().capitalized) + } + if index == nil { + Text(player.male ? "♂︎" : "♀︎") + } + Spacer() + if let index { + HStack(alignment: .top, spacing: 0) { + Text(index.formatted()) + .foregroundStyle(.secondary) + .font(.title3) + Text(index.ordinalFormattedSuffix()) + .foregroundStyle(.secondary) + .font(.caption) + } + } + } + .font(.title3) + .lineLimit(1) + HStack { + HStack(alignment: .top, spacing: 0) { + Text(player.formattedRank()).italic(player.isAssimilated) + .font(.title3) + .background { + if player.isNotFromCurrentDate() { + UnderlineView() + } + } + if let rank = player.getRank() { + Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated) + .font(.caption) + } + } + + if showProgression, player.getProgression() != 0 { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(player.getProgression().formatted(.number.sign(strategy: .always()))) + .foregroundStyle(player.getProgressionColor(progression: player.getProgression())) + Text(")") + }.font(.title3) + } + + if let pts = player.getPoints(), pts > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(pts.formatted()).font(.title3) + Text(" pts").font(.caption) + } + } + + if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(tournamentPlayed.formatted()).font(.title3) + Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption) + } + } + } + .lineLimit(1) + + if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(assimilatedAsMaleRank.formatted()) + VStack(alignment: .leading, spacing: 0) { + Text("équivalence") + Text("messieurs") + } + .font(.caption) + Text(")").font(.title3) + } + } + + HStack { + Text(player.formattedLicense()) + if let computedAge = player.computedAge { + Text(computedAge.formatted() + " ans") + } + } + .font(.caption) + if let clubName = player.clubName { + Text(clubName) + .font(.caption) + } + if let ligueName = player.ligueName { + Text(ligueName) + .font(.caption) + } + } } .buttonStyle(.plain) } @@ -410,9 +696,104 @@ struct MySearchView: View { .id(UUID()) } else { Section { - ForEach(players.indices, id: \.self) { index in - let player = players[index] - ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) + ForEach(players.indices, id: \.self) { playerIndex in + let player = players[playerIndex] + let index: Int? = searchViewModel.showIndex() ? (playerIndex + 1) : nil + + VStack(alignment: .leading) { + HStack { + if player.isAnonymous() { + Text("Joueur Anonyme") + } else { + Text(player.getLastName().capitalized) + Text(player.getFirstName().capitalized) + } + if index == nil { + Text(player.male ? "♂︎" : "♀︎") + } + Spacer() + if let index { + HStack(alignment: .top, spacing: 0) { + Text(index.formatted()) + .foregroundStyle(.secondary) + .font(.title3) + Text(index.ordinalFormattedSuffix()) + .foregroundStyle(.secondary) + .font(.caption) + } + } + } + .font(.title3) + .lineLimit(1) + HStack { + HStack(alignment: .top, spacing: 0) { + Text(player.formattedRank()).italic(player.isAssimilated) + .font(.title3) + .background { + if player.isNotFromCurrentDate() { + UnderlineView() + } + } + if let rank = player.getRank() { + Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated) + .font(.caption) + } + } + + if showProgression, player.getProgression() != 0 { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(player.getProgression().formatted(.number.sign(strategy: .always()))) + .foregroundStyle(player.getProgressionColor(progression: player.getProgression())) + Text(")") + }.font(.title3) + } + + if let pts = player.getPoints(), pts > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(pts.formatted()).font(.title3) + Text(" pts").font(.caption) + } + } + + if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(tournamentPlayed.formatted()).font(.title3) + Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption) + } + } + } + .lineLimit(1) + + if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(assimilatedAsMaleRank.formatted()) + VStack(alignment: .leading, spacing: 0) { + Text("équivalence") + Text("messieurs") + } + .font(.caption) + Text(")").font(.title3) + } + } + + HStack { + Text(player.formattedLicense()) + if let computedAge = player.computedAge { + Text(computedAge.formatted() + " ans") + } + } + .font(.caption) + if let clubName = player.clubName { + Text(clubName) + .font(.caption) + } + if let ligueName = player.ligueName { + Text(ligueName) + .font(.caption) + } + } } } header: { if players.isEmpty == false { @@ -423,19 +804,205 @@ struct MySearchView: View { } } else { Section { - ForEach(players.indices, id: \.self) { index in - let player = players[index] + ForEach(players.indices, id: \.self) { playerIndex in + let player = players[playerIndex] + let index: Int? = searchViewModel.showIndex() ? (playerIndex + 1) : nil if searchViewModel.allowSingleSelection { Button { searchViewModel.selectedPlayers.insert(player) } label: { - ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) - .contentShape(Rectangle()) + VStack(alignment: .leading) { + HStack { + if player.isAnonymous() { + Text("Joueur Anonyme") + } else { + Text(player.getLastName().capitalized) + Text(player.getFirstName().capitalized) + } + if index == nil { + Text(player.male ? "♂︎" : "♀︎") + } + Spacer() + if let index { + HStack(alignment: .top, spacing: 0) { + Text(index.formatted()) + .foregroundStyle(.secondary) + .font(.title3) + Text(index.ordinalFormattedSuffix()) + .foregroundStyle(.secondary) + .font(.caption) + } + } + } + .font(.title3) + .lineLimit(1) + HStack { + HStack(alignment: .top, spacing: 0) { + Text(player.formattedRank()).italic(player.isAssimilated) + .font(.title3) + .background { + if player.isNotFromCurrentDate() { + UnderlineView() + } + } + if let rank = player.getRank() { + Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated) + .font(.caption) + } + } + + if showProgression, player.getProgression() != 0 { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(player.getProgression().formatted(.number.sign(strategy: .always()))) + .foregroundStyle(player.getProgressionColor(progression: player.getProgression())) + Text(")") + }.font(.title3) + } + + if let pts = player.getPoints(), pts > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(pts.formatted()).font(.title3) + Text(" pts").font(.caption) + } + } + + if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(tournamentPlayed.formatted()).font(.title3) + Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption) + } + } + } + .lineLimit(1) + + if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(assimilatedAsMaleRank.formatted()) + VStack(alignment: .leading, spacing: 0) { + Text("équivalence") + Text("messieurs") + } + .font(.caption) + Text(")").font(.title3) + } + } + + HStack { + Text(player.formattedLicense()) + if let computedAge = player.computedAge { + Text(computedAge.formatted() + " ans") + } + } + .font(.caption) + if let clubName = player.clubName { + Text(clubName) + .font(.caption) + } + if let ligueName = player.ligueName { + Text(ligueName) + .font(.caption) + } + } } .frame(maxWidth: .infinity) .buttonStyle(.plain) } else { - ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) + VStack(alignment: .leading) { + HStack { + if player.isAnonymous() { + Text("Joueur Anonyme") + } else { + Text(player.getLastName().capitalized) + Text(player.getFirstName().capitalized) + } + if index == nil { + Text(player.male ? "♂︎" : "♀︎") + } + Spacer() + if let index { + HStack(alignment: .top, spacing: 0) { + Text(index.formatted()) + .foregroundStyle(.secondary) + .font(.title3) + Text(index.ordinalFormattedSuffix()) + .foregroundStyle(.secondary) + .font(.caption) + } + } + } + .font(.title3) + .lineLimit(1) + HStack { + HStack(alignment: .top, spacing: 0) { + Text(player.formattedRank()).italic(player.isAssimilated) + .font(.title3) + .background { + if player.isNotFromCurrentDate() { + UnderlineView() + } + } + if let rank = player.getRank() { + Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated) + .font(.caption) + } + } + + if showProgression, player.getProgression() != 0 { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(player.getProgression().formatted(.number.sign(strategy: .always()))) + .foregroundStyle(player.getProgressionColor(progression: player.getProgression())) + Text(")") + }.font(.title3) + } + + if let pts = player.getPoints(), pts > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(pts.formatted()).font(.title3) + Text(" pts").font(.caption) + } + } + + if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(tournamentPlayed.formatted()).font(.title3) + Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption) + } + } + } + .lineLimit(1) + + if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(assimilatedAsMaleRank.formatted()) + VStack(alignment: .leading, spacing: 0) { + Text("équivalence") + Text("messieurs") + } + .font(.caption) + Text(")").font(.title3) + } + } + + HStack { + Text(player.formattedLicense()) + if let computedAge = player.computedAge { + Text(computedAge.formatted() + " ans") + } + } + .font(.caption) + if let clubName = player.clubName { + Text(clubName) + .font(.caption) + } + if let ligueName = player.ligueName { + Text(ligueName) + .font(.caption) + } + } } } } header: { diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 5a03ef6..bee37c6 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -147,7 +147,7 @@ struct EditingTeamView: View { .frame(maxWidth: .infinity) .submitLabel(.done) .onSubmit(of: .text) { - let trimmed = name.trimmed + let trimmed = name.trimmedMultiline if trimmed.isEmpty { team.name = nil } else { @@ -243,7 +243,7 @@ struct EditingTeamView: View { } .tint(.master) } - .sheet(item: $editedTeam) { editedTeam in + .fullScreenCover(item: $editedTeam) { editedTeam in NavigationStack { AddTeamView(tournament: tournament, editedTeam: editedTeam) } diff --git a/PadelClub/Views/Team/TeamPickerView.swift b/PadelClub/Views/Team/TeamPickerView.swift index a9b143c..0515283 100644 --- a/PadelClub/Views/Team/TeamPickerView.swift +++ b/PadelClub/Views/Team/TeamPickerView.swift @@ -18,6 +18,7 @@ struct TeamPickerView: View { var shouldConfirm: Bool = false var groupStagePosition: Int? = nil + var round: Round? = nil var matchTypeContext: MatchType = .bracket var luckyLosers: [TeamRegistration] = [] let teamPicked: ((TeamRegistration) -> (Void)) @@ -39,6 +40,14 @@ struct TeamPickerView: View { .sheet(isPresented: $presentTeamPickerView) { NavigationStack { List { + if matchTypeContext == .loserBracket, let losers = round?.parentRound?.losers() { + _sectionView(losers.sorted(by: \.weight, order: sortOrder), title: "Perdant du tour précédent") + } + + if matchTypeContext == .loserBracket, let losers = round?.previousRound()?.winners() { + _sectionView(losers.sorted(by: \.weight, order: sortOrder), title: "Gagnant du tour précédent") + } + if let groupStagePosition, let replacementRangeExtended = tournament.replacementRangeExtended(groupStagePosition: groupStagePosition) { Section { GroupStageTeamReplacementView.TeamRangeView(teamRange: replacementRangeExtended, playerWeight: 0) @@ -130,6 +139,7 @@ struct TeamPickerView: View { } .frame(maxWidth: .infinity) .buttonStyle(.plain) + .listRowView(isActive: matchTypeContext == .loserBracket && round?.teams().map({ $0.id }).contains(team.id) == true, color: .green, hideColorVariation: true) // .confirmationDialog("Attention", isPresented: confirmationRequest, titleVisibility: .visible) { // Button("Retirer du tableau", role: .destructive) { // teamPicked(confirmTeam!) diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index f7e9c42..c6644ff 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -317,6 +317,20 @@ struct FileImportView: View { } } + let unfound = _getUnfound(tournament: tournament, fromTeams: _filteredTeams) + + if unfound.isEmpty == false { + Section { + LabeledContent { + Text(unfound.count.formatted()) + } label: { + Text("Équipe\(unfound.count.pluralSuffix) précédente\(unfound.count.pluralSuffix) introuvable\(unfound.count.pluralSuffix)") + } + } footer: { + Text("Équipes de votre liste précédente non détectées dans ce nouveau fichier") + } + } + ForEach(_filteredTeams) { team in _teamView(team: team, inTeams: _filteredTeams, previousTeams: previousTeams) } @@ -442,11 +456,16 @@ struct FileImportView: View { .disabled(validationInProgress) } + private func _getUnfound(tournament: Tournament, fromTeams filteredTeams: [FileImportManager.TeamHolder]) -> Set { + let previousTeams = filteredTeams.compactMap({ $0.previousTeam }) + let unfound = Set(tournament.unsortedTeams()).subtracting(Set(previousTeams)) + return unfound + } + private func _validate(tournament: Tournament) async { let filteredTeams = filteredTeams(tournament: tournament) - let previousTeams = filteredTeams.compactMap({ $0.previousTeam }) - let unfound = Set(tournament.unsortedTeams()).subtracting(Set(previousTeams)) + let unfound = _getUnfound(tournament: tournament, fromTeams: filteredTeams) unfound.forEach { team in team.resetPositions() team.wildCardBracket = false @@ -455,7 +474,7 @@ struct FileImportView: View { } do { - try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unfound) + try tournament.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unfound) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Tournament/Screen/AddTeamView.swift b/PadelClub/Views/Tournament/Screen/AddTeamView.swift index f97e3a7..32b0f1a 100644 --- a/PadelClub/Views/Tournament/Screen/AddTeamView.swift +++ b/PadelClub/Views/Tournament/Screen/AddTeamView.swift @@ -20,7 +20,11 @@ struct AddTeamView: View { var tournament: Tournament var cancelShouldDismiss: Bool = false + enum FocusField: Hashable { + case pasteField + } + @FocusState private var focusedField: FocusField? @State private var searchField: String = "" @State private var presentSearch: Bool = false @State private var presentPlayerSearch: Bool = false @@ -36,7 +40,8 @@ struct AddTeamView: View { @State private var confirmDuplicate: Bool = false @State private var homonyms: [PlayerRegistration] = [] @State private var confirmHomonym: Bool = false - + @State private var editableTextField: String = "" + var tournamentStore: TournamentStore { return self.tournament.tournamentStore } @@ -61,13 +66,32 @@ struct AddTeamView: View { _pasteString = .init(wrappedValue: pasteString) _fetchPlayers = FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)], predicate: SearchViewModel.pastePredicate(pasteField: pasteString, mostRecentDate: tournament.rankSourceDate, filterOption: tournament.tournamentCategory.playerFilterOption)) _autoSelect = .init(wrappedValue: true) + _editableTextField = .init(wrappedValue: pasteString) cancelShouldDismiss = true } } var body: some View { - _buildingTeamView() - .navigationBarBackButtonHidden(true) + if pasteString != nil, fetchPlayers.isEmpty == false { + computedBody + .searchable(text: $searchField, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher dans les résultats")) + } else { + computedBody + } + } + + var computedBody: some View { + List(selection: $createdPlayerIds) { + _buildingTeamView() + } + .onReceive(fetchPlayers.publisher.count()) { _ in // <-- here + if let pasteString, count == 2, autoSelect == true { + fetchPlayers.filter { $0.hitForSearch(pasteString) >= hitTarget }.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) }).forEach { player in + createdPlayerIds.insert(player.license!) + } + autoSelect = false + } + } .alert("Présence d'homonyme", isPresented: $confirmHomonym) { Button("Créer l'équipe quand même") { _createTeam(checkDuplicates: false, checkHomonym: false) @@ -121,7 +145,6 @@ struct AddTeamView: View { } .tint(.master) } - .navigationTitle(editedTeam == nil ? "Ajouter une équipe" : "Modifier l'équipe") .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Annuler", role: .cancel) { @@ -130,27 +153,23 @@ struct AddTeamView: View { } if pasteString == nil { - ToolbarItem(placement: .topBarTrailing) { + ToolbarItem(placement: .bottomBar) { PasteButton(payloadType: String.self) { strings in guard let first = strings.first else { return } - Task { - await MainActor.run { - fetchPlayers.nsPredicate = SearchViewModel.pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption()) - fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)] - pasteString = first - autoSelect = true - } - } + handlePasteString(first) } .foregroundStyle(.master) - .labelStyle(.iconOnly) + .labelStyle(.titleAndIcon) .buttonBorderShape(.capsule) } } } - .navigationBarBackButtonHidden(_isEditingTeam()) + .navigationBarBackButtonHidden(true) .toolbarBackground(.visible, for: .navigationBar) + .toolbarBackground(.visible, for: .bottomBar) .navigationBarTitleDisplayMode(.inline) + .navigationTitle(editedTeam == nil ? "Ajouter une équipe" : "Modifier l'équipe") + .environment(\.editMode, Binding.constant(EditMode.active)) } private func _isEditingTeam() -> Bool { @@ -275,6 +294,8 @@ struct AddTeamView: View { createdPlayers.removeAll() createdPlayerIds.removeAll() pasteString = nil + editableTextField = "" + if team.players().count > 1 { dismiss() } @@ -302,28 +323,50 @@ struct AddTeamView: View { createdPlayers.removeAll() createdPlayerIds.removeAll() pasteString = nil + editableTextField = "" + self.editedTeam = nil if editedTeam.players().count > 1 { dismiss() } } + @ViewBuilder private func _buildingTeamView() -> some View { - List(selection: $createdPlayerIds) { if let pasteString { - Section { - Text(pasteString) + TextEditor(text: $editableTextField) + .frame(minHeight: 120, maxHeight: .infinity) + .focused($focusedField, equals: .pasteField) + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Button("Fermer", role: .cancel) { + self.editableTextField = pasteString + self.focusedField = nil + } + Spacer() + Button("Chercher") { + self.handlePasteString(editableTextField) + self.focusedField = nil + } + .buttonStyle(.bordered) + } + } + } header: { + Text("Contenu du presse-papier") } footer: { HStack { - Text("contenu du presse-papier") + FooterButtonView("éditer") { + self.focusedField = .pasteField + } Spacer() - Button("effacer", role: .destructive) { + FooterButtonView("effacer", role: .destructive) { + self.focusedField = nil + self.editableTextField = "" self.pasteString = nil self.createdPlayers.removeAll() self.createdPlayerIds.removeAll() } - .buttonStyle(.borderless) } } } @@ -387,6 +430,8 @@ struct AddTeamView: View { Text(tournament.cutLabel(index: teamIndex, teamCount: selectedSortedTeams.count)) } } +// } else { +// Text("Préparation de l'équipe") } } @@ -404,33 +449,16 @@ struct AddTeamView: View { RowButtonView("Effacer cette recherche") { self.pasteString = nil + self.editableTextField = "" } } } else { - Section { - let sortedPlayers = fetchPlayers.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) }) - ForEach(sortedPlayers) { player in - ImportedPlayerView(player: player).tag(player.license!) - } - } header: { - Text(fetchPlayers.count.formatted() + " résultat" + fetchPlayers.count.pluralSuffix) - } + _listOfPlayers(pasteString: pasteString) } } else { _managementView() } - } - .headerProminence(.increased) - .onReceive(fetchPlayers.publisher.count()) { _ in // <-- here - if let pasteString, count == 2, autoSelect == true { - fetchPlayers.filter { $0.hitForSearch(pasteString) >= hitTarget }.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) }).forEach { player in - createdPlayerIds.insert(player.license!) - } - autoSelect = false - } - } - .environment(\.editMode, Binding.constant(EditMode.active)) } private var count: Int { @@ -453,4 +481,32 @@ struct AddTeamView: View { Logger.error(error) } } + + private func handlePasteString(_ first: String) { + Task { + await MainActor.run { + fetchPlayers.nsPredicate = SearchViewModel.pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption()) + fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)] + pasteString = first + editableTextField = first + autoSelect = true + } + } + + } + + + @ViewBuilder + private func _listOfPlayers(pasteString: String) -> some View { + let sortedPlayers = fetchPlayers.filter({ $0.contains(searchField) || searchField.isEmpty }).sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) }) + + Section { + ForEach(sortedPlayers) { player in + ImportedPlayerView(player: player).tag(player.license!) + } + } header: { + Text(sortedPlayers.count.formatted() + " résultat" + sortedPlayers.count.pluralSuffix) + } + } + } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift index ed37d53..35afb65 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -11,7 +11,7 @@ import LeStorage struct TournamentGeneralSettingsView: View { @EnvironmentObject var dataStore: DataStore - var tournament: Tournament + @Bindable var tournament: Tournament @State private var tournamentName: String = "" @State private var entryFee: Double? = nil @FocusState private var focusedField: Tournament.CodingKeys? @@ -24,7 +24,7 @@ struct TournamentGeneralSettingsView: View { var body: some View { @Bindable var tournament = tournament - Form { + Form { Section { TournamentDatePickerView() TournamentDurationManagerView() @@ -34,6 +34,43 @@ struct TournamentGeneralSettingsView: View { TournamentLevelPickerView() } + Section { + Picker(selection: $tournament.loserBracketMode) { + ForEach(LoserBracketMode.allCases) { + Text($0.localizedLoserBracketMode()).tag($0) + } + } label: { + Text("Position des perdants") + } + .onChange(of: tournament.loserBracketMode) { + + _save() + + let rounds = tournament.rounds() + rounds.forEach { round in + round.loserBracketMode = tournament.loserBracketMode + } + + do { + try self.tournament.tournamentStore.rounds.addOrUpdate(contentOfs: rounds) + } catch { + Logger.error(error) + } + } + } header: { + Text("Matchs de classement") + } footer: { + if dataStore.user.loserBracketMode != tournament.loserBracketMode { + _footerView() + .onTapGesture(perform: { + self.dataStore.user.loserBracketMode = tournament.loserBracketMode + self.dataStore.saveUser() + }) + } else { + Text(tournament.loserBracketMode.localizedLoserBracketModeDescription()) + } + } + Section { LabeledContent { TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR")) @@ -118,4 +155,10 @@ struct TournamentGeneralSettingsView: View { Logger.error(error) } } + + private func _footerView() -> some View { + Text(tournament.loserBracketMode.localizedLoserBracketModeDescription()) + + + Text(" Modifier le réglage par défaut pour tous vos tournois").foregroundStyle(.blue) + } } diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 6c80e60..58f26f4 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -224,14 +224,11 @@ struct InscriptionManagerView: View { RowButtonView("Ajouter une équipe") { presentAddTeamView = true } - .padding(.horizontal) RowButtonView("Importer un fichier") { presentImportView = true } - .padding(.horizontal) } - .padding() } } } @@ -327,7 +324,7 @@ struct InscriptionManagerView: View { UpdateSourceRankDateView(currentRankSourceDate: $currentRankSourceDate, confirmUpdateRank: $confirmUpdateRank, tournament: tournament) .tint(.master) } - .sheet(isPresented: $presentAddTeamView, onDismiss: { + .fullScreenCover(isPresented: $presentAddTeamView, onDismiss: { _setHash() }) { NavigationStack { @@ -335,7 +332,7 @@ struct InscriptionManagerView: View { } .tint(.master) } - .sheet(item: $editedTeam, onDismiss: { + .fullScreenCover(item: $editedTeam, onDismiss: { _setHash() }) { editedTeam in NavigationStack { @@ -343,7 +340,7 @@ struct InscriptionManagerView: View { } .tint(.master) } - .sheet(item: $pasteString, onDismiss: { + .fullScreenCover(item: $pasteString, onDismiss: { _setHash() }) { pasteString in NavigationStack { diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift index 98837ad..5dc380e 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -74,7 +74,7 @@ struct TournamentSettingsView: View { } .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) - .navigationTitle("Réglages") + .navigationTitle("Réglages du tournoi") } } diff --git a/PadelClub/Views/Tournament/Subscription/Guard.swift b/PadelClub/Views/Tournament/Subscription/Guard.swift index c092401..49ba138 100644 --- a/PadelClub/Views/Tournament/Subscription/Guard.swift +++ b/PadelClub/Views/Tournament/Subscription/Guard.swift @@ -178,7 +178,9 @@ import LeStorage #if DEBUG return .monthlyUnlimited #elseif TESTFLIGHT - return .monthlyUnlimited + return .monthlyUnlimited + #elseif PRODTEST + return .monthlyUnlimited #else if let currentBestPurchase = self.currentBestPurchase, let plan = StoreItem(rawValue: currentBestPurchase.productId) { return plan diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index 969f6ff..2f36a05 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -40,7 +40,7 @@ struct TournamentInitView: View { Image(systemName: "checkmark").foregroundStyle(.green) } } label: { - LabelStructure() + Text("Structure") Text(tournament.structureDescriptionLocalizedLabel()) } } @@ -49,7 +49,7 @@ struct TournamentInitView: View { LabeledContent { Text(tournament.localizedTournamentType()) } label: { - LabelSettings() + Text("Réglages du tournoi") Text("Formats, terrains, prix et plus") } }