multistore
Razmig Sarkissian 2 years ago
parent d131eae629
commit fd661687c3
  1. 12
      PadelClub.xcodeproj/project.pbxproj
  2. 4
      PadelClub/Data/Club.swift
  3. 8
      PadelClub/Data/Tournament.swift
  4. 16
      PadelClub/Extensions/NumberFormatter+Extensions.swift
  5. 29
      PadelClub/Extensions/String+Extensions.swift
  6. 25
      PadelClub/Views/Club/ClubRowView.swift
  7. 35
      PadelClub/Views/Club/ClubsView.swift
  8. 88
      PadelClub/Views/Event/EventCreationView.swift
  9. 53
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  10. 60
      PadelClub/Views/Navigation/Toolbox/RankCalculatorView.swift
  11. 6
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  12. 31
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  13. 51
      PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift
  14. 7
      PadelClub/Views/Tournament/Shared/DateBoxView.swift
  15. 10
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift

@ -104,6 +104,9 @@
FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */; };
FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */; };
FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D772BB42C5B005CB568 /* InscriptionInfoView.swift */; };
FF5D0D852BB48997005CB568 /* RankCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */; };
FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */; };
FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D882BB4935C005CB568 /* ClubRowView.swift */; };
FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; };
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; };
FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; };
@ -317,6 +320,9 @@
FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = "<group>"; };
FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViewModifier.swift; sourceTree = "<group>"; };
FF5D0D772BB42C5B005CB568 /* InscriptionInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionInfoView.swift; sourceTree = "<group>"; };
FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankCalculatorView.swift; sourceTree = "<group>"; };
FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NumberFormatter+Extensions.swift"; sourceTree = "<group>"; };
FF5D0D882BB4935C005CB568 /* ClubRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubRowView.swift; sourceTree = "<group>"; };
FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = "<group>"; };
FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = "<group>"; };
FF6EC8FD2B94792300EA7F5A /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
@ -657,6 +663,7 @@
FF1DC5562BAB3AED00FD8220 /* ClubsView.swift */,
FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */,
FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */,
FF5D0D882BB4935C005CB568 /* ClubRowView.swift */,
);
path = Club;
sourceTree = "<group>";
@ -723,6 +730,7 @@
isa = PBXGroup;
children = (
FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */,
FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */,
);
path = Toolbox;
sourceTree = "<group>";
@ -880,6 +888,7 @@
isa = PBXGroup;
children = (
FFF8ACD52B923960008466FA /* URL+Extensions.swift */,
FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */,
FFF8ACD82B923F3C008466FA /* String+Extensions.swift */,
FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */,
FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */,
@ -1144,6 +1153,7 @@
FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */,
FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */,
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */,
FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */,
FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */,
C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */,
FF6EC90B2B947AC000EA7F5A /* Array+Extensions.swift in Sources */,
@ -1193,9 +1203,11 @@
FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */,
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */,
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */,
FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */,
C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */,
FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */,
FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */,
FF5D0D852BB48997005CB568 /* RankCalculatorView.swift in Sources */,
FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */,
FF5D0D722BB3EFA5005CB568 /* LearnMoreSheetView.swift in Sources */,
FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */,

@ -35,7 +35,7 @@ class Club : ModelObject, Storable, Hashable {
internal init(name: String, acronym: String? = nil, phone: String? = nil, code: String? = nil, address: String? = nil, city: String? = nil, zipCode: String? = nil, latitude: Double? = nil, longitude: Double? = nil) {
self.name = name
self.acronym = acronym ?? name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
self.acronym = acronym ?? name.acronym()
self.phone = phone
self.code = code
self.address = address
@ -73,7 +73,7 @@ extension Club {
}
func automaticShortName() -> String {
String(name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines).prefix(10))
name.acronym()
}
enum AcronymMode: String, CaseIterable {

@ -107,6 +107,14 @@ class Tournament : ModelObject, Storable {
eventObject?.clubObject
}
func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if let club = club() {
return "@" + club.acronym
} else {
return ""
}
}
func hasEnded() -> Bool {
endDate != nil
}

@ -0,0 +1,16 @@
//
// NumberFormatter+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 27/03/2024.
//
import Foundation
extension NumberFormatter {
static var ordinal: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
return formatter
}
}

@ -25,6 +25,35 @@ extension String {
}
}
extension String {
func acronym() -> String {
let acronym = canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
if acronym.count > 10 {
return concatenateFirstLetters()
} else {
return acronym
}
}
func concatenateFirstLetters() -> String {
// Split the input into sentences
let sentences = self.components(separatedBy: .whitespacesAndNewlines)
// Extract the first character of each sentence
let firstLetters = sentences.compactMap { sentence -> Character? in
let trimmedSentence = sentence.trimmingCharacters(in: .whitespacesAndNewlines)
if let firstCharacter = trimmedSentence.first {
return firstCharacter
}
return nil
}
// Join the first letters together into a string
let result = String(firstLetters)
return result
}
}
extension String {
var computedLicense: String {
if let licenseKey {

@ -0,0 +1,25 @@
//
// ClubRowView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 27/03/2024.
//
import SwiftUI
struct ClubRowView: View {
let club: Club
var body: some View {
LabeledContent {
} label: {
Text(club.name)
Text(club.acronym)
}
}
}
#Preview {
ClubRowView(club: Club.mock())
}

@ -10,23 +10,37 @@ import TipKit
struct ClubsView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(\.dismiss) private var dismiss
@State private var presentClubCreationView: Bool = false
@State private var presentClubSearchView: Bool = false
let tip = SlideToDeleteTip()
var selection: ((Club) -> ())? = nil
var body: some View {
List {
ForEach(dataStore.clubs) { club in
NavigationLink {
ClubDetailView(club: club)
} label: {
Text(club.name)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
try? dataStore.clubs.delete(instance: club)
if let selection {
Button {
selection(club)
dismiss()
} label: {
ClubRowView(club: club)
.frame(maxWidth: .infinity)
}
.contentShape(Rectangle())
.buttonStyle(.plain)
} else {
NavigationLink {
ClubDetailView(club: club)
} label: {
LabelDelete()
ClubRowView(club: club)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
try? dataStore.clubs.delete(instance: club)
} label: {
LabelDelete()
}
}
}
}
@ -37,7 +51,6 @@ struct ClubsView: View {
.tipStyle(tint: nil)
}
}
}
.overlay {
if dataStore.clubs.isEmpty {
@ -55,7 +68,7 @@ struct ClubsView: View {
}
}
}
.navigationTitle("Mes clubs")
.navigationTitle(selection == nil ? "Mes clubs" : "Choisir un club")
.sheet(isPresented: $presentClubCreationView) {
CreateClubView()
}

@ -17,6 +17,8 @@ struct EventCreationView: View {
@State private var duration: Int = 3
@State private var eventName: String = ""
@State var tournaments: [Tournament] = []
@State private var selectedClub: Club?
let multiTournamentsEventTip = MultiTournamentsEventTip()
var body: some View {
@ -32,18 +34,6 @@ struct EventCreationView: View {
// }
// }
Section {
TextField("Nom de l'événement", text: $eventName)
}
Section {
TipView(multiTournamentsEventTip) { action in
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
}
.tipStyle(tint: .orange)
}
Section {
DatePicker(selection: $startingDate) {
Text(startingDate.formatted(.dateTime.weekday(.wide)).capitalized)
@ -58,8 +48,31 @@ struct EventCreationView: View {
}
}
}
NavigationLink {
ClubsView() { club in
print("club", club.acronym)
selectedClub = club
}
} label: {
if let selectedClub {
ClubRowView(club: selectedClub)
} else {
Text("Choisir un club")
}
}
TextField("Nom de l'événement", text: $eventName)
} header: {
Text("Démarrage")
Text("Informations générales")
}
Section {
TipView(multiTournamentsEventTip) { action in
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
}
.tipStyle(tint: .orange)
}
@ -73,30 +86,12 @@ struct EventCreationView: View {
case .animation:
animationEditorView
}
}
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Annuler", role: .cancel) {
dismiss()
}
}
ToolbarItem(placement: .topBarTrailing) {
Button {
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
} label: {
Label("épreuve", systemImage: "plus.circle.fill").labelStyle(.titleAndIcon)
}
.clipShape(Capsule())
.buttonStyle(.bordered)
}
ToolbarItem(placement: .bottomBar) {
Button {
if tournaments.count > 1 || eventName.trimmed.isEmpty == false {
Section {
RowButtonView(title:"Valider") {
if tournaments.count > 1 || eventName.trimmed.isEmpty == false || selectedClub != nil {
let event = Event(name: eventName)
event.club = selectedClub?.id
tournaments.forEach { tournament in
tournament.event = event.id
}
@ -110,17 +105,19 @@ struct EventCreationView: View {
try? dataStore.tournaments.addOrUpdate(contentOfs: tournaments)
dismiss()
} label: {
Text("Valider")
.frame(maxWidth: .infinity)
}
.font(.headline)
.buttonStyle(.borderedProminent)
.tint(.launchScreenBackground)
}
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Annuler", role: .cancel) {
dismiss()
}
}
}
.navigationTitle("Nouvel événement")
.navigationBarTitleDisplayMode(.large)
.toolbarBackground(.visible, for: .navigationBar)
}
}
@ -144,6 +141,13 @@ struct EventCreationView: View {
}
}
}
Section {
RowButtonView(title: "Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") {
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
}
}
}
@ViewBuilder

@ -12,32 +12,45 @@ struct EventListView: View {
let tournaments: [Tournament]
var body: some View {
Section {
ForEach(tournaments) { tournament in
let groupedTournamentsByDate = Dictionary(grouping: tournaments) { $0.startDate.monthYearFormatted }
NavigationLink(value: tournament) {
ForEach(groupedTournamentsByDate.keys.sorted(by: >), id: \.self) { section in
if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) {
Section {
ForEach(_tournaments) { tournament in
NavigationLink(value: tournament) {
HStack {
TournamentCellView(tournament: tournament)
Spacer()
Text(tournament.sortedTeams().count.formatted())
.font(.largeTitle)
}
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
try? DataStore.shared.tournaments.delete(instance: tournament)
} label: {
LabelDelete()
}
}
.contextMenu {
Button {
} label: {
Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow")
}
}
}
} header: {
HStack {
TournamentCellView(tournament: tournament)
Text(section)
Spacer()
Text(tournament.sortedTeams().count.formatted())
}
}
.contextMenu {
Button {
} label: {
Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow")
Text(_tournaments.count.formatted())
}
}
.headerProminence(.increased)
}
.onDelete(perform: { indexSet in
for index in indexSet {
try? DataStore.shared.tournaments.delete(instance: tournaments[index])
}
})
} header: {
Text(Date().formatted(.dateTime.month().year()))
}.headerProminence(.increased)
}
}
}

@ -0,0 +1,60 @@
//
// RankCalculatorView.swift
// Padel Tournament
//
// Created by Razmig Sarkissian on 03/05/2023.
//
import SwiftUI
struct RankCalculatorView: View {
@State private var rank: Int = 1
@AppStorage("lastRankCalculatorLevel") private var tournamentLevel: TournamentLevel = .p25
@AppStorage("lastRankCalculatorCount") private var count: PlayersCountRange = .N8
var body: some View {
Form {
Section {
HStack {
let ordinal = NumberFormatter.ordinal.string(from: NSNumber(value:rank))!
Text("\(ordinal) d'un \(tournamentLevel.localizedLabel()) de \(count.localizedLabel()) équipes:")
Spacer()
Text(tournamentLevel.points(for: rank-1, count: count.rawValue).formatted(.number.sign(strategy: .always())))
}
}
Section {
Picker(selection: $tournamentLevel) {
ForEach(TournamentLevel.allCases) { level in
Text(level.localizedLabel()).tag(level)
}
} label: {
Label("Niveau", systemImage: "gauge.medium")
}
Picker(selection: $count) {
ForEach(tournamentLevel.ranges, id: \.self) {
Text($0.localizedLabel()).tag($0)
}
} label: {
Label("Nombre d'équipes", systemImage: "person.2")
}
Picker(selection: $rank) {
ForEach((1...count.rawValue), id: \.self) {
Text("#\($0)").tag($0)
}
} label: {
Label("Votre position", systemImage: "number")
}
}
Section {
ForEach(tournamentLevel.allPoints(for: count.rawValue).indices, id: \.self) { i in
HStack {
Text("#"+(i+1).formatted())
Spacer()
Text(tournamentLevel.allPoints(for: count.rawValue)[i].formatted(.number.sign(strategy: .always())))
}
}
}
}
.navigationTitle("Calculateur de points")
}
}

@ -16,6 +16,12 @@ struct ToolboxView: View {
} label: {
Label("Rechercher un joueur", systemImage: "person.fill.viewfinder")
}
NavigationLink {
RankCalculatorView()
} label: {
Label("Calculateur de points", systemImage: "scalemass")
}
}
.navigationTitle(TabDestination.toolbox.title)
}

@ -668,14 +668,41 @@ struct InscriptionManagerView: View {
}
.labelsHidden()
Divider()
NavigationLink {
ClubsView() { club in
if let event = tournament.eventObject {
event.club = club.id
try? dataStore.events.addOrUpdate(instance: event)
} else {
let event = Event(club: club.id)
tournament.event = event.id
try? dataStore.events.addOrUpdate(instance: event)
}
_save()
}
} label: {
Text("Changer de club")
}
} label: {
Text("Membres prioritaires")
Text(federalClub.acronym)
}
Divider()
} else if let event = tournament.eventObject {
} else {
NavigationLink {
ClubSearchView()
ClubsView() { club in
if let event = tournament.eventObject {
event.club = club.id
try? dataStore.events.addOrUpdate(instance: event)
} else {
let event = Event(club: club.id)
tournament.event = event.id
try? dataStore.events.addOrUpdate(instance: event)
}
_save()
}
} label: {
Text("Identifier le club")
}

@ -46,18 +46,55 @@ struct TournamentSettingsView: View {
TournamentDurationManagerView()
TournamentFieldsManagerView()
TournamentDatePickerView()
let event = tournament.eventObject
let selectedClub = event?.clubObject
Section {
if let selectedClub {
NavigationLink {
ClubDetailView(club: selectedClub, displayContext: .edition)
} label: {
ClubRowView(club: selectedClub)
}
} else {
NavigationLink {
ClubsView() { club in
if let event {
event.club = club.id
try? dataStore.events.addOrUpdate(instance: event)
} else {
let event = Event(club: club.id)
tournament.event = event.id
try? dataStore.events.addOrUpdate(instance: event)
}
}
} label: {
Text("Choisir un club")
}
}
} header: {
Text("Lieu du tournoi")
} footer: {
if let event, selectedClub != nil {
HStack {
Spacer()
Button("modifier", role: .destructive) {
event.club = nil
try? dataStore.events.addOrUpdate(instance: event)
}
.font(.caption)
}
}
}
TournamentFormatSelectionView()
}
.navigationTitle("Réglages")
.toolbarBackground(.visible, for: .navigationBar)
.onAppear {
tournamentName = tournament.name ?? ""
tournament.undoManager = tournament.hashValue
}
.onDisappear {
if tournament.undoManager != tournament.hashValue {
try? dataStore.tournaments.addOrUpdate(instance: tournament)
}
try? dataStore.tournaments.addOrUpdate(instance: tournament)
}
}
}

@ -14,8 +14,11 @@ struct DateBoxView: View {
VStack(alignment: .center, spacing: -2) {
Text(date.formatted(.dateTime.weekday(.abbreviated)))
.font(.caption2)
Text(date.formatted(.dateTime.day()))
.font(.title)
HStack(alignment: .bottom) {
Text(date.formatted(.dateTime.day(.twoDigits)))
.font(.title)
.monospacedDigit()
}
Text(date.formatted(.dateTime.month(.abbreviated)))
.font(.caption2)
Text(date.formatted(.dateTime.year()))

@ -19,10 +19,14 @@ struct TournamentCellView: View {
.fill(color)
.frame(width: 2)
VStack(alignment: .leading, spacing: -2) {
Text(tournament.subtitle())
Text(tournament.locationLabel())
.font(.caption2)
Text(tournament.tournamentLevel.localizedLabel())
.font(.title)
HStack(alignment: .bottom) {
Text(tournament.tournamentLevel.localizedLabel())
.font(.title)
Text(tournament.subtitle())
.font(.headline)
}
Text(tournament.tournamentCategory.localizedLabel())
.font(.caption2)
Text(tournament.federalTournamentAge.localizedLabel())

Loading…
Cancel
Save