From 68338f580ac41dcc08bca478fe259d6a175f5396 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 10 Sep 2024 10:09:08 +0200 Subject: [PATCH] clean up new features --- .../Coredata/ImportedPlayer+Extensions.swift | 2 +- PadelClub/Data/User.swift | 7 +++ PadelClub/Extensions/Array+Extensions.swift | 16 ++++++ .../Extensions/Calendar+Extensions.swift | 20 +++++++ .../ViewModel/FederalDataViewModel.swift | 8 +-- .../Navigation/Agenda/ActivityView.swift | 33 +++++++----- .../Navigation/Agenda/CalendarView.swift | 11 +++- .../Navigation/Agenda/EventListView.swift | 38 +++++++++---- .../Agenda/TournamentLookUpView.swift | 6 +-- .../Agenda/TournamentSubscriptionView.swift | 54 ++++++++++++++----- 10 files changed, 149 insertions(+), 46 deletions(-) diff --git a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift index 12ba149..1745db9 100644 --- a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift +++ b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift @@ -59,7 +59,7 @@ extension ImportedPlayer: PlayerHolder { } func pasteData() -> String { - return [firstName?.capitalized, lastName?.capitalized, license].compactMap({ $0 }).joined(separator: " ") + return [firstName?.capitalized, lastName?.capitalized, license?.computedLicense].compactMap({ $0 }).joined(separator: " ") } func isNotFromCurrentDate() -> Bool { diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift index 6ce9d08..d40e126 100644 --- a/PadelClub/Data/User.swift +++ b/PadelClub/Data/User.swift @@ -75,6 +75,13 @@ class User: ModelObject, UserBase, Storable { return "Sportivement,\n\(firstName) \(lastName), votre JAP." } + func fullName() -> String? { + guard firstName.isEmpty == false && lastName.isEmpty == false else { + return nil + } + return "\(firstName) \(lastName)" + } + func hasTenupClubs() -> Bool { self.clubsObjects().filter({ $0.code != nil }).isEmpty == false } diff --git a/PadelClub/Extensions/Array+Extensions.swift b/PadelClub/Extensions/Array+Extensions.swift index f1ab768..baadf6c 100644 --- a/PadelClub/Extensions/Array+Extensions.swift +++ b/PadelClub/Extensions/Array+Extensions.swift @@ -78,3 +78,19 @@ extension Dictionary where Key == Int, Value == [String] { } } } + + +extension Array where Element == String { + func formatList(maxDisplay: Int = 2) -> [String] { + // Check if the array has fewer or equal elements than the maximum display limit + if self.count <= maxDisplay { + // Join all elements with commas + return self + } else { + // Join only the first `maxDisplay` elements and add "et plus" + let displayedItems = self.prefix(maxDisplay) + let remainingCount = self.count - maxDisplay + return displayedItems.dropLast() + [displayedItems.last! + " et \(remainingCount) de plus"] + } + } +} diff --git a/PadelClub/Extensions/Calendar+Extensions.swift b/PadelClub/Extensions/Calendar+Extensions.swift index 3e04ff3..47971b5 100644 --- a/PadelClub/Extensions/Calendar+Extensions.swift +++ b/PadelClub/Extensions/Calendar+Extensions.swift @@ -49,3 +49,23 @@ extension Calendar { return sportYear } } + +extension Calendar { + // Add or subtract months from a date + func addMonths(_ months: Int, to date: Date) -> Date { + return self.date(byAdding: .month, value: months, to: date)! + } + + // Generate a list of month start dates between two dates + func generateMonthRange(startDate: Date, endDate: Date) -> [Date] { + var dates: [Date] = [] + var currentDate = startDate + + while currentDate <= endDate { + dates.append(currentDate) + currentDate = self.addMonths(1, to: currentDate) + } + + return dates + } +} diff --git a/PadelClub/ViewModel/FederalDataViewModel.swift b/PadelClub/ViewModel/FederalDataViewModel.swift index e83dc5b..010b211 100644 --- a/PadelClub/ViewModel/FederalDataViewModel.swift +++ b/PadelClub/ViewModel/FederalDataViewModel.swift @@ -25,15 +25,15 @@ class FederalDataViewModel { func filterStatus() -> String { var labels: [String] = [] - labels.append(contentsOf: levels.map { $0.localizedLabel() }) - labels.append(contentsOf: categories.map { $0.localizedLabel() }) - labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }) + labels.append(contentsOf: levels.map { $0.localizedLabel() }.formatList()) + labels.append(contentsOf: categories.map { $0.localizedLabel() }.formatList()) + labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }.formatList()) let clubNames = selectedClubs.compactMap { codeClub in let club: Club? = DataStore.shared.clubs.first(where: { $0.code == codeClub }) return club?.clubTitle(.short) } - labels.append(contentsOf: clubNames) + labels.append(contentsOf: clubNames.formatList()) if dayPeriod != .all { labels.append(dayPeriod.localizedDayPeriodLabel()) } diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index e6085f7..596b360 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -90,17 +90,24 @@ struct ActivityView: View { VStack(spacing: 0) { GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false) - List { - switch navigation.agendaDestination! { - case .activity: - EventListView(tournaments: runningTournaments, sortAscending: true) - case .history: - EventListView(tournaments: endedTournaments, sortAscending: false) - case .tenup: - EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true) - .id(uuid) - case .around: - EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true) + ScrollViewReader { proxy in + List { + switch navigation.agendaDestination! { + case .activity: + EventListView(tournaments: runningTournaments, sortAscending: true) + case .history: + EventListView(tournaments: endedTournaments, sortAscending: false) + case .tenup: + EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true) + .id(uuid) + case .around: + EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true) + } + } + .onChange(of: navigation.agendaDestination) { + withAnimation { + proxy.scrollTo(0, anchor: .center) + } } } .environment(\.viewStyle, viewStyle) @@ -227,7 +234,7 @@ struct ActivityView: View { } } - if presentToolbar, tournaments.isEmpty == false { + if presentToolbar, tournaments.isEmpty == false, federalDataViewModel.areFiltersEnabled() || navigation.agendaDestination == .around { ToolbarItemGroup(placement: .bottomBar) { VStack(spacing: 0) { let searchStatus = _searchStatus() @@ -447,7 +454,7 @@ struct ActivityView: View { ContentUnavailableView { Label("Recherche de tournoi", systemImage: "magnifyingglass") } description: { - Text("Chercher les tournois autour de vous pour vous aidez à mieux selectionner ce que vous pouvez proposer.") + Text("Chercher les tournois autour de vous pour mieux décider les tournois à proposer dans votre club. Padel Club vous facilite même l'inscription !") } actions: { RowButtonView("Lancer la recherche") { displaySearchView = true diff --git a/PadelClub/Views/Navigation/Agenda/CalendarView.swift b/PadelClub/Views/Navigation/Agenda/CalendarView.swift index a39f63f..474fb28 100644 --- a/PadelClub/Views/Navigation/Agenda/CalendarView.swift +++ b/PadelClub/Views/Navigation/Agenda/CalendarView.swift @@ -91,8 +91,15 @@ struct CalendarView: View { Menu { ForEach(tournament.tournaments, id: \.id) { build in if federalDataViewModel.isFederalTournamentValidForFilters(tournament, build: build) { - Button(build.buildHolderTitle()) { - _createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build) + + if navigation.agendaDestination == .around { + NavigationLink(build.buildHolderTitle()) { + TournamentSubscriptionView(federalTournament: tournament, build: build, user: dataStore.user) + } + } else { + Button(build.buildHolderTitle()) { + _createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build) + } } } } diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index f146b31..3a8a53b 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -21,7 +21,10 @@ struct EventListView: View { let groupedTournamentsByDate = Dictionary(grouping: federalDataViewModel.filteredFederalTournaments(from: tournaments)) { $0.startDate.startOfMonth } switch viewStyle { case .list: - ForEach(groupedTournamentsByDate.keys.sorted(by: sortAscending ? { $0 < $1 } : { $0 > $1 }), id: \.self) { section in + let nextMonths = groupedTournamentsByDate.keys.sorted(by: sortAscending ? { $0 < $1 } : { $0 > $1 }) + + ForEach(nextMonths.indices, id: \.self) { sectionIndex in + let section = nextMonths[sectionIndex] if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: sortAscending ? { $0.startDate < $1.startDate } : { $0.startDate > $1.startDate } ) { Section { @@ -34,11 +37,14 @@ struct EventListView: View { Text("\(count.formatted()) tournoi" + count.pluralSuffix) } } + .id(sectionIndex) .headerProminence(.increased) } } case .calendar: - ForEach(_nextMonths(), id: \.self) { section in + let nextMonths = _nextMonths() + ForEach(nextMonths.indices, id: \.self) { sectionIndex in + let section = nextMonths[sectionIndex] let _tournaments = groupedTournamentsByDate[section] ?? [] Section { CalendarView(date: section, tournaments: _tournaments).id(federalDataViewModel.id) @@ -50,6 +56,7 @@ struct EventListView: View { Text("\(count.formatted()) tournoi" + count.pluralSuffix) } } + .id(sectionIndex) .headerProminence(.increased) .task { if navigation.agendaDestination == .tenup @@ -77,16 +84,25 @@ struct EventListView: View { } private func _nextMonths() -> [Date] { - var result: [Date] = [] - var currentDate = Date().startOfMonth - - // Generate 100 future months - for _ in 0..<12 { - result.append(currentDate) - currentDate = Calendar.current.date(byAdding: .month, value: 1, to: currentDate)! + let currentDate = Date().startOfMonth + let uniqueDates = tournaments.map { $0.startDate.startOfMonth }.uniqued().sorted() + let firstMonthOfDate = uniqueDates.first + let lastMonthOfDate = uniqueDates.last + let calendar = Calendar.current + + if let firstMonthOfDate, let lastMonthOfDate { + if navigation.agendaDestination == .history { + return calendar.generateMonthRange(startDate: firstMonthOfDate, endDate: lastMonthOfDate).reversed() + } else if navigation.agendaDestination == .around || navigation.agendaDestination == .tenup { + return calendar.generateMonthRange(startDate: firstMonthOfDate, endDate: lastMonthOfDate) + } else { + let min = min(currentDate, firstMonthOfDate) + let max = max(currentDate, lastMonthOfDate) + return calendar.generateMonthRange(startDate: min, endDate: calendar.addMonths(3, to: max)) + } + } else { + return calendar.generateMonthRange(startDate: currentDate, endDate: calendar.addMonths(3, to: currentDate)) } - - return result } private func _listView(_ tournaments: [FederalTournamentHolder]) -> some View { diff --git a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift index f622231..d4e8ca7 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift @@ -27,10 +27,10 @@ struct TournamentLookUpView: View { @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 = "" - @AppStorage("lastDistance") private var distance: Double = 30 - @AppStorage("lastSortingOption") private var sortingOption: String = "dateDebut+asc" + @State private var distance: Double = 30 + @State private var sortingOption: String = "dateDebut+asc" @State private var requestedToGetAllPages: Bool = false - @AppStorage("lastNationalCup") private var nationalCup: Bool = false + @State private var nationalCup: Bool = false @State private var revealSearchParameters: Bool = true @State private var presentAlert: Bool = false diff --git a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift index b7afe67..70467d6 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift @@ -12,6 +12,7 @@ struct TournamentSubscriptionView: View { let federalTournament: FederalTournament let build: any TournamentBuildHolder + let user: User @State private var selectedPlayers: [ImportedPlayer] @State private var contactType: ContactType? = nil @@ -20,6 +21,7 @@ struct TournamentSubscriptionView: View { init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: User) { self.federalTournament = federalTournament self.build = build + self.user = user _selectedPlayers = .init(wrappedValue: [user.currentPlayerData()].compactMap({ $0 })) } @@ -84,36 +86,43 @@ struct TournamentSubscriptionView: View { } } - let teams = selectedPlayers.map { $0.pasteData() }.joined(separator: "\n") - let body = [[build.buildHolderTitle(), federalTournament.computedStartDate].compacted().joined(separator: " "), teams].compactMap { $0 }.joined(separator: "\n") + "\n" - let subject = [build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " ") if let courrielEngagement = federalTournament.courrielEngagement { Section { RowButtonView("Contacter par email") { - contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: body, subject: subject, tournamentBuild: build as? TournamentBuild) + contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: messageBody, subject: messageSubject, tournamentBuild: build as? TournamentBuild) } } } if let installation = federalTournament.installation, let telephone = installation.telephone { if telephone.isMobileNumber() { - let body = [[build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " "), federalTournament.computedStartDate, teams].compacted().joined(separator: "\n") + "\n" Section { RowButtonView("Contacter par message") { - contactType = .message(date: nil, recipients: [telephone], body: body, tournamentBuild: build as? TournamentBuild) + contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild) } } - } else { - let number = telephone.replacingOccurrences(of: " ", with: "") - if let url = URL(string: "tel:\(number)") { - Link(destination: url) { - Label("Appeler", systemImage: "phone") - } + } + let number = telephone.replacingOccurrences(of: " ", with: "") + if let url = URL(string: "tel:\(number)") { + Link(destination: url) { + Label("Appeler", systemImage: "phone") } } } } + .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") + } + } label: { + LabelOptions() + } + }) .alert("Un problème est survenu", isPresented: messageSentFailed) { Button("OK") { } @@ -164,6 +173,27 @@ struct TournamentSubscriptionView: View { .navigationTitle("Détail du tournoi") } + var teamsString: String { + selectedPlayers.map { $0.pasteData() }.joined(separator: "\n") + } + + 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" + return body + } + + var messageBodyShort: String { + let body = [[build.buildHolderTitle(), federalTournament.clubLabel()].compacted().joined(separator: " "), federalTournament.computedStartDate, teamsString].compacted().joined(separator: "\n") + "\n" + return body + } + + var messageSubject: String { + let subject = [build.buildHolderTitle(), federalTournament.clubLabel()].compacted().joined(separator: " ") + return subject + } + var messageSentFailed: Binding { Binding { sentError != nil