fix some stuff on team selection and add a special animation mode for selecting players from club

sync2
Raz 1 year ago
parent eca125ef73
commit 6e68226ac7
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 10
      PadelClub/Data/Federal/FederalTournament.swift
  3. 4
      PadelClub/Data/Federal/FederalTournamentHolder.swift
  4. 41
      PadelClub/Data/Tournament.swift
  5. 1
      PadelClub/Extensions/String+Extensions.swift
  6. 38
      PadelClub/Utils/DisplayContext.swift
  7. 8
      PadelClub/Utils/PadelRule.swift
  8. 2
      PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift
  9. 2
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  10. 1
      PadelClub/Views/Shared/ImportedPlayerView.swift
  11. 12
      PadelClub/Views/Shared/SelectablePlayerListView.swift
  12. 42
      PadelClub/Views/Tournament/Screen/AddTeamView.swift
  13. 15
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift

@ -3134,7 +3134,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -3179,7 +3179,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -210,9 +210,17 @@ extension FederalTournament: FederalTournamentHolder {
nomClub ?? villeEngagement ?? installation?.nom ?? "" nomClub ?? villeEngagement ?? installation?.nom ?? ""
} }
func subtitleLabel() -> String { func subtitleLabel(forBuild build: any TournamentBuildHolder) -> String {
"" ""
} }
func tournamentTitle(_ displayStyle: DisplayStyle, forBuild build: any TournamentBuildHolder) -> String {
build.level.localizedLevelLabel(displayStyle)
}
func displayAgeAndCategory(forBuild build: any TournamentBuildHolder) -> Bool {
true
}
} }
// MARK: - CategorieAge // MARK: - CategorieAge

@ -14,9 +14,11 @@ protocol FederalTournamentHolder {
var codeClub: String? { get } var codeClub: String? { get }
var tournaments: [any TournamentBuildHolder] { get } var tournaments: [any TournamentBuildHolder] { get }
func clubLabel() -> String func clubLabel() -> String
func subtitleLabel() -> String func subtitleLabel(forBuild build: any TournamentBuildHolder) -> String
var dayDuration: Int { get } var dayDuration: Int { get }
var dayPeriod: DayPeriod { get } var dayPeriod: DayPeriod { get }
func tournamentTitle(_ displayStyle: DisplayStyle, forBuild build: any TournamentBuildHolder) -> String
func displayAgeAndCategory(forBuild build: any TournamentBuildHolder) -> Bool
} }
extension FederalTournamentHolder { extension FederalTournamentHolder {

@ -2251,6 +2251,20 @@ extension Tournament: Hashable {
} }
extension Tournament: FederalTournamentHolder { extension Tournament: FederalTournamentHolder {
func tournamentTitle(_ displayStyle: DisplayStyle, forBuild build: any TournamentBuildHolder) -> String {
if isAnimation() {
if let name {
return name.trunc(length: DeviceHelper.charLength())
} else if build.age == .unlisted, build.category == .unlisted {
return build.level.localizedLevelLabel(.title)
} else {
return build.level.localizedLevelLabel(displayStyle)
}
}
return build.level.localizedLevelLabel(displayStyle)
}
var codeClub: String? { var codeClub: String? {
club()?.code club()?.code
} }
@ -2261,8 +2275,18 @@ extension Tournament: FederalTournamentHolder {
locationLabel() locationLabel()
} }
func subtitleLabel() -> String { func subtitleLabel(forBuild build: any TournamentBuildHolder) -> String {
subtitle() if isAnimation() {
if displayAgeAndCategory(forBuild: build) == false {
return [build.category.localizedLabel(), build.age.localizedLabel()].filter({ $0.isEmpty == false }).joined(separator: " ")
} else if name != nil {
return build.level.localizedLevelLabel(.title)
} else {
return ""
}
} else {
return subtitle()
}
} }
var tournaments: [any TournamentBuildHolder] { var tournaments: [any TournamentBuildHolder] {
@ -2280,6 +2304,19 @@ extension Tournament: FederalTournamentHolder {
return .weekend return .weekend
} }
} }
func displayAgeAndCategory(forBuild build: any TournamentBuildHolder) -> Bool {
if isAnimation() {
if let name, name.count < DeviceHelper.maxCharacter() {
return true
} else if build.age == .unlisted, build.category == .unlisted {
return true
} else {
return DeviceHelper.isBigScreen()
}
}
return true
}
} }
extension Tournament: TournamentBuildHolder { extension Tournament: TournamentBuildHolder {

@ -10,6 +10,7 @@ import Foundation
// MARK: - Trimming and stuff // MARK: - Trimming and stuff
extension String { extension String {
func trunc(length: Int, trailing: String = "") -> String { func trunc(length: Int, trailing: String = "") -> String {
if length <= 0 { return self }
return (self.count > length) ? self.prefix(length) + trailing : self return (self.count > length) ? self.prefix(length) + trailing : self
} }

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
enum DisplayContext { enum DisplayContext {
case addition case addition
@ -27,3 +28,40 @@ enum MatchViewStyle {
case plainStyle // vue detail case plainStyle // vue detail
case tournamentResultStyle //vue resultat tournoi case tournamentResultStyle //vue resultat tournoi
} }
struct DeviceHelper {
static func isBigScreen() -> Bool {
switch UIDevice.current.userInterfaceIdiom {
case .pad: // iPads
return true
case .phone: // iPhones (you can add more cases here for large vs small phones)
if UIScreen.main.bounds.size.width > 375 { // iPhone X, 11, 12, 13 Pro Max etc.
return true // large phones
} else {
return false // smaller phones
}
default:
return false // Other devices (Apple Watch, TV, etc.)
}
}
static func maxCharacter() -> Int {
switch UIDevice.current.userInterfaceIdiom {
case .pad: // iPads
return 30
case .phone: // iPhones (you can add more cases here for large vs small phones)
if UIScreen.main.bounds.size.width > 375 { // iPhone X, 11, 12, 13 Pro Max etc.
return 15 // large phones
} else {
return 9 // smaller phones
}
default:
return 9 // Other devices (Apple Watch, TV, etc.)
}
}
static func charLength() -> Int {
isBigScreen() ? 0 : 15
}
}

@ -488,7 +488,13 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable {
} }
func localizedLevelLabel(_ displayStyle: DisplayStyle = .wide) -> String { func localizedLevelLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if self == .unlisted { return displayStyle == .title ? "Animation" : "Anim." } if self == .unlisted {
if DeviceHelper.isBigScreen() {
return "Animation"
} else {
return displayStyle == .title ? "Animation" : "Anim."
}
}
return String(describing: self).capitalized return String(describing: self).capitalized
} }

@ -123,7 +123,7 @@ struct GroupStageTeamReplacementView: View {
private func _searchLinkView(_ teamRange: TeamRegistration.TeamRange) -> some View { private func _searchLinkView(_ teamRange: TeamRegistration.TeamRange) -> some View {
NavigationStack { NavigationStack {
let tournament = team.tournamentObject() let tournament = team.tournamentObject()
SelectablePlayerListView(searchField: _searchableRange(teamRange), dataSet: .favoriteClubs, filterOption: tournament?.tournamentCategory.playerFilterOption ?? .all, sortOption: .rank, showFemaleInMaleAssimilation: true, tokens: [_searchToken(teamRange)], hidePlayers: tournament?.selectedPlayers().compactMap { $0.licenceId }) SelectablePlayerListView(isPresented: false, searchField: _searchableRange(teamRange), dataSet: .favoriteClubs, filterOption: tournament?.tournamentCategory.playerFilterOption ?? .all, sortOption: .rank, showFemaleInMaleAssimilation: true, tokens: [_searchToken(teamRange)], hidePlayers: tournament?.selectedPlayers().compactMap { $0.licenceId })
} }
} }

@ -129,7 +129,7 @@ struct ToolboxView: View {
Section { Section {
NavigationLink { NavigationLink {
SelectablePlayerListView() SelectablePlayerListView(isPresented: false)
} label: { } label: {
Label("Rechercher un joueur", systemImage: "person.fill.viewfinder") Label("Rechercher un joueur", systemImage: "person.fill.viewfinder")
} }

@ -82,6 +82,7 @@ struct ImportedPlayerView: View {
} }
} }
.lineLimit(1) .lineLimit(1)
.truncationMode(.tail)
if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() {
HStack(alignment: .top, spacing: 2) { HStack(alignment: .top, spacing: 2) {

@ -34,7 +34,7 @@ struct SelectablePlayerListView: View {
return URL.importDateFormatter.date(from: lastDataSource) return URL.importDateFormatter.date(from: lastDataSource)
} }
init(allowSelection: Int = 0, searchField: String? = nil, dataSet: DataSet = .national, filterOption: PlayerFilterOption = .all, hideAssimilation: Bool = false, ascending: Bool = true, sortOption: SortOption = .rank, fromPlayer: FederalPlayer? = nil, codeClub: String? = nil, ligue: String? = nil, showFemaleInMaleAssimilation: Bool = false, tokens: [SearchToken] = [], hidePlayers: [String]? = nil, playerSelectionAction: PlayerSelectionAction? = nil, contentUnavailableAction: ContentUnavailableAction? = nil) { init(allowSelection: Int = 0, isPresented: Bool = true, searchField: String? = nil, dataSet: DataSet = .national, filterOption: PlayerFilterOption = .all, hideAssimilation: Bool = false, ascending: Bool = true, sortOption: SortOption = .rank, fromPlayer: FederalPlayer? = nil, codeClub: String? = nil, ligue: String? = nil, showFemaleInMaleAssimilation: Bool = false, tokens: [SearchToken] = [], hidePlayers: [String]? = nil, playerSelectionAction: PlayerSelectionAction? = nil, contentUnavailableAction: ContentUnavailableAction? = nil) {
self.allowSelection = allowSelection self.allowSelection = allowSelection
self.playerSelectionAction = playerSelectionAction self.playerSelectionAction = playerSelectionAction
self.contentUnavailableAction = contentUnavailableAction self.contentUnavailableAction = contentUnavailableAction
@ -45,7 +45,7 @@ struct SelectablePlayerListView: View {
searchViewModel.debouncableText = searchField ?? "" searchViewModel.debouncableText = searchField ?? ""
searchViewModel.showFemaleInMaleAssimilation = showFemaleInMaleAssimilation searchViewModel.showFemaleInMaleAssimilation = showFemaleInMaleAssimilation
searchViewModel.searchText = searchField ?? "" searchViewModel.searchText = searchField ?? ""
searchViewModel.isPresented = allowSelection != 0 searchViewModel.isPresented = isPresented
searchViewModel.allowSelection = allowSelection searchViewModel.allowSelection = allowSelection
searchViewModel.codeClub = fromPlayer?.clubCode ?? codeClub searchViewModel.codeClub = fromPlayer?.clubCode ?? codeClub
searchViewModel.clubName = nil searchViewModel.clubName = nil
@ -221,7 +221,7 @@ struct SelectablePlayerListView: View {
if searchViewModel.selectedPlayers.isEmpty && searchViewModel.filterSelectionEnabled { if searchViewModel.selectedPlayers.isEmpty && searchViewModel.filterSelectionEnabled {
searchViewModel.filterSelectionEnabled = false searchViewModel.filterSelectionEnabled = false
} else { } else if searchViewModel.allowSelection >= searchViewModel.selectedPlayers.count {
searchViewModel.filterSelectionEnabled = true searchViewModel.filterSelectionEnabled = true
} }
} }
@ -430,6 +430,7 @@ struct MySearchView: View {
} }
} }
.lineLimit(1) .lineLimit(1)
.truncationMode(.tail)
if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() {
HStack(alignment: .top, spacing: 2) { HStack(alignment: .top, spacing: 2) {
@ -540,6 +541,7 @@ struct MySearchView: View {
} }
} }
.lineLimit(1) .lineLimit(1)
.truncationMode(.tail)
if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() {
HStack(alignment: .top, spacing: 2) { HStack(alignment: .top, spacing: 2) {
@ -654,6 +656,7 @@ struct MySearchView: View {
} }
} }
.lineLimit(1) .lineLimit(1)
.truncationMode(.tail)
if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() {
HStack(alignment: .top, spacing: 2) { HStack(alignment: .top, spacing: 2) {
@ -763,6 +766,7 @@ struct MySearchView: View {
} }
} }
.lineLimit(1) .lineLimit(1)
.truncationMode(.tail)
if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() {
HStack(alignment: .top, spacing: 2) { HStack(alignment: .top, spacing: 2) {
@ -874,6 +878,7 @@ struct MySearchView: View {
} }
} }
.lineLimit(1) .lineLimit(1)
.truncationMode(.tail)
if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() {
HStack(alignment: .top, spacing: 2) { HStack(alignment: .top, spacing: 2) {
@ -972,6 +977,7 @@ struct MySearchView: View {
} }
} }
.lineLimit(1) .lineLimit(1)
.truncationMode(.tail)
if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() {
HStack(alignment: .top, spacing: 2) { HStack(alignment: .top, spacing: 2) {

@ -45,8 +45,7 @@ struct AddTeamView: View {
@State private var searchForHit: Int = 0 @State private var searchForHit: Int = 0
@State private var displayWarningNotEnoughCharacter: Bool = false @State private var displayWarningNotEnoughCharacter: Bool = false
@State private var testMessageIndex: Int = 0 @State private var testMessageIndex: Int = 0
@State private var presentLocalMultiplayerSearch: Bool = false
let filterLimit : Int = 1000
var tournamentStore: TournamentStore { var tournamentStore: TournamentStore {
return self.tournament.tournamentStore return self.tournament.tournamentStore
@ -97,7 +96,7 @@ struct AddTeamView: View {
_buildingTeamView() _buildingTeamView()
} }
.onReceive(fetchPlayers.publisher.count()) { receivedCount in // <-- here .onReceive(fetchPlayers.publisher.count()) { receivedCount in // <-- here
if receivedCount < filterLimit, let pasteString, pasteString.isEmpty == false, count == 2, autoSelect == true { if let pasteString, pasteString.isEmpty == false, count == 2, autoSelect == true {
fetchPlayers.filter { hitForSearch($0, pasteString) >= hitTarget }.sorted(by: { hitForSearch($0, pasteString) > hitForSearch($1, pasteString) }).forEach { player in fetchPlayers.filter { hitForSearch($0, pasteString) >= hitTarget }.sorted(by: { hitForSearch($0, pasteString) > hitForSearch($1, pasteString) }).forEach { player in
createdPlayerIds.insert(player.license!) createdPlayerIds.insert(player.license!)
} }
@ -142,6 +141,27 @@ struct AddTeamView: View {
} message: { } message: {
Text("Cette équipe existe déjà dans votre liste d'inscription.") Text("Cette équipe existe déjà dans votre liste d'inscription.")
} }
.sheet(isPresented: $presentLocalMultiplayerSearch) {
NavigationStack {
SelectablePlayerListView(allowSelection: -1, isPresented: false, searchField: searchField, dataSet: .club, filterOption: _filterOption(), showFemaleInMaleAssimilation: tournament.tournamentCategory.showFemaleInMaleAssimilation) { players in
players.forEach { player in
let newPlayer = PlayerRegistration(importedPlayer: player)
newPlayer.setComputedRank(in: tournament)
createdPlayers = Set<PlayerRegistration>()
createdPlayerIds = Set<String>()
createdPlayers.insert(newPlayer)
createdPlayerIds.insert(newPlayer.id)
_createTeam(checkDuplicates: false, checkHomonym: false)
}
} contentUnavailableAction: { searchViewModel in
presentLocalMultiplayerSearch = false
selectionSearchField = searchViewModel.searchText
}
}
.tint(.master)
}
.sheet(isPresented: $presentPlayerSearch) { .sheet(isPresented: $presentPlayerSearch) {
NavigationStack { NavigationStack {
SelectablePlayerListView(allowSelection: -1, searchField: searchField, filterOption: _filterOption(), showFemaleInMaleAssimilation: tournament.tournamentCategory.showFemaleInMaleAssimilation) { players in SelectablePlayerListView(allowSelection: -1, searchField: searchField, filterOption: _filterOption(), showFemaleInMaleAssimilation: tournament.tournamentCategory.showFemaleInMaleAssimilation) { players in
@ -243,6 +263,16 @@ struct AddTeamView: View {
} }
} }
if tournament.isAnimation(), createdPlayers.isEmpty == true {
Section {
RowButtonView("Ajouter plusieurs joueurs du club") {
presentLocalMultiplayerSearch = true
}
} footer: {
Text("Crée une équipe par joueur sélectionné")
}
}
Section { Section {
RowButtonView("Créer un non classé / non licencié") { RowButtonView("Créer un non classé / non licencié") {
if let pasteString, pasteString.isEmpty == false { if let pasteString, pasteString.isEmpty == false {
@ -633,11 +663,7 @@ struct AddTeamView: View {
} }
private func _sortedPlayers(searchFilteredPlayers: [ImportedPlayer], pasteString: String) -> [ImportedPlayer] { private func _sortedPlayers(searchFilteredPlayers: [ImportedPlayer], pasteString: String) -> [ImportedPlayer] {
if searchFilteredPlayers.count < filterLimit { return searchFilteredPlayers.sorted(by: { hitForSearch($0, pasteString) > hitForSearch($1, pasteString) })
return searchFilteredPlayers.sorted(by: { hitForSearch($0, pasteString) > hitForSearch($1, pasteString) })
} else {
return searchFilteredPlayers
}
} }
} }

@ -94,9 +94,9 @@ struct TournamentCellView: View {
.font(.caption) .font(.caption)
} }
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
Text(build.level.localizedLevelLabel()) Text(tournament.tournamentTitle(displayStyle, forBuild: build))
.fontWeight(.semibold) .fontWeight(.semibold)
if displayStyle == .wide { if displayStyle == .wide, tournament.displayAgeAndCategory(forBuild: build) {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Text(build.category.localizedLabel()) Text(build.category.localizedLabel())
Text(build.age.localizedLabel()) Text(build.age.localizedLabel())
@ -128,8 +128,14 @@ struct TournamentCellView: View {
.font(displayStyle == .wide ? .title : .title3) .font(displayStyle == .wide ? .title : .title3)
if displayStyle == .wide { if displayStyle == .wide {
HStack { HStack(alignment: .top) {
Text(tournament.durationLabel()) VStack(alignment: .leading) {
let sub = tournament.subtitleLabel(forBuild: build)
if sub.isEmpty == false {
Text(sub).lineLimit(1)
}
Text(tournament.durationLabel())
}
Spacer() Spacer()
if let tournament = tournament as? Tournament, tournament.isCanceled == false, let teamCount { if let tournament = tournament as? Tournament, tournament.isCanceled == false, let teamCount {
let hasStarted = tournament.inscriptionClosed() || tournament.hasStarted() let hasStarted = tournament.inscriptionClosed() || tournament.hasStarted()
@ -137,7 +143,6 @@ struct TournamentCellView: View {
Text(word + teamCount.pluralSuffix) Text(word + teamCount.pluralSuffix)
} }
} }
Text(tournament.subtitleLabel()).lineLimit(1)
} else { } else {
Text(build.category.localizedLabel()) Text(build.category.localizedLabel())
Text(build.age.localizedLabel()) Text(build.age.localizedLabel())

Loading…
Cancel
Save