diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 1278226..215bce4 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1939,7 +1939,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 33; + CURRENT_PROJECT_VERSION = 34; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -1977,7 +1977,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 33; + CURRENT_PROJECT_VERSION = 34; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Utils/HtmlGenerator.swift b/PadelClub/Utils/HtmlGenerator.swift index d9a068d..29682f1 100644 --- a/PadelClub/Utils/HtmlGenerator.swift +++ b/PadelClub/Utils/HtmlGenerator.swift @@ -58,6 +58,10 @@ class HtmlGenerator: ObservableObject { self.width = width as! CGFloat }) } + + if self.completionHandler != nil { + self.buildPDF() + } }) } diff --git a/PadelClub/Utils/HtmlService.swift b/PadelClub/Utils/HtmlService.swift index ea5a64b..73fc31a 100644 --- a/PadelClub/Utils/HtmlService.swift +++ b/PadelClub/Utils/HtmlService.swift @@ -183,7 +183,7 @@ enum HtmlService { var template = "" var bracket = "" if let round = tournament.rounds().first(where: { $0.index == roundIndex }) { - for (_, match) in round.playedMatches().enumerated() { + for (_, match) in round._matches().enumerated() { template = template.appending(HtmlService.match(match: match).html(headName: headName, withRank: withRank, withScore: withScore)) } bracket = html.replacingOccurrences(of: "{{match-template}}", with: template) diff --git a/PadelClub/Utils/Tips.swift b/PadelClub/Utils/Tips.swift index 5f7c555..2a5abb4 100644 --- a/PadelClub/Utils/Tips.swift +++ b/PadelClub/Utils/Tips.swift @@ -412,6 +412,34 @@ struct TournamentRunningTip: Tip { } } +struct CreateAccountTip: Tip { + var title: Text { + Text("Créer votre compte Padel Club") + } + + var message: Text? { + let message = "Un compte est nécessaire pour publier le tournoi sur [Padel Club](\(URLs.main.rawValue)) et profiter de toutes du site, comme le mode TV pour transformer l'expérience de vos tournois !" + return Text(.init(message)) + } + + var image: Image? { + Image(systemName: "person.crop.circle") + } + + var actions: [Action] { + Action(id: ActionKey.createAccount.rawValue, title: "Créer votre compte") + //todo + //Action(id: ActionKey.learnMore.rawValue, title: "En savoir plus") + Action(id: ActionKey.accessPadelClubWebPage.rawValue, title: "Jeter un oeil au site Padel Club") + } + + enum ActionKey: String { + case createAccount = "createAccount" + case learnMore = "learnMore" + case accessPadelClubWebPage = "accessPadelClubWebPage" + } +} + struct TipStyleModifier: ViewModifier { @Environment(\.colorScheme) var colorScheme var tint: Color? diff --git a/PadelClub/ViewModel/SearchViewModel.swift b/PadelClub/ViewModel/SearchViewModel.swift index badbdd1..bc6ce97 100644 --- a/PadelClub/ViewModel/SearchViewModel.swift +++ b/PadelClub/ViewModel/SearchViewModel.swift @@ -7,6 +7,11 @@ import SwiftUI +class DebouncableViewModel: ObservableObject { + @Published var debouncableText: String = "" + var debounceTrigger: Double = 0.15 +} + class SearchViewModel: ObservableObject, Identifiable { let id: UUID = UUID() var allowSelection : Int = 0 diff --git a/PadelClub/ViewModel/Selectable.swift b/PadelClub/ViewModel/Selectable.swift index 201a4b9..0953678 100644 --- a/PadelClub/ViewModel/Selectable.swift +++ b/PadelClub/ViewModel/Selectable.swift @@ -13,6 +13,13 @@ protocol Selectable { func badgeValue() -> Int? func badgeImage() -> Badge? func badgeValueColor() -> Color? + func displayImageIfValueZero() -> Bool +} + +extension Selectable { + func displayImageIfValueZero() -> Bool { + return false + } } enum Badge { diff --git a/PadelClub/Views/Cashier/CashierView.swift b/PadelClub/Views/Cashier/CashierView.swift index e3c107e..dc708e9 100644 --- a/PadelClub/Views/Cashier/CashierView.swift +++ b/PadelClub/Views/Cashier/CashierView.swift @@ -8,39 +8,62 @@ import SwiftUI import Combine -struct CashierView: View { - @EnvironmentObject var dataStore: DataStore - var tournaments : [Tournament] - var teams: [TeamRegistration] - @State private var sortOption: SortOption = .callDate - @State private var filterOption: FilterOption = .all - @State private var sortOrder: SortOrder = .ascending - @State private var searchText = "" - @State private var isSearching: Bool = false - - init(tournament: Tournament, teams: [TeamRegistration]) { - self.tournaments = [tournament] - self.teams = teams - if tournament.hasEnded(), tournament.players().anySatisfy({ $0.hasPaid() == false }) { - _filterOption = .init(wrappedValue: .didNotPay) - } - if teams.filter({ $0.callDate != nil }).isEmpty { - _sortOption = .init(wrappedValue: .teamRank) - } else { - _sortOption = .init(wrappedValue: .callDate) - } - } +struct ShareableObject { - private func _sharedData() -> String { - let players = teams.filter({ _shouldDisplayTeam($0) }) - .flatMap({ $0.players().filter({ _shouldDisplayPlayer($0) }) }) + let cashierViewModel: CashierViewModel + let teams: [TeamRegistration] + let fileName: String + + func sharedData() async -> Data? { + let players = teams.filter({ cashierViewModel._shouldDisplayTeam($0) }) + .flatMap({ $0.players().filter({ cashierViewModel._shouldDisplayPlayer($0) }) }) .map { [$0.pasteData()] .compacted() .joined(separator: "\n") } .joined(separator: "\n\n") - return players + return players.data(using: .utf8) + } +} + +extension ShareableObject: Transferable { + enum ShareError: Error { + case failed + } + + static var transferRepresentation: some TransferRepresentation { + let rep = DataRepresentation(exportedContentType: .plainText) { object in + guard let data = await object.sharedData() else { + throw ShareError.failed + } + return data + } + return rep.suggestedFileName { object in object.fileName } + } +} + + +class CashierViewModel: ObservableObject { + let id: UUID = UUID() + @Published var sortOption: SortOption = .callDate + @Published var filterOption: FilterOption = .all + @Published var sortOrder: SortOrder = .ascending + @Published var searchText: String = "" + @Published var isSearching: Bool = false + + func _shouldDisplayTeam(_ team: TeamRegistration) -> Bool { + team.unsortedPlayers().anySatisfy({ + _shouldDisplayPlayer($0) + }) + } + + func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool { + if searchText.isEmpty == false { + filterOption.shouldDisplayPlayer(player) && player.contains(searchText) + } else { + filterOption.shouldDisplayPlayer(player) + } } enum SortOption: Int, Identifiable, CaseIterable { @@ -100,28 +123,43 @@ struct CashierView: View { } } } + +} + +struct CashierView: View { + @EnvironmentObject var dataStore: DataStore + @EnvironmentObject var cashierViewModel: CashierViewModel + + var tournaments : [Tournament] + var teams: [TeamRegistration] + @State private var shareableObject: ShareableObject? + + init(tournament: Tournament, teams: [TeamRegistration]) { + self.tournaments = [tournament] + self.teams = teams + } var body: some View { List { - if isSearching == false { + if cashierViewModel.isSearching == false { Section { - Picker(selection: $filterOption) { - ForEach(FilterOption.allCases) { filterOption in + Picker(selection: $cashierViewModel.filterOption) { + ForEach(CashierViewModel.FilterOption.allCases) { filterOption in Text(filterOption.localizedLabel()).tag(filterOption) } } label: { Text("Statut du règlement") } - Picker(selection: $sortOption) { - ForEach(SortOption.allCases) { sortOption in + Picker(selection: $cashierViewModel.sortOption) { + ForEach(CashierViewModel.SortOption.allCases) { sortOption in Text(sortOption.localizedLabel()).tag(sortOption) } } label: { Text("Affichage par") } - Picker(selection: $sortOrder) { + Picker(selection: $cashierViewModel.sortOrder) { Text("Croissant").tag(SortOrder.ascending) Text("Décroissant").tag(SortOrder.descending) } label: { @@ -131,12 +169,8 @@ struct CashierView: View { Text("Options d'affichage") } } - - if _isContentUnavailable() { - _contentUnavailableView() - } - switch sortOption { + switch cashierViewModel.sortOption { case .teamRank: _byTeamRankView() case .alphabeticalLastName: @@ -151,51 +185,53 @@ struct CashierView: View { _byCallDateView() } } + .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) + .onAppear { + cashierViewModel.searchText = "" +// if tournaments.count == 1 { +// if tournaments.first!.hasEnded() == true, tournaments.first!.players().anySatisfy({ $0.hasPaid() == false }) { +// filterOption = .didNotPay +// } +// } + if cashierViewModel.sortOption == .callDate && teams.first(where: { $0.callDate != nil }) == nil { + cashierViewModel.sortOption = .teamRank + } + self.shareableObject = ShareableObject(cashierViewModel: cashierViewModel, teams: teams, fileName: "Encaissement.txt") + } .headerProminence(.increased) - .searchable(text: $searchText, isPresented: $isSearching, prompt: Text("Chercher un joueur")) .toolbar { ToolbarItem(placement: .topBarTrailing) { - ShareLink(item: _sharedData().createTxtFile("bilan")) + if let shareableObject { + ShareLink( + item: shareableObject, + preview: SharePreview(shareableObject.fileName) + ) + } } } } - - @ViewBuilder - func computedPlayerView(_ player: PlayerRegistration) -> some View { - EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) - } - - private func _shouldDisplayTeam(_ team: TeamRegistration) -> Bool { - team.players().anySatisfy({ - _shouldDisplayPlayer($0) - }) - } - - private func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool { - if searchText.isEmpty == false { - filterOption.shouldDisplayPlayer(player) && player.contains(searchText) - } else { - filterOption.shouldDisplayPlayer(player) - } - } - + @ViewBuilder private func _byPlayer(_ players: [PlayerRegistration]) -> some View { - let _players = sortOrder == .ascending ? players : players.reversed() - ForEach(_players) { player in - Section { - computedPlayerView(player) - } header: { - HStack { - if let teamCallDate = player.team()?.callDate { - Text(teamCallDate.localizedDate()) + let _players = cashierViewModel.sortOrder == .ascending ? players : players.reversed() + if _players.isEmpty { + _contentUnavailableView() + } else { + ForEach(_players) { player in + Section { + EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) + } header: { + HStack { + if let teamCallDate = player.team()?.callDate { + Text(teamCallDate.localizedDate()) + } + Spacer() + Text(player.formattedRank()) + } + } footer: { + if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() { + Text(tournamentTitle) } - Spacer() - Text(player.computedRank.formatted()) - } - } footer: { - if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() { - Text(tournamentTitle) } } } @@ -203,42 +239,52 @@ struct CashierView: View { @ViewBuilder private func _byPlayerRank() -> some View { - let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.computedRank)).filter({ _shouldDisplayPlayer($0) }) + let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.computedRank)).filter({ cashierViewModel._shouldDisplayPlayer($0) }) _byPlayer(players) } @ViewBuilder private func _byPlayerAge() -> some View { - let players = teams.flatMap({ $0.players() }).filter({ $0.computedAge != nil }).sorted(using: .keyPath(\.computedAge!)).filter({ _shouldDisplayPlayer($0) }) + let players = teams.flatMap({ $0.unsortedPlayers() }).filter({ $0.computedAge != nil }).sorted(using: .keyPath(\.computedAge!)).filter({ cashierViewModel._shouldDisplayPlayer($0) }) _byPlayer(players) } @ViewBuilder private func _byPlayerLastName() -> some View { - let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.lastName)).filter({ _shouldDisplayPlayer($0) }) + let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.lastName)).filter({ cashierViewModel._shouldDisplayPlayer($0) }) _byPlayer(players) } @ViewBuilder private func _byPlayerFirstName() -> some View { - let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.firstName)).filter({ _shouldDisplayPlayer($0) }) + let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.firstName)).filter({ cashierViewModel._shouldDisplayPlayer($0) }) _byPlayer(players) } @ViewBuilder private func _byTeamRankView() -> some View { - let _teams = sortOrder == .ascending ? teams : teams.reversed() - ForEach(_teams) { team in - if _shouldDisplayTeam(team) { + let _teams = cashierViewModel.sortOrder == .ascending ? teams : teams.reversed() + let _filteredTeams = _teams.filter({ cashierViewModel._shouldDisplayTeam($0) }) + if _filteredTeams.isEmpty { + _contentUnavailableView() + } else { + ForEach(_filteredTeams) { team in Section { - _cashierPlayersView(team.players()) + ForEach(team.players()) { player in + if cashierViewModel._shouldDisplayPlayer(player) { + EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) + } + } } header: { HStack { if let callDate = team.callDate { Text(callDate.localizedDate()) } Spacer() - Text(team.weight.formatted()) + VStack(alignment: .trailing, spacing: 0) { + Text("Poids").font(.caption) + Text(team.weight.formatted()) + } } } footer: { if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { @@ -252,52 +298,40 @@ struct CashierView: View { @ViewBuilder private func _byCallDateView() -> some View { - let groupedTeams = Dictionary(grouping: teams) { team in + let _teams = teams.filter({ $0.callDate != nil && cashierViewModel._shouldDisplayTeam($0) }) + + if _teams.isEmpty { + _contentUnavailableView() + } + + let groupedTeams = Dictionary(grouping: _teams) { team in team.callDate } - let keys = sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed() + let keys = cashierViewModel.sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed() ForEach(keys, id: \.self) { key in if let _teams = groupedTeams[key] { ForEach(_teams) { team in - if _shouldDisplayTeam(team) { - Section { - _cashierPlayersView(team.players()) - } header: { - Text(key.localizedDate()) - } footer: { - if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { - Text(tournamentTitle) + Section { + ForEach(team.players()) { player in + if cashierViewModel._shouldDisplayPlayer(player) { + EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) } } + } header: { + Text(key.localizedDate()) + } footer: { + if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { + Text(tournamentTitle) + } } } } } } - - @ViewBuilder - private func _cashierPlayersView(_ players: [PlayerRegistration]) -> some View { - ForEach(players) { player in - if _shouldDisplayPlayer(player) { - computedPlayerView(player) - } - } - } - - private func _isContentUnavailable() -> Bool { - switch sortOption { - case .callDate: - return teams.filter({ $0.callDate != nil && _shouldDisplayTeam($0) }).isEmpty - case .teamRank: - return teams.filter({ _shouldDisplayTeam($0) }).isEmpty - default: - return teams.flatMap({ $0.players() }).filter({ _shouldDisplayPlayer($0) }).isEmpty - } - } private func _unavailableIcon() -> String { - switch sortOption { + switch cashierViewModel.sortOption { case .teamRank, .callDate: return "person.2.slash.fill" default: @@ -307,8 +341,8 @@ struct CashierView: View { @ViewBuilder private func _contentUnavailableView() -> some View { - if isSearching { - ContentUnavailableView.search(text: searchText) + if cashierViewModel.isSearching { + ContentUnavailableView.search(text: cashierViewModel.searchText) } else { ContentUnavailableView("Aucun résultat", systemImage: _unavailableIcon()) } diff --git a/PadelClub/Views/Club/ClubSearchView.swift b/PadelClub/Views/Club/ClubSearchView.swift index 44e284c..7f393d6 100644 --- a/PadelClub/Views/Club/ClubSearchView.swift +++ b/PadelClub/Views/Club/ClubSearchView.swift @@ -41,11 +41,6 @@ struct ClubSearchView: View { var club: Club? var selection: ((Club) -> ())? = nil - fileprivate class DebouncableViewModel: ObservableObject { - @Published var debouncableText: String = "" - var debounceTrigger: Double = 0.15 - } - private var distanceLimit: Measurement { Measurement(value: radius, unit: .kilometers) } diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index f404ef2..2c7fc9a 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -48,24 +48,47 @@ struct GenericDestinationPickerView: .id(destination.id) .buttonStyle(.plain) .overlay(alignment: .bottomTrailing) { - if let badge = destination.badgeImage() { - Image(systemName: badge.systemName()) - .foregroundColor(badge.color()) - .imageScale(.medium) - .background ( - Color(.systemBackground) - .clipShape(.circle) - ) - .offset(x: 3, y: 3) - } else if let count = destination.badgeValue(), count > 0 { - Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill") - .foregroundColor(destination.badgeValueColor() ?? .red) - .imageScale(.medium) - .background ( - Color(.systemBackground) - .clipShape(.circle) - ) - .offset(x: 3, y: 3) + if destination.displayImageIfValueZero() { + let count = destination.badgeValue() + if let count, count == 0, let badge = destination.badgeImage() { + Image(systemName: badge.systemName()) + .foregroundColor(badge.color()) + .imageScale(.medium) + .background ( + Color(.systemBackground) + .clipShape(.circle) + ) + .offset(x: 3, y: 3) + } else if let count, count > 0 { + Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill") + .foregroundColor(destination.badgeValueColor() ?? .red) + .imageScale(.medium) + .background ( + Color(.systemBackground) + .clipShape(.circle) + ) + .offset(x: 3, y: 3) + } + } else { + if let badge = destination.badgeImage() { + Image(systemName: badge.systemName()) + .foregroundColor(badge.color()) + .imageScale(.medium) + .background ( + Color(.systemBackground) + .clipShape(.circle) + ) + .offset(x: 3, y: 3) + } else if let count = destination.badgeValue(), count > 0 { + Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill") + .foregroundColor(destination.badgeValueColor() ?? .red) + .imageScale(.medium) + .background ( + Color(.systemBackground) + .clipShape(.circle) + ) + .offset(x: 3, y: 3) + } } } } diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 7d33725..caf27f2 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -17,18 +17,40 @@ extension String : Identifiable { struct BroadcastView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament + @Environment(NavigationViewModel.self) var navigation: NavigationViewModel + let context = CIContext() let filter = CIFilter.qrCodeGenerator() @State private var urlToShow: String? @State private var tvMode: Bool = false @State private var pageLink: PageLink = .teams + let createAccountTip = CreateAccountTip() let tournamentPublishingTip = TournamentPublishingTip() let tournamentTVBroadcastTip = TournamentTVBroadcastTip() var body: some View { @Bindable var tournament = tournament List { + if Store.main.userId == nil { + Section { + TipView(createAccountTip) { action in + switch action.id { + case CreateAccountTip.ActionKey.accessPadelClubWebPage.rawValue: + UIApplication.shared.open(URLs.main.url) + case CreateAccountTip.ActionKey.createAccount.rawValue: + navigation.selectedTab = .umpire + default: + break + //todo +// case CreateAccountTip.ActionKey.learnMore.rawValue: +// UIApplication.shared.open(URLs.padelClubLandingPage.url) + } + } + .tipStyle(tint: .master) + } + } + Section { TipView(tournamentPublishingTip) { action in UIApplication.shared.open(URLs.main.url) diff --git a/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift b/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift index 2f669e7..ddb8701 100644 --- a/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift @@ -13,6 +13,9 @@ struct PrintSettingsView: View { @StateObject var generator: HtmlGenerator @State private var presentShareView: Bool = false @State private var prepareGroupStage: Bool = false + @State private var generationId: UUID = UUID() + @State private var generationGroupStageId: UUID = UUID() + @State private var generating: Bool = false init(tournament: Tournament) { self.tournament = tournament @@ -63,26 +66,55 @@ struct PrintSettingsView: View { } header: { Text("Tableau principal") } + + if generating == false { + RowButtonView("Générer le PDF", systemImage: "printer") { + await MainActor.run() { + self.generating = true + } + generator.preparePDF { result in + switch result { + case .success(true): + if generator.includeGroupStage && generator.groupStageIsReady == false && tournament.groupStages().isEmpty == false { + self.prepareGroupStage = true + self.generationGroupStageId = UUID() + } else { + self.presentShareView = true + self.generating = false + } + case .success(false): + print("didn't save pdf") + break + case .failure(let error): + print(error) + break + } + } + self.prepareGroupStage = false + self.generationId = UUID() + } + .disabled(generator.includeBracket == false && generator.includeGroupStage == false && generator.includeLoserBracket == false) + } else { + LabeledContent { + ProgressView() + } label: { + Text("Préparation du PDF") + } + .id(generationId) + } } Section { NavigationLink { - WebView(htmlRawData: generator.generateHtml(), loadStatusChanged: { loaded, error, webView in - }) + WebViewPreview(bracket: true) + .environmentObject(generator) } label: { Text("Aperçu du tableau") } ForEach(tournament.groupStages()) { groupStage in NavigationLink { - WebView(htmlRawData: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false), loadStatusChanged: { loaded, error, webView in - if let error { - print("preparePDF", error) - } else if loaded == false { - generator.generateGroupStage(webView: webView) - } else { - print("preparePDF", "is loading") - } - }) + WebViewPreview(groupStage: groupStage) + .environmentObject(generator) } label: { Text("Aperçu de la \(groupStage.groupStageTitle())") } @@ -90,16 +122,59 @@ struct PrintSettingsView: View { } } .background { - WebView(htmlRawData: generator.generateHtml(), loadStatusChanged: { loaded, error, webView in - if let error { - print("preparePDF", error) - } else if loaded == false { - generator.generateWebView(webView: webView) - } else { - print("preparePDF", "is loading") - } - }).opacity(0) - + if generating { + _backgroundGenerationWebView() + _backgroundGroupStageWebView() + } + } + .navigationTitle("Imprimer") + .toolbarBackground(.visible, for: .navigationBar) + .navigationBarTitleDisplayMode(.inline) +// .toolbar { +// ToolbarItem(placement: .topBarTrailing) { +// Menu { +// Section { +// ShareLink(item: generator.generateHtml()) { +// Text("Tableau") +// } +// +// if let groupStage = tournament.groupStages().first { +// ShareLink(item: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false)) { +// Text("Poule") +// } +// } +// } header: { +// Text("Partager le code source HTML") +// } +// } label: { +// Label("Options", systemImage: "ellipsis.circle") +// } +// } +// } + .sheet(isPresented: $presentShareView) { + if let pdfURL = generator.pdfURL { + ShareSheet(urls: [pdfURL]) + } + } + } + + @ViewBuilder + private func _backgroundGenerationWebView() -> some View { + WebView(htmlRawData: generator.generateHtml(), loadStatusChanged: { loaded, error, webView in + if let error { + print("preparePDF", error) + } else if loaded == false { + generator.generateWebView(webView: webView) + } else { + print("preparePDF", "is loading") + } + }) + .opacity(0) + .id(generationId) + } + + private func _backgroundGroupStageWebView() -> some View { + Group { if prepareGroupStage { ForEach(tournament.groupStages()) { groupStage in WebView(htmlRawData: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false), loadStatusChanged: { loaded, error, webView in @@ -114,64 +189,7 @@ struct PrintSettingsView: View { } } } - .navigationTitle("Imprimer") - .toolbarBackground(.visible, for: .navigationBar) - .toolbarBackground(.visible, for: .bottomBar) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .bottomBar) { - Button { - generator.preparePDF { result in - switch result { - case .success(true): - if generator.includeGroupStage && generator.groupStageIsReady == false { - self.prepareGroupStage = true - } else { - self.presentShareView = true - } - case .success(false): - print("didn't save pdf") - break - case .failure(let error): - print(error) - break - } - } - - self.prepareGroupStage = false - self.generator.buildPDF() - - } label: { - Text("Obtenir le PDF") - } - .disabled(generator.includeBracket == false && generator.includeGroupStage == false && generator.includeLoserBracket == false) - .buttonStyle(.borderedProminent) - } - ToolbarItem(placement: .topBarTrailing) { - Menu { - Section { - ShareLink(item: generator.generateHtml()) { - Text("Tableau") - } - - if let groupStage = tournament.groupStages().first { - ShareLink(item: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false)) { - Text("Poule") - } - } - } header: { - Text("Partager le code source HTML") - } - } label: { - Label("Options", systemImage: "ellipsis.circle") - } - } - } - .sheet(isPresented: $presentShareView) { - if let pdfURL = generator.pdfURL { - ShareSheet(urls: [pdfURL]) - } - } + .id(generationGroupStageId) } } @@ -239,3 +257,34 @@ struct WebView: UIViewRepresentable { } } + +struct WebViewPreview: View { + @EnvironmentObject var generator: HtmlGenerator + let bracket: Bool + let groupStage: GroupStage? + + @State private var html: String? + + init(bracket: Bool = false, groupStage: GroupStage? = nil) { + self.bracket = bracket + self.groupStage = groupStage + } + + var body: some View { + Group { + if let html { + WebView(htmlRawData: html, loadStatusChanged: { loaded, error, webView in + }) + } else { + ProgressView() + .onAppear { + if let groupStage { + html = HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false) + } else { + html = generator.generateHtml() + } + } + } + } + } +} diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index a829f0a..f48afd9 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -42,6 +42,10 @@ enum CashierDestination: Identifiable, Selectable, Equatable { } } + func displayImageIfValueZero() -> Bool { + return true + } + func badgeValue() -> Int? { switch self { case .summary: @@ -51,7 +55,7 @@ enum CashierDestination: Identifiable, Selectable, Equatable { case .bracket(let round): return round.seeds().flatMap { $0.unsortedPlayers() }.filter({ $0.hasPaid() == false }).count case .all(let tournament): - return tournament.selectedPlayers().filter({ $0.hasPaid() == false }).count + return nil } } @@ -63,12 +67,10 @@ enum CashierDestination: Identifiable, Selectable, Equatable { switch self { case .summary: return nil - case .groupStage(let groupStage): - return groupStage.unsortedPlayers().allSatisfy({ $0.hasPaid() }) ? .checkmark : nil - case .bracket(let round): - return round.seeds().flatMap { $0.unsortedPlayers() }.allSatisfy({ $0.hasPaid() }) ? .checkmark : nil - case .all(let tournament): - return tournament.selectedPlayers().allSatisfy({ $0.hasPaid() }) ? .checkmark : nil + case .all: + return nil + default: + return .checkmark } } @@ -77,7 +79,8 @@ enum CashierDestination: Identifiable, Selectable, Equatable { struct TournamentCashierView: View { var tournament: Tournament @State private var selectedDestination: CashierDestination? - + @StateObject private var cashierViewModel: CashierViewModel = CashierViewModel() + func allDestinations() -> [CashierDestination] { var allDestinations : [CashierDestination] = [] let tournamentHasEnded = tournament.hasEnded() @@ -132,10 +135,13 @@ struct TournamentCashierView: View { CashierDetailView(tournament: tournament) case .groupStage(let groupStage): CashierView(tournament: tournament, teams: groupStage.teams()) + .environmentObject(cashierViewModel) case .bracket(let round): CashierView(tournament: tournament, teams: round.seeds()) + .environmentObject(cashierViewModel) case .all(let tournament): CashierView(tournament: tournament, teams: tournament.selectedSortedTeams()) + .environmentObject(cashierViewModel) } } } diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index 7db9a94..1860e54 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -20,7 +20,7 @@ struct TournamentBuildView: View { if tournament.hasEnded() { Section { NavigationLink(value: Screen.rankings) { - Text("Classement") + Text("Classement final des équipes") } } } @@ -30,7 +30,7 @@ struct TournamentBuildView: View { NavigationLink(value: Screen.groupStage) { LabeledContent { if let groupStageStatus { - Text(groupStageStatus) + Text(groupStageStatus).lineLimit(1) .multilineTextAlignment(.trailing) } else { ProgressView() @@ -51,7 +51,7 @@ struct TournamentBuildView: View { NavigationLink(value: Screen.round) { LabeledContent { if let bracketStatus { - Text(bracketStatus) + Text(bracketStatus).lineLimit(1) .multilineTextAlignment(.trailing) } else { ProgressView() @@ -82,7 +82,9 @@ struct TournamentBuildView: View { } label: { Text("Horaires") if let tournamentStatus { - Text(tournamentStatus.label) + Text(tournamentStatus.label).lineLimit(1) + } else { + Text(" ") } } } @@ -101,7 +103,9 @@ struct TournamentBuildView: View { } label: { Text("Convocations") if let tournamentStatus { - Text(tournamentStatus.label) + Text(tournamentStatus.label).lineLimit(1) + } else { + Text(" ") } } } @@ -122,7 +126,9 @@ struct TournamentBuildView: View { } label: { Text("Encaissement") if let tournamentStatus { - Text(tournamentStatus.label) + Text(tournamentStatus.label).lineLimit(1) + } else { + Text(" ") } } } diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index c96ff3f..ba3c6ef 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct TournamentInitView: View { var tournament: Tournament @@ -43,13 +44,21 @@ struct TournamentInitView: View { NavigationLink(value: Screen.broadcast) { LabeledContent { - if tournament.isPrivate { - Text("tournoi privé").foregroundStyle(.logoRed) + if Store.main.userId == nil { + Image(systemName: "xmark.circle.fill") + .foregroundStyle(.logoRed) } else { - Text("Automatique") + if tournament.isPrivate { + Text("tournoi privé").foregroundStyle(.logoRed) + } else { + Text("Automatique") + } } } label: { Text("Publication") + if Store.main.userId == nil { + Text("Un compte Padel Club est nécessaire") + } } } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 48c8fbf..6b93243 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -93,6 +93,7 @@ struct TournamentView: View { TournamentRankView() case .broadcast: BroadcastView() + .environment(navigation) case .event: if let event = tournament.eventObject() { EventView(event: event)