parent
604d24c326
commit
1601f72ad2
@ -0,0 +1,36 @@ |
|||||||
|
// |
||||||
|
// WeekdayselectionView.swift |
||||||
|
// PadelClub |
||||||
|
// |
||||||
|
// Created by Razmig Sarkissian on 24/09/2025. |
||||||
|
// |
||||||
|
|
||||||
|
import SwiftUI |
||||||
|
import PadelClubData |
||||||
|
import LeStorage |
||||||
|
|
||||||
|
struct WeekdayselectionView: View { |
||||||
|
@Binding var weekdays: Set<Int> |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
NavigationLink { |
||||||
|
List((1...7), selection: $weekdays) { type in |
||||||
|
Text(Date.weekdays[type - 1]).tag(type as Int) |
||||||
|
} |
||||||
|
.navigationTitle("Jour de la semaine") |
||||||
|
.environment(\.editMode, Binding.constant(EditMode.active)) |
||||||
|
} label: { |
||||||
|
HStack { |
||||||
|
Text("Jour de la semaine") |
||||||
|
Spacer() |
||||||
|
if weekdays.isEmpty || weekdays.count == 7 { |
||||||
|
Text("N'importe") |
||||||
|
.foregroundStyle(.secondary) |
||||||
|
} else { |
||||||
|
Text(weekdays.sorted().map({ Date.weekdays[$0 - 1] }).joined(separator: ", ")) |
||||||
|
.foregroundStyle(.secondary) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,235 @@ |
|||||||
|
import SwiftUI |
||||||
|
|
||||||
|
struct OnboardingView: View { |
||||||
|
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel |
||||||
|
@State private var selection = 0 |
||||||
|
@Environment(\.openURL) var openURL |
||||||
|
@Environment(\.dismiss) private var dismiss |
||||||
|
@AppStorage("didSeeOnboarding") private var didSeeOnboarding: Bool = false |
||||||
|
|
||||||
|
var steps: [OnboardingStep] { |
||||||
|
[ |
||||||
|
// Écran 1 – Bienvenue |
||||||
|
.single( |
||||||
|
title: "Bienvenue sur Padel Club", |
||||||
|
description: "L’outil idéal des juges-arbitres et organisateurs pour gérer leurs tournois de A à Z.", |
||||||
|
image: .padelClubLogoFondclairTransparent, |
||||||
|
imageSystem: nil, |
||||||
|
buttonTitle: "Suivant", |
||||||
|
action: { selection += 1 } |
||||||
|
), |
||||||
|
|
||||||
|
// Écran 2 – Juges arbitres |
||||||
|
.single( |
||||||
|
title: "Pour les Juges-Arbitres", |
||||||
|
description: "Planification, convocations, tirages, résultats… Tout ce qu’il faut pour organiser un tournoi de padel.", |
||||||
|
image: nil, |
||||||
|
imageSystem: "calendar.badge.clock", |
||||||
|
buttonTitle: "Suivant", |
||||||
|
action: { selection += 1 } |
||||||
|
), |
||||||
|
|
||||||
|
// Écran 3 – Joueurs (Multi boutons) |
||||||
|
.multi( |
||||||
|
title: "Vous êtes joueur ?", |
||||||
|
description: "Cette app a été pensée faite pour les organisateurs.\nPour suivre vos tournois et convocations, rendez-vous sur https://padelclub.app", |
||||||
|
image: nil, |
||||||
|
imageSystem: "person.fill.questionmark", |
||||||
|
tools: [ |
||||||
|
("Aller sur le site joueur", { |
||||||
|
if let url = URL(string: "https://padelclub.app") { |
||||||
|
openURL(url) |
||||||
|
} |
||||||
|
}) |
||||||
|
], |
||||||
|
finalButtonTitle: "Continuer", |
||||||
|
finalAction: { |
||||||
|
selection += 1 |
||||||
|
} |
||||||
|
), |
||||||
|
|
||||||
|
// Écran 4 – Outils utiles aux joueurs |
||||||
|
.multi( |
||||||
|
title: "Quelques outils utiles", |
||||||
|
description: "Même si pensée pour les organisateurs, vous trouverez aussi quelques fonctions pratiques en tant que joueur.", |
||||||
|
image: nil, |
||||||
|
imageSystem: "wrench.and.screwdriver", |
||||||
|
tools: [ |
||||||
|
("Chercher un tournoi Ten'Up", { |
||||||
|
dismiss() |
||||||
|
navigation.agendaDestination = .around |
||||||
|
}), |
||||||
|
("Calculateur de points", { |
||||||
|
dismiss() |
||||||
|
navigation.selectedTab = .toolbox |
||||||
|
}), |
||||||
|
("Consulter les règles du jeu", { |
||||||
|
dismiss() |
||||||
|
navigation.selectedTab = .toolbox |
||||||
|
}), |
||||||
|
("Créer vos animations amicales", { |
||||||
|
dismiss() |
||||||
|
navigation.agendaDestination = .activity |
||||||
|
}) |
||||||
|
], |
||||||
|
finalButtonTitle: "J'ai compris", |
||||||
|
finalAction: { |
||||||
|
UserDefaults.standard.set(true, forKey: "didSeeOnboarding") |
||||||
|
dismiss() |
||||||
|
} |
||||||
|
) |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
NavigationStack { |
||||||
|
TabView(selection: $selection) { |
||||||
|
ForEach(Array(steps.enumerated()), id: \.offset) { index, step in |
||||||
|
switch step { |
||||||
|
case let .single(title, description, image, imageSystem, buttonTitle, action): |
||||||
|
OnboardingPage( |
||||||
|
title: title, |
||||||
|
description: description, |
||||||
|
image: image, |
||||||
|
imageSystem: imageSystem, |
||||||
|
buttonTitle: buttonTitle, |
||||||
|
action: action |
||||||
|
) |
||||||
|
.tag(index) |
||||||
|
|
||||||
|
case let .multi(title, description, image, imageSystem, tools, finalButtonTitle, finalAction): |
||||||
|
OnboardingMultiButtonPage( |
||||||
|
title: title, |
||||||
|
description: description, |
||||||
|
image: image, |
||||||
|
imageSystem: imageSystem, |
||||||
|
tools: tools, |
||||||
|
finalButtonTitle: finalButtonTitle, |
||||||
|
finalAction: finalAction |
||||||
|
) |
||||||
|
.tag(index) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always)) |
||||||
|
.indexViewStyle(.page(backgroundDisplayMode: .always)) // <- ensures background |
||||||
|
.tint(.black) // <- sets the indicator color |
||||||
|
.toolbar { |
||||||
|
ToolbarItem(placement: .topBarTrailing) { |
||||||
|
Button { |
||||||
|
didSeeOnboarding = true |
||||||
|
dismiss() |
||||||
|
} label: { |
||||||
|
Text("Plus tard") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.tint(.master) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// MARK: - Enum de configuration |
||||||
|
enum OnboardingStep { |
||||||
|
case single(title: String, description: String, image: ImageResource?, imageSystem: String?, buttonTitle: String, action: () -> Void) |
||||||
|
case multi(title: String, description: String, image: ImageResource?, imageSystem: String?, tools: [(String, () -> Void)], finalButtonTitle: String?, finalAction: () -> Void) |
||||||
|
} |
||||||
|
|
||||||
|
// MARK: - Vue de base commune |
||||||
|
struct OnboardingBasePage<Content: View>: View { |
||||||
|
var title: String |
||||||
|
var description: String |
||||||
|
var image: ImageResource? |
||||||
|
var imageSystem: String? |
||||||
|
@ViewBuilder var content: () -> Content |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
VStack(spacing: 20) { |
||||||
|
Spacer() |
||||||
|
|
||||||
|
if let imageSystem { |
||||||
|
Image(systemName: imageSystem) |
||||||
|
.resizable() |
||||||
|
.scaledToFit() |
||||||
|
.frame(width: 100, height: 100) |
||||||
|
} else if let image { |
||||||
|
Image(image) |
||||||
|
.resizable() |
||||||
|
.scaledToFit() |
||||||
|
.frame(width: 100, height: 100) |
||||||
|
} |
||||||
|
|
||||||
|
Text(title) |
||||||
|
.font(.title) |
||||||
|
.fontWeight(.bold) |
||||||
|
.multilineTextAlignment(.center) |
||||||
|
|
||||||
|
Text(description) |
||||||
|
.font(.body) |
||||||
|
.multilineTextAlignment(.center) |
||||||
|
.padding(.horizontal, 30) |
||||||
|
.lineLimit(nil) |
||||||
|
.fixedSize(horizontal: false, vertical: true) |
||||||
|
|
||||||
|
Spacer() |
||||||
|
|
||||||
|
content() |
||||||
|
|
||||||
|
Spacer(minLength: 40) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// MARK: - Page avec un bouton |
||||||
|
struct OnboardingPage: View { |
||||||
|
var title: String |
||||||
|
var description: String |
||||||
|
var image: ImageResource? |
||||||
|
var imageSystem: String? |
||||||
|
var buttonTitle: String |
||||||
|
var action: () -> Void |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
OnboardingBasePage(title: title, description: description, image: image, imageSystem: imageSystem) { |
||||||
|
RowButtonView(buttonTitle) { |
||||||
|
action() |
||||||
|
} |
||||||
|
.padding() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// MARK: - Page avec plusieurs boutons |
||||||
|
struct OnboardingMultiButtonPage: View { |
||||||
|
var title: String |
||||||
|
var description: String |
||||||
|
var image: ImageResource? |
||||||
|
var imageSystem: String? |
||||||
|
var tools: [(String, () -> Void)] |
||||||
|
var finalButtonTitle: String? |
||||||
|
var finalAction: () -> Void |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
OnboardingBasePage(title: title, description: description, image: image, imageSystem: imageSystem) { |
||||||
|
VStack(spacing: 12) { |
||||||
|
ForEach(Array(tools.enumerated()), id: \.offset) { _, tool in |
||||||
|
FooterButtonView(tool.0) { |
||||||
|
tool.1() |
||||||
|
} |
||||||
|
.tint(.master) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if let finalButtonTitle = finalButtonTitle { |
||||||
|
RowButtonView(finalButtonTitle) { |
||||||
|
finalAction() |
||||||
|
} |
||||||
|
.padding() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#Preview { |
||||||
|
OnboardingView() |
||||||
|
} |
||||||
Loading…
Reference in new issue