fix issue with ios 26

sync3
Razmig Sarkissian 2 months ago
parent cb4e2c5ed6
commit 3af1de6ff9
  1. 16
      PadelClub.xcodeproj/project.pbxproj
  2. 104
      PadelClub/Data/Federal/FederalTournament.swift
  3. 3
      PadelClub/PadelClubApp.swift
  4. 74
      PadelClub/Utils/Network/FederalDataService.swift
  5. 2
      PadelClub/Utils/Network/NetworkFederalService.swift
  6. 1
      PadelClub/ViewModel/FederalDataViewModel.swift
  7. 99
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  8. 5
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  9. 53
      PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift
  10. 66
      PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift
  11. 97
      PadelClub/Views/Navigation/MainView.swift
  12. 4
      PadelClub/Views/Navigation/OnboardingView.swift
  13. 1
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  14. 6
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift

@ -3139,6 +3139,7 @@
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
ENABLE_DEBUG_DYLIB = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club";
@ -3155,7 +3156,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.1; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -3185,6 +3186,7 @@
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
ENABLE_DEBUG_DYLIB = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club";
@ -3201,7 +3203,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.1; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -3304,6 +3306,7 @@
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
ENABLE_DEBUG_DYLIB = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (ProdTest)"; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (ProdTest)";
@ -3320,7 +3323,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.1; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -3349,6 +3352,7 @@
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
ENABLE_DEBUG_DYLIB = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (ProdTest)"; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (ProdTest)";
@ -3365,7 +3369,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.1; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -3409,7 +3413,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.1; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -3451,7 +3455,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.1; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",

@ -10,7 +10,7 @@ import PadelClubData
// MARK: - FederalTournament // MARK: - FederalTournament
struct FederalTournament: Identifiable, Codable { struct FederalTournament: Identifiable, Codable, Hashable {
func getEvent() -> Event { func getEvent() -> Event {
let club = DataStore.shared.user.clubsObjects().first(where: { $0.code == codeClub }) let club = DataStore.shared.user.clubsObjects().first(where: { $0.code == codeClub })
@ -313,7 +313,7 @@ extension FederalTournament: FederalTournamentHolder {
} }
// MARK: - CategorieAge // MARK: - CategorieAge
struct CategorieAge: Codable { struct CategorieAge: Codable, Hashable {
var ageJoueurMin, ageMin, ageJoueurMax, ageRechercheMax: Int? var ageJoueurMin, ageMin, ageJoueurMax, ageRechercheMax: Int?
var categoriesAgeTypePratique: [CategoriesAgeTypePratique]? var categoriesAgeTypePratique: [CategoriesAgeTypePratique]?
var ageMax: Int? var ageMax: Int?
@ -335,18 +335,18 @@ struct CategorieAge: Codable {
} }
// MARK: - CategoriesAgeTypePratique // MARK: - CategoriesAgeTypePratique
struct CategoriesAgeTypePratique: Codable { struct CategoriesAgeTypePratique: Codable, Hashable {
var id: ID? var id: ID?
} }
// MARK: - ID // MARK: - ID
struct ID: Codable { struct ID: Codable, Hashable {
var typePratique: String? var typePratique: String?
var idCategorieAge: Int? var idCategorieAge: Int?
} }
// MARK: - CategorieTournoi // MARK: - CategorieTournoi
struct CategorieTournoi: Codable { struct CategorieTournoi: Codable, Hashable {
var code, codeTaxe: String? var code, codeTaxe: String?
var compteurGda: CompteurGda? var compteurGda: CompteurGda?
var libelle, niveauHierarchique: String? var libelle, niveauHierarchique: String?
@ -354,14 +354,14 @@ struct CategorieTournoi: Codable {
} }
// MARK: - CompteurGda // MARK: - CompteurGda
struct CompteurGda: Codable { struct CompteurGda: Codable, Hashable {
var classementMax: Classement? var classementMax: Classement?
var libelle: String? var libelle: String?
var classementMin: Classement? var classementMin: Classement?
} }
// MARK: - Classement // MARK: - Classement
struct Classement: Codable { struct Classement: Codable, Hashable {
var nature, libelle: String? var nature, libelle: String?
var serie: Serie? var serie: Serie?
var sexe: String? var sexe: String?
@ -371,7 +371,7 @@ struct Classement: Codable {
} }
// MARK: - Serie // MARK: - Serie
struct Serie: Codable { struct Serie: Codable, Hashable {
var code, libelle: String? var code, libelle: String?
var valide: Bool? var valide: Bool?
var sexe: String? var sexe: String?
@ -382,7 +382,7 @@ struct Serie: Codable {
} }
// MARK: - Epreuve // MARK: - Epreuve
struct Epreuve: Codable { struct Epreuve: Codable, Hashable {
var inscriptionEnLigneEnCours: Bool? var inscriptionEnLigneEnCours: Bool?
var categorieAge: CategorieAge? var categorieAge: CategorieAge?
var typeEpreuve: TypeEpreuve? var typeEpreuve: TypeEpreuve?
@ -419,7 +419,7 @@ struct Epreuve: Codable {
} }
// MARK: - TypeEpreuve // MARK: - TypeEpreuve
struct TypeEpreuve: Codable { struct TypeEpreuve: Codable, Hashable {
let code: String? let code: String?
let delai: Int? let delai: Int?
let libelle: String? let libelle: String?
@ -437,12 +437,12 @@ struct TypeEpreuve: Codable {
} }
// MARK: - BorneAnneesNaissance // MARK: - BorneAnneesNaissance
struct BorneAnneesNaissance: Codable { struct BorneAnneesNaissance: Codable, Hashable {
var min, max: Int? var min, max: Int?
} }
// MARK: - Installation // MARK: - Installation
struct Installation: Codable { struct Installation: Codable, Hashable {
var ville: String? var ville: String?
var lng: Double? var lng: Double?
var surfaces: [JSONAny]? var surfaces: [JSONAny]?
@ -457,7 +457,7 @@ struct Installation: Codable {
} }
// MARK: - JugeArbitre // MARK: - JugeArbitre
struct JugeArbitre: Codable { struct JugeArbitre: Codable, Hashable {
var idCRM, id: Int? var idCRM, id: Int?
var nom, prenom: String? var nom, prenom: String?
@ -468,7 +468,7 @@ struct JugeArbitre: Codable {
} }
// MARK: - ModeleDeBalle // MARK: - ModeleDeBalle
struct ModeleDeBalle: Codable { struct ModeleDeBalle: Codable, Hashable {
var libelle: String? var libelle: String?
var marqueDeBalle: MarqueDeBalle? var marqueDeBalle: MarqueDeBalle?
var id: Int? var id: Int?
@ -476,7 +476,7 @@ struct ModeleDeBalle: Codable {
} }
// MARK: - MarqueDeBalle // MARK: - MarqueDeBalle
struct MarqueDeBalle: Codable { struct MarqueDeBalle: Codable, Hashable {
var id: Int? var id: Int?
var valide: Bool? var valide: Bool?
var marque: String? var marque: String?
@ -529,9 +529,13 @@ class JSONCodingKey: CodingKey {
} }
} }
class JSONAny: Codable { class JSONAny: Codable, Hashable, Equatable {
let value: Any var value: Any
init() {
self.value = ()
}
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError { static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny") let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
@ -722,4 +726,70 @@ class JSONAny: Codable {
try JSONAny.encode(to: &container, value: self.value) try JSONAny.encode(to: &container, value: self.value)
} }
} }
public static func == (lhs: JSONAny, rhs: JSONAny) -> Bool {
switch (lhs.value, rhs.value) {
case (let l as Bool, let r as Bool): return l == r
case (let l as Int64, let r as Int64): return l == r
case (let l as Double, let r as Double): return l == r
case (let l as String, let r as String): return l == r
case (let l as JSONNull, let r as JSONNull): return true
case (let l as [Any], let r as [Any]):
guard l.count == r.count else { return false }
return zip(l, r).allSatisfy { (a, b) in
// Recursively wrap in JSONAny for comparison
JSONAny(value: a) == JSONAny(value: b)
}
case (let l as [String: Any], let r as [String: Any]):
guard l.count == r.count else { return false }
for (key, lVal) in l {
guard let rVal = r[key], JSONAny(value: lVal) == JSONAny(value: rVal) else { return false }
}
return true
default:
return false
}
}
public func hash(into hasher: inout Hasher) {
switch value {
case let v as Bool:
hasher.combine(0)
hasher.combine(v)
case let v as Int64:
hasher.combine(1)
hasher.combine(v)
case let v as Double:
hasher.combine(2)
hasher.combine(v)
case let v as String:
hasher.combine(3)
hasher.combine(v)
case is JSONNull:
hasher.combine(4)
case let v as [Any]:
hasher.combine(5)
for elem in v {
JSONAny(value: elem).hash(into: &hasher)
}
case let v as [String: Any]:
hasher.combine(6)
// Order of hashing dictionary keys shouldn't matter
for key in v.keys.sorted() {
hasher.combine(key)
if let val = v[key] {
JSONAny(value: val).hash(into: &hasher)
}
}
default:
hasher.combine(-1)
}
}
// Helper init for internal use
convenience init(value: Any) {
self.init()
self.value = value
}
} }

@ -248,7 +248,8 @@ struct DownloadNewVersionView: View {
}.padding().background(.logoYellow) }.padding().background(.logoYellow)
.clipShape(.buttonBorder) .clipShape(.buttonBorder)
}.frame(maxWidth: .infinity) }
.frame(maxWidth: .infinity)
.foregroundStyle(.logoBackground) .foregroundStyle(.logoBackground)
.fontWeight(.medium) .fontWeight(.medium)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)

@ -240,7 +240,8 @@ class FederalDataService {
let queryString = urlComponents.query ?? "" let queryString = urlComponents.query ?? ""
// The servicePath now points to your backend's endpoint for all tournaments: 'fft/all-tournaments/' // The servicePath now points to your backend's endpoint for all tournaments: 'fft/all-tournaments/'
let urlRequest = try service._baseRequest(servicePath: "fft/all-tournaments?\(queryString)", method: .get, requiresToken: true) var urlRequest = try service._baseRequest(servicePath: "fft/all-tournaments?\(queryString)", method: .get, requiresToken: true)
urlRequest.timeoutInterval = 180
let (data, response) = try await URLSession.shared.data(for: urlRequest) let (data, response) = try await URLSession.shared.data(for: urlRequest)
@ -275,7 +276,8 @@ class FederalDataService {
// The servicePath now points to your backend's endpoint for umpire data: 'fft/umpire/{tournament_id}/' // The servicePath now points to your backend's endpoint for umpire data: 'fft/umpire/{tournament_id}/'
let servicePath = "fft/umpire/\(idTournament)/" let servicePath = "fft/umpire/\(idTournament)/"
let urlRequest = try service._baseRequest(servicePath: servicePath, method: .get, requiresToken: false) var urlRequest = try service._baseRequest(servicePath: servicePath, method: .get, requiresToken: false)
urlRequest.timeoutInterval = 120.0
let (data, response) = try await URLSession.shared.data(for: urlRequest) let (data, response) = try await URLSession.shared.data(for: urlRequest)
@ -297,72 +299,4 @@ class FederalDataService {
throw NetworkManagerError.apiError("Failed to decode UmpireContactInfo: \(error.localizedDescription)") throw NetworkManagerError.apiError("Failed to decode UmpireContactInfo: \(error.localizedDescription)")
} }
} }
/// Fetches umpire contact data for multiple tournament IDs.
/// This function calls your backend endpoint that handles multiple tournament IDs via query parameters.
/// - Parameter tournamentIds: An array of tournament ID strings.
/// - Returns: A dictionary mapping tournament IDs to tuples `(name: String?, email: String?, phone: String?)` containing the umpire's contact info.
/// - Throws: An error if the network request fails or decoding the response is unsuccessful.
func getUmpiresData(tournamentIds: [String]) async throws -> [String: (name: String?, email: String?, phone: String?)] {
let service = try StoreCenter.main.service()
// Validate input
guard !tournamentIds.isEmpty else {
throw NetworkManagerError.apiError("Tournament IDs array cannot be empty")
}
// Create the base service path
let basePath = "fft/umpires/"
// Build query parameters - join tournament IDs with commas
let tournamentIdsParam = tournamentIds.joined(separator: ",")
let queryItems = [URLQueryItem(name: "tournament_ids", value: tournamentIdsParam)]
// Create the URL with query parameters
var urlComponents = URLComponents()
urlComponents.queryItems = queryItems
let servicePath = basePath + (urlComponents.url?.query.map { "?\($0)" } ?? "")
let urlRequest = try service._baseRequest(servicePath: servicePath, method: .get, requiresToken: false)
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard !data.isEmpty else {
throw NetworkManagerError.noDataReceived
}
// Check for HTTP errors
guard httpResponse.statusCode == 200 else {
if let errorData = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let message = errorData["message"] as? String {
throw NetworkManagerError.apiError("Server error: \(message)")
}
throw NetworkManagerError.apiError("HTTP error: \(httpResponse.statusCode)")
}
do {
let umpireResponse = try JSONDecoder().decode(UmpireDataResponse.self, from: data)
// Convert the results to the expected return format
var resultDict: [String: (name: String?, email: String?, phone: String?)] = [:]
for (tournamentId, umpireInfo) in umpireResponse.results {
resultDict[tournamentId] = (name: umpireInfo.name, email: umpireInfo.email, phone: umpireInfo.phone)
}
print("Umpire data fetched for \(resultDict.count) tournaments")
return resultDict
} catch {
print("Decoding error for UmpireDataResponse: \(error)")
throw NetworkManagerError.apiError("Failed to decode UmpireDataResponse: \(error.localizedDescription)")
}
}
} }

@ -93,7 +93,7 @@ class NetworkFederalService {
//"geocoding%5Bcountry%5D=fr&geocoding%5Bville%5D=13%20Avenue%20Emile%20Bodin%2013260%20Cassis&geocoding%5Brayon%5D=15&geocoding%5BuserPosition%5D%5Blat%5D=43.22278594081477&geocoding%5BuserPosition%5D%5Blng%5D=5.556953900769194&geocoding%5BuserPosition%5D%5BshowDistance%5D=true&nombreResultat=0&diplomeEtatOption=false&galaxieOption=false&fauteuilOption=false&tennisSanteOption=false" //"geocoding%5Bcountry%5D=fr&geocoding%5Bville%5D=13%20Avenue%20Emile%20Bodin%2013260%20Cassis&geocoding%5Brayon%5D=15&geocoding%5BuserPosition%5D%5Blat%5D=43.22278594081477&geocoding%5BuserPosition%5D%5Blng%5D=5.556953900769194&geocoding%5BuserPosition%5D%5BshowDistance%5D=true&nombreResultat=0&diplomeEtatOption=false&galaxieOption=false&fauteuilOption=false&tennisSanteOption=false"
let postData = parameters.data(using: .utf8) let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://tenup.fft.fr/recherche/clubs/ajax")!,timeoutInterval: Double.infinity) var request = URLRequest(url: URL(string: "https://tenup.fft.fr/recherche/clubs/ajax")!)
request.addValue("application/json, text/plain, */*", forHTTPHeaderField: "Accept") request.addValue("application/json, text/plain, */*", forHTTPHeaderField: "Accept")
request.addValue("fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3", forHTTPHeaderField: "Accept-Language") request.addValue("fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3", forHTTPHeaderField: "Accept-Language")
request.addValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding") request.addValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding")

@ -70,6 +70,7 @@ class FederalDataViewModel {
selectedClubs.removeAll() selectedClubs.removeAll()
dayPeriod = .all dayPeriod = .all
dayDuration = nil dayDuration = nil
weekdays.removeAll()
id = UUID() id = UUID()
} }

@ -224,6 +224,12 @@ struct ActivityView: View {
.navigationDestination(for: Tournament.self) { tournament in .navigationDestination(for: Tournament.self) { tournament in
TournamentView(tournament: tournament) TournamentView(tournament: tournament)
} }
.navigationDestination(for: SubScreen.self) { build in
switch build {
case .subscription(let federalTournament, let build):
TournamentSubscriptionView(federalTournament: federalTournament, build: build, user: dataStore.user)
}
}
// .onDisappear(perform: { // .onDisappear(perform: {
// pasteButtonIsDisplayed = nil // pasteButtonIsDisplayed = nil
// print("disappearing", "pasteButtonIsDisplayed", pasteButtonIsDisplayed) // print("disappearing", "pasteButtonIsDisplayed", pasteButtonIsDisplayed)
@ -231,6 +237,7 @@ struct ActivityView: View {
.toolbar { .toolbar {
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {
if #available(iOS 26.0, *) { if #available(iOS 26.0, *) {
if viewStyle == .calendar {
Button("Vue calendrier", systemImage: "calendar") { Button("Vue calendrier", systemImage: "calendar") {
switch viewStyle { switch viewStyle {
case .list: case .list:
@ -239,7 +246,17 @@ struct ActivityView: View {
viewStyle = .list viewStyle = .list
} }
} }
.symbolVariant(viewStyle == .calendar ? .fill : .none) .buttonStyle(.borderedProminent)
} else {
Button("Vue calendrier", systemImage: "calendar") {
switch viewStyle {
case .list:
viewStyle = .calendar
case .calendar:
viewStyle = .list
}
}
}
} else { } else {
Button { Button {
switch viewStyle { switch viewStyle {
@ -265,11 +282,16 @@ struct ActivityView: View {
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {
if #available(iOS 26.0, *) { if #available(iOS 26.0, *) {
if federalDataViewModel.areFiltersEnabled() {
Button("Filtre", systemImage: "line.3.horizontal.decrease") { Button("Filtre", systemImage: "line.3.horizontal.decrease") {
presentFilterView.toggle() presentFilterView.toggle()
} }
.symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none) .buttonStyle(.borderedProminent)
} else {
Button("Filtre", systemImage: "line.3.horizontal.decrease") {
presentFilterView.toggle()
}
}
} else { } else {
Button { Button {
presentFilterView.toggle() presentFilterView.toggle()
@ -308,35 +330,10 @@ struct ActivityView: View {
} }
} }
if tournaments.isEmpty == false, federalDataViewModel.areFiltersEnabled() || navigation.agendaDestination == .around { if #unavailable(iOS 26.0) {
if _shouldDisplaySearchStatus() {
ToolbarItemGroup(placement: .bottomBar) { ToolbarItemGroup(placement: .bottomBar) {
VStack(spacing: 0) { _searchBoxView()
let searchStatus = _searchStatus()
if searchStatus.isEmpty == false {
Text(_searchStatus())
.font(.footnote)
.foregroundStyle(.secondary)
}
HStack {
if navigation.agendaDestination == .around {
FooterButtonView("modifier votre recherche") {
displaySearchView = true
}
if federalDataViewModel.areFiltersEnabled() {
Text("ou")
}
}
if federalDataViewModel.areFiltersEnabled() {
FooterButtonView(_filterButtonTitle()) {
presentFilterView = true
}
}
}
.padding(.bottom, 8)
} }
} }
} }
@ -446,6 +443,41 @@ struct ActivityView: View {
} }
} }
private func _shouldDisplaySearchStatus() -> Bool {
tournaments.isEmpty == false && (federalDataViewModel.areFiltersEnabled() || navigation.agendaDestination == .around)
}
private func _searchBoxView() -> some View {
VStack(spacing: 0) {
let searchStatus = _searchStatus()
if searchStatus.isEmpty == false {
Text(_searchStatus())
.font(.footnote)
.foregroundStyle(.secondary)
}
HStack {
if navigation.agendaDestination == .around {
FooterButtonView("modifier votre recherche") {
displaySearchView = true
}
if federalDataViewModel.areFiltersEnabled() {
Text("ou")
}
}
if federalDataViewModel.areFiltersEnabled() {
FooterButtonView(_filterButtonTitle()) {
presentFilterView = true
}
}
}
.padding(.bottom, 8)
}
}
private func _searchStatus() -> String { private func _searchStatus() -> String {
var searchStatus : [String] = [] var searchStatus : [String] = []
if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false { if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false {
@ -623,3 +655,8 @@ struct ActivityView: View {
//#Preview { //#Preview {
// ActivityView() // ActivityView()
//} //}
enum SubScreen: Hashable {
case subscription(FederalTournament, TournamentBuild)
}

@ -95,9 +95,14 @@ struct CalendarView: View {
if federalDataViewModel.isFederalTournamentValidForFilters(tournament, build: build) { if federalDataViewModel.isFederalTournamentValidForFilters(tournament, build: build) {
if navigation.agendaDestination == .around { if navigation.agendaDestination == .around {
if #available(iOS 26.0, *) {
NavigationLink(build.buildHolderTitle(.wide), value: SubScreen.subscription(tournament, build as! TournamentBuild))
} else {
NavigationLink(build.buildHolderTitle(.wide)) { NavigationLink(build.buildHolderTitle(.wide)) {
TournamentSubscriptionView(federalTournament: tournament, build: build, user: dataStore.user) TournamentSubscriptionView(federalTournament: tournament, build: build, user: dataStore.user)
} }
}
} else { } else {
Button(build.buildHolderTitle(.wide)) { Button(build.buildHolderTitle(.wide)) {
_createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build) _createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build)

@ -173,8 +173,7 @@ struct TournamentLookUpView: View {
Spacer() Spacer()
ProgressView() ProgressView()
if total > 0 { if total > 0 {
let percent = Double(tournaments.count) / Double(total) Text("\(total) tournois en cours de récupération")
Text(percent.formatted(.percent.precision(.significantDigits(1...3))) + " en récupération de Tenup")
.font(.caption) .font(.caption)
} }
Spacer() Spacer()
@ -211,6 +210,7 @@ struct TournamentLookUpView: View {
revealSearchParameters = true revealSearchParameters = true
federalDataViewModel.searchedFederalTournaments = [] federalDataViewModel.searchedFederalTournaments = []
federalDataViewModel.searchAttemptCount = 0 federalDataViewModel.searchAttemptCount = 0
federalDataViewModel.removeFilters()
} label: { } label: {
Text("Ré-initialiser la recherche") Text("Ré-initialiser la recherche")
} }
@ -239,12 +239,28 @@ struct TournamentLookUpView: View {
private func _gatherNumbers() { private func _gatherNumbers() {
Task { Task {
print("Doing.....") print("Doing.....")
let tournamentsToFetch = tournaments.enumerated().filter { (idx, tournament) in
tournament.japPhoneNumber == nil || tournament.japPhoneNumber?.isEmpty == true
}
let idIndexPairs: [(Int, String)] = tournamentsToFetch.map { ($0.offset, $0.element.id) }
let tournamentIDs: [String] = idIndexPairs.map { $0.1 }
guard !tournamentIDs.isEmpty else {
print("All numbers already gathered.")
return
}
await withTaskGroup(of: (Int, String?).self) { group in // Split into batches of 100
for i in 0..<tournaments.count { let batchSize = 100
let tournamentID = tournaments[i].id let batches = idIndexPairs.chunked(into: batchSize)
let index = i // Capture index for use in the child task
print("Processing \(idIndexPairs.count) tournaments in \(batches.count) batches of \(batchSize)")
// Process each batch sequentially
for (batchIndex, batch) in batches.enumerated() {
print("Starting batch \(batchIndex + 1) of \(batches.count) (\(batch.count) tournaments)")
await withTaskGroup(of: (Int, String?).self) { group in
for (index, tournamentID) in batch {
group.addTask { group.addTask {
print("Starting task for tournament \(index) / \(self.tournaments.count)") print("Starting task for tournament \(index) / \(self.tournaments.count)")
let phone = try? await NetworkFederalService.shared.getUmpireData(idTournament: tournamentID).phone let phone = try? await NetworkFederalService.shared.getUmpireData(idTournament: tournamentID).phone
@ -259,6 +275,10 @@ struct TournamentLookUpView: View {
federalDataViewModel.searchedFederalTournaments[index] = tournamentData // Assign back federalDataViewModel.searchedFederalTournaments[index] = tournamentData // Assign back
} }
} }
print("Completed batch \(batchIndex + 1) of \(batches.count)")
}
print(".....Done") print(".....Done")
} }
} }
@ -271,7 +291,6 @@ struct TournamentLookUpView: View {
federalDataViewModel.searchedFederalTournaments = [] federalDataViewModel.searchedFederalTournaments = []
searching = true searching = true
requestedToGetAllPages = false requestedToGetAllPages = false
federalDataViewModel.weekdays = dataStore.appSettings.weekdays
federalDataViewModel.searchAttemptCount += 1 federalDataViewModel.searchAttemptCount += 1
federalDataViewModel.dayPeriod = dataStore.appSettings.dayPeriod federalDataViewModel.dayPeriod = dataStore.appSettings.dayPeriod
federalDataViewModel.dayDuration = dataStore.appSettings.dayDuration federalDataViewModel.dayDuration = dataStore.appSettings.dayDuration
@ -339,7 +358,7 @@ struct TournamentLookUpView: View {
print("count", count, total, tournaments.count, page) print("count", count, total, tournaments.count, page)
total = count total = count
if tournaments.count < count && page < total / 30 { if total - tournaments.count > count / 50 && page < total / 30 {
if total < 200 || requestedToGetAllPages { if total < 200 || requestedToGetAllPages {
page += 1 page += 1
await getNewPage() await getNewPage()
@ -395,8 +414,18 @@ struct TournamentLookUpView: View {
appSettings.endDate = Date().endOfMonth.nextDay.endOfMonth.nextDay.endOfMonth appSettings.endDate = Date().endOfMonth.nextDay.endOfMonth.nextDay.endOfMonth
} }
} }
DatePicker("Début", selection: $appSettings.startDate, displayedComponents: .date) DatePicker(selection: $appSettings.startDate, displayedComponents: .date) {
DatePicker("Fin", selection: $appSettings.endDate, displayedComponents: .date) Text("Début")
.onTapGesture(count: 2) {
appSettings.startDate = appSettings.startDate.startOfCurrentMonth
}
}
DatePicker(selection: $appSettings.endDate, displayedComponents: .date) {
Text("Fin")
.onTapGesture(count: 2) {
appSettings.endDate = appSettings.endDate.nextDay.endOfMonth
}
}
Picker(selection: $appSettings.dayDuration) { Picker(selection: $appSettings.dayDuration) {
Text("Aucune").tag(nil as Int?) Text("Aucune").tag(nil as Int?)
Text(1.formatted()).tag(1 as Int?) Text(1.formatted()).tag(1 as Int?)
@ -406,7 +435,8 @@ struct TournamentLookUpView: View {
Text("Durée souhaitée (en jours)") Text("Durée souhaitée (en jours)")
} }
WeekdayselectionView(weekdays: $appSettings.weekdays) @Bindable var federalDataViewModel = federalDataViewModel
WeekdayselectionView(weekdays: $federalDataViewModel.weekdays)
Picker(selection: $appSettings.dayPeriod) { Picker(selection: $appSettings.dayPeriod) {
ForEach(DayPeriod.allCases) { ForEach(DayPeriod.allCases) {
@ -449,7 +479,6 @@ struct TournamentLookUpView: View {
} }
.symbolVariant(.fill) .symbolVariant(.fill)
.foregroundColor (Color.white) .foregroundColor (Color.white)
.cornerRadius (20)
.font(.system(size: 12)) .font(.system(size: 12))
} }
} }

@ -22,6 +22,7 @@ struct TournamentSubscriptionView: View {
@State private var didSendMessage: Bool = false @State private var didSendMessage: Bool = false
@State private var didSaveInCalendar: Bool = false @State private var didSaveInCalendar: Bool = false
@State private var phoneNumber: String? = nil @State private var phoneNumber: String? = nil
@State private var errorWhenGatheringPhone: Bool = false
init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: CustomUser) { init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: CustomUser) {
self.federalTournament = federalTournament self.federalTournament = federalTournament
@ -111,9 +112,13 @@ struct TournamentSubscriptionView: View {
Text(federalTournament.phoneLabel()) Text(federalTournament.phoneLabel())
} }
if let phoneNumber {
LabeledContent("Téléphone JAP") { LabeledContent("Téléphone JAP") {
if let phoneNumber {
Text(phoneNumber) Text(phoneNumber)
} else if errorWhenGatheringPhone == false {
ProgressView()
} else {
Image(systemName: "exclamationmark.triangle")
} }
} }
} header: { } header: {
@ -163,8 +168,15 @@ struct TournamentSubscriptionView: View {
CopyPasteButtonView(pasteValue: messageBody) CopyPasteButtonView(pasteValue: messageBody)
} }
} }
.ifAvailableiOS26 { view in
view.toolbar(.hidden, for: .tabBar)
}
.task { .task {
self.phoneNumber = try? await NetworkFederalService.shared.getUmpireData(idTournament: federalTournament.id).phone do {
self.phoneNumber = try await NetworkFederalService.shared.getUmpireData(idTournament: federalTournament.id).phone
} catch {
self.errorWhenGatheringPhone = true
}
} }
.toolbarBackground(.visible, for: .bottomBar) .toolbarBackground(.visible, for: .bottomBar)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
@ -176,24 +188,24 @@ struct TournamentSubscriptionView: View {
} }
} }
.toolbar(content: { .toolbar(content: {
ToolbarItem(placement: .status) { if #available(iOS 26.0, *) {
ToolbarSpacer(placement: .bottomBar)
}
ToolbarItem(placement: .bottomBar) {
Menu {
Menu { Menu {
if let courrielEngagement = federalTournament.courrielEngagement { if let courrielEngagement = federalTournament.courrielEngagement {
Section { Button("Email", systemImage: "envelope") {
RowButtonView("S'inscrire par email", systemImage: "envelope") {
contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: messageBody, subject: messageSubject, tournamentBuild: build as? TournamentBuild) contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: messageBody, subject: messageSubject, tournamentBuild: build as? TournamentBuild)
} }
} }
}
if let telephone = phoneNumber { if let telephone = phoneNumber {
if telephone.isMobileNumber() { if telephone.isMobileNumber() {
Section { Button("Message", systemImage: "message") {
RowButtonView("S'inscrire par message", systemImage: "message") {
contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild) contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild)
} }
} }
}
let number = telephone.replacingOccurrences(of: " ", with: "") let number = telephone.replacingOccurrences(of: " ", with: "")
if let url = URL(string: "tel:\(number)") { if let url = URL(string: "tel:\(number)") {
Link(destination: url) { Link(destination: url) {
@ -201,26 +213,36 @@ struct TournamentSubscriptionView: View {
} }
} }
} }
} label: {
Label("Inscription", systemImage: "pencil.and.list.clipboard")
}
Menu {
if let installation = federalTournament.installation, let telephone = installation.telephone { if let installation = federalTournament.installation, let telephone = installation.telephone {
Section { Button("Email", systemImage: "envelope") {
RowButtonView("Contacter le club", systemImage: "house.and.flag") {
contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild) contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild)
} }
}
let number = telephone.replacingOccurrences(of: " ", with: "") let number = telephone.replacingOccurrences(of: " ", with: "")
if let url = URL(string: "tel:\(number)") { if let url = URL(string: "tel:\(number)") {
Link(destination: url) { Link(destination: url) {
Label("Appeler le club", systemImage: "phone") Label("Appeler", systemImage: "phone")
} }
} }
} }
} label: {
Label("Contacter le club", systemImage: "house.and.flag")
}
} label: { } label: {
Text("Contact et inscription") Text("S'inscrire")
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
} }
.menuStyle(.button) .menuStyle(.button)
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.offset(y:-2) }
if #available(iOS 26.0, *) {
ToolbarSpacer(placement: .bottomBar)
} }
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
@ -361,3 +383,17 @@ struct TournamentSubscriptionView: View {
} }
} }
extension View {
/// Runs a transform only on iOS 26+, otherwise returns self
@ViewBuilder
func ifAvailableiOS26<Content: View>(
@ViewBuilder transform: (Self) -> Content
) -> some View {
if #available(iOS 26.0, *) {
transform(self)
} else {
self
}
}
}

@ -17,10 +17,13 @@ struct MainView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@Environment(ImportObserver.self) private var importObserver: ImportObserver @Environment(ImportObserver.self) private var importObserver: ImportObserver
@State private var federalDataViewModel: FederalDataViewModel = FederalDataViewModel.shared
@State private var mainViewId: UUID = UUID() @State private var mainViewId: UUID = UUID()
@State private var presentOnboarding: Bool = false @State private var presentOnboarding: Bool = false
@State private var canPresentOnboarding: Bool = false @State private var canPresentOnboarding: Bool = false
@State private var presentFilterView: Bool = false
@State private var displaySearchView: Bool = false
@AppStorage("didSeeOnboarding") private var didSeeOnboarding: Bool = false @AppStorage("didSeeOnboarding") private var didSeeOnboarding: Bool = false
@ -94,6 +97,23 @@ struct MainView: View {
// PadelClubView() // PadelClubView()
// .tabItem(for: .padelClub) // .tabItem(for: .padelClub)
} }
.applyTabViewBottomAccessory(content: {
if (navigation.selectedTab == .activity || navigation.selectedTab == nil) && _shouldDisplaySearchStatus() {
_searchBoxView()
}
})
.sheet(isPresented: $presentFilterView) {
TournamentFilterView(federalDataViewModel: federalDataViewModel)
.environment(navigation)
.tint(.master)
}
.sheet(isPresented: $displaySearchView) {
NavigationStack {
TournamentLookUpView()
.environment(federalDataViewModel)
.environment(navigation)
}
}
.onAppear { .onAppear {
if canPresentOnboarding || StoreCenter.main.userId != nil { if canPresentOnboarding || StoreCenter.main.userId != nil {
if didSeeOnboarding == false { if didSeeOnboarding == false {
@ -264,8 +284,85 @@ struct MainView: View {
} }
} }
} }
private func _searchStatus() -> String {
var searchStatus : [String] = []
if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false {
let filteredSearchedFederalTournaments = federalDataViewModel.filteredSearchedFederalTournaments
let status : String = filteredSearchedFederalTournaments.count.formatted() + " tournoi" + filteredSearchedFederalTournaments.count.pluralSuffix
searchStatus.append(status)
}
if federalDataViewModel.areFiltersEnabled() {
searchStatus.append(federalDataViewModel.filterStatus())
}
return searchStatus.joined(separator: " ")
}
private func _shouldDisplaySearchStatus() -> Bool {
guard navigation.path.count == 0 else { return false }
return federalDataViewModel.areFiltersEnabled() || (navigation.agendaDestination == .around && federalDataViewModel.searchedFederalTournaments.isEmpty == false)
}
private func _searchBoxView() -> some View {
VStack(spacing: 0) {
let searchStatus = _searchStatus()
if searchStatus.isEmpty == false {
Text(_searchStatus())
.font(.footnote)
.foregroundStyle(.secondary)
}
HStack {
if navigation.agendaDestination == .around {
FooterButtonView("modifier votre recherche") {
displaySearchView = true
}
if federalDataViewModel.areFiltersEnabled() {
Text("ou")
}
}
if federalDataViewModel.areFiltersEnabled() {
FooterButtonView(_filterButtonTitle()) {
presentFilterView = true
}
}
}
}
}
private func _filterButtonTitle() -> String {
var prefix = "modifier "
if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false {
prefix = ""
}
return prefix + "vos filtres"
}
} }
//#Preview { //#Preview {
// MainView() // MainView()
//} //}
fileprivate extension View {
@ViewBuilder
func applyTabViewBottomAccessory<Content: View>(
@ViewBuilder content: () -> Content
) -> some View {
if #available(iOS 26.0, *) {
self.tabViewBottomAccessory {
content()
}
} else {
self
}
}
}

@ -59,6 +59,10 @@ struct OnboardingView: View {
dismiss() dismiss()
navigation.agendaDestination = .around navigation.agendaDestination = .around
}), }),
("Accès au classement mensuel", {
dismiss()
navigation.selectedTab = .toolbox
}),
("Calculateur de points", { ("Calculateur de points", {
dismiss() dismiss()
navigation.selectedTab = .toolbox navigation.selectedTab = .toolbox

@ -65,6 +65,7 @@ struct ToolboxView: View {
Section { Section {
NavigationLink { NavigationLink {
SelectablePlayerListView(isPresented: false, lastDataSource: true) SelectablePlayerListView(isPresented: false, lastDataSource: true)
.toolbar(.hidden, for: .tabBar)
} label: { } label: {
Label("Rechercher un joueur", systemImage: "person.fill.viewfinder") Label("Rechercher un joueur", systemImage: "person.fill.viewfinder")
} }

@ -28,11 +28,17 @@ struct TournamentCellView: View {
if let federalTournament = tournament as? FederalTournament { if let federalTournament = tournament as? FederalTournament {
if FederalDataViewModel.shared.isFederalTournamentValidForFilters(federalTournament, build: build) { if FederalDataViewModel.shared.isFederalTournamentValidForFilters(federalTournament, build: build) {
if navigation.agendaDestination == .around { if navigation.agendaDestination == .around {
if #available(iOS 26.0, *) {
NavigationLink(value: SubScreen.subscription(federalTournament, build as! TournamentBuild)) {
_buildView(build, existingTournament: event?.existingBuild(build))
}
} else {
NavigationLink { NavigationLink {
TournamentSubscriptionView(federalTournament: federalTournament, build: build, user: dataStore.user) TournamentSubscriptionView(federalTournament: federalTournament, build: build, user: dataStore.user)
} label: { } label: {
_buildView(build, existingTournament: event?.existingBuild(build)) _buildView(build, existingTournament: event?.existingBuild(build))
} }
}
} else { } else {
_buildView(build, existingTournament: event?.existingBuild(build)) _buildView(build, existingTournament: event?.existingBuild(build))
} }

Loading…
Cancel
Save