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