🚀 Start Project
This commit is contained in:
@@ -10,9 +10,22 @@
|
|||||||
57282ACB2CBD2810000C443E /* AlloVoisinsSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AlloVoisinsSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
57282ACB2CBD2810000C443E /* AlloVoisinsSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AlloVoisinsSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
57282BA72CBD579A000C443E /* Exceptions for "AlloVoisinsSwiftUI" folder in "AlloVoisinsSwiftUI" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = 57282ACA2CBD2810000C443E /* AlloVoisinsSwiftUI */;
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
57282ACD2CBD2810000C443E /* AlloVoisinsSwiftUI */ = {
|
57282ACD2CBD2810000C443E /* AlloVoisinsSwiftUI */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
57282BA72CBD579A000C443E /* Exceptions for "AlloVoisinsSwiftUI" folder in "AlloVoisinsSwiftUI" target */,
|
||||||
|
);
|
||||||
path = AlloVoisinsSwiftUI;
|
path = AlloVoisinsSwiftUI;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -255,6 +268,7 @@
|
|||||||
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = AlloVoisinsSwiftUI/Info.plist;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
@@ -284,6 +298,7 @@
|
|||||||
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = AlloVoisinsSwiftUI/Info.plist;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Bucket
|
||||||
|
uuid = "9F0CD4E1-EA25-475C-9981-B5E4AE8BD8B9"
|
||||||
|
type = "1"
|
||||||
|
version = "2.0">
|
||||||
|
</Bucket>
|
||||||
@@ -11,7 +11,9 @@ import SwiftUI
|
|||||||
struct AlloVoisinsSwiftUIApp: App {
|
struct AlloVoisinsSwiftUIApp: App {
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
NavigationStack {
|
||||||
|
ResiliationNavigationView(resiliationType: .apProWithTrial)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
159
AlloVoisinsSwiftUI/Components/File.swift
Normal file
159
AlloVoisinsSwiftUI/Components/File.swift
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
//
|
||||||
|
// ResponsiveSheetWrapper.swift
|
||||||
|
//
|
||||||
|
// Created by Bacem Ben Afia on 04/08/2022.
|
||||||
|
// ba.bessem@gmail.com
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
//MARK: - ViewModifier
|
||||||
|
fileprivate struct ResponsiveSheetWrapper: ViewModifier {
|
||||||
|
|
||||||
|
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
/// check device type (ipad sheet are centred / iPhone sheet pinned to bottom )
|
||||||
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
|
ZStack (alignment: .bottom){
|
||||||
|
Color(UIColor.systemBackground.withAlphaComponent(0.01))
|
||||||
|
.onTapGesture {
|
||||||
|
// tap outside the view to dismiss
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
VStack {
|
||||||
|
/// the small thumb for bottom sheet native like
|
||||||
|
content
|
||||||
|
}
|
||||||
|
/// redesign the content
|
||||||
|
.asResponsiveSheetContent()
|
||||||
|
}
|
||||||
|
/// remove system background
|
||||||
|
.clearSheetSystemBackground()
|
||||||
|
.edgesIgnoringSafeArea(.bottom)
|
||||||
|
} else {
|
||||||
|
ZStack {
|
||||||
|
Color(UIColor.systemBackground.withAlphaComponent(0.01))
|
||||||
|
.onTapGesture {
|
||||||
|
// tap outside the view to dismiss
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}
|
||||||
|
content
|
||||||
|
/// redesign the content
|
||||||
|
.asResponsiveSheetContent()
|
||||||
|
}
|
||||||
|
/// remove system background
|
||||||
|
.clearSheetSystemBackground()
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate struct ResponsiveSheetContent: ViewModifier {
|
||||||
|
/// ResponsiveSheetContent will create the form of a bottom sheet (apply corners radius for both iPad an iPhone sheet)
|
||||||
|
@Environment(\.safeAreaInsets) private var safeAreaInsets
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
|
content
|
||||||
|
.padding(.bottom, safeAreaInsets.bottom)
|
||||||
|
.background(Color(UIColor.systemBackground))
|
||||||
|
.cornerRadius(10, corners: [.topLeft, .topRight])
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
.padding()
|
||||||
|
.background(Color(UIColor.systemBackground))
|
||||||
|
.cornerRadius(10, corners: [.allCorners])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate struct ClearBackgroundViewModifier: ViewModifier {
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.background(ClearBackgroundView())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate struct CornerRadiusStyle: ViewModifier {
|
||||||
|
var radius: CGFloat
|
||||||
|
var corners: UIRectCorner
|
||||||
|
|
||||||
|
struct CornerRadiusShape: Shape {
|
||||||
|
|
||||||
|
var radius = CGFloat.infinity
|
||||||
|
var corners = UIRectCorner.allCorners
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
|
||||||
|
return Path(path.cgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.clipShape(CornerRadiusShape(radius: radius, corners: corners))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - UIViewRepresentable
|
||||||
|
fileprivate struct ClearBackgroundView: UIViewRepresentable {
|
||||||
|
/// The Key
|
||||||
|
func makeUIView(context: Context) -> UIView {
|
||||||
|
let view = UIView()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
/// GOD BLESS UI KIT
|
||||||
|
/// Target sheet system background view
|
||||||
|
/// Apply clear Color
|
||||||
|
view.superview?.superview?.backgroundColor = .clear
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UIView, context: Context) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - View Extension
|
||||||
|
extension View {
|
||||||
|
/// Return a formatted View Based on Device Type
|
||||||
|
/// if iPad => Centred Alert-Like View
|
||||||
|
/// if iPhone => Bottom Sheet View
|
||||||
|
/// That's all folks
|
||||||
|
func asResponsiveSheet() -> some View {
|
||||||
|
self.modifier(ResponsiveSheetWrapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func asResponsiveSheetContent() -> some View {
|
||||||
|
self.modifier(ResponsiveSheetContent())
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func clearSheetSystemBackground() -> some View {
|
||||||
|
self.modifier(ClearBackgroundViewModifier())
|
||||||
|
}
|
||||||
|
|
||||||
|
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
|
||||||
|
self.modifier(CornerRadiusStyle(radius: radius, corners: corners))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - EnvironmentKey & Values
|
||||||
|
fileprivate struct SafeAreaInsetsKey: EnvironmentKey {
|
||||||
|
static var defaultValue: EdgeInsets {
|
||||||
|
(UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.safeAreaInsets ?? .zero).insets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension EnvironmentValues {
|
||||||
|
var safeAreaInsets: EdgeInsets {
|
||||||
|
self[SafeAreaInsetsKey.self]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - UIEdgeInsets Extension
|
||||||
|
fileprivate extension UIEdgeInsets {
|
||||||
|
var insets: EdgeInsets {
|
||||||
|
EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right)
|
||||||
|
}
|
||||||
|
}
|
||||||
36
AlloVoisinsSwiftUI/Components/FontRegistration.swift
Normal file
36
AlloVoisinsSwiftUI/Components/FontRegistration.swift
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// FontRegistration.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Victor on 12/06/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreGraphics
|
||||||
|
import CoreText
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct FontRegistration {
|
||||||
|
public enum FontError: Swift.Error {
|
||||||
|
case failedToRegisterFont
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFonts() throws {
|
||||||
|
let fontNames = [
|
||||||
|
"TTChocolates-Bold-Italic",
|
||||||
|
"TTChocolates-Bold",
|
||||||
|
"TTChocolates-DemiBold",
|
||||||
|
"TTChocolates-Medium",
|
||||||
|
"TTChocolates-MediumIt"
|
||||||
|
]
|
||||||
|
|
||||||
|
for name in fontNames {
|
||||||
|
guard let asset = NSDataAsset(name: "Fonts/\(name)"),
|
||||||
|
let provider = CGDataProvider(data: asset.data as NSData),
|
||||||
|
let font = CGFont(provider),
|
||||||
|
CTFontManagerRegisterGraphicsFont(font, nil)
|
||||||
|
else {
|
||||||
|
throw FontError.failedToRegisterFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
AlloVoisinsSwiftUI/Extensions/Extension+Color.swift
Normal file
55
AlloVoisinsSwiftUI/Extensions/Extension+Color.swift
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// Extension+Color.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Victor on 12/06/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension Color {
|
||||||
|
static func sqRoyal(_ variant: Int = 60) -> Color {
|
||||||
|
return Color("ROYAL_\(variant)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sqPurple(_ variant: Int = 60) -> Color {
|
||||||
|
return Color("PURPLE_\(variant)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sqNeutral(_ variant: Int = 60) -> Color {
|
||||||
|
return Color("NEUTRAL_\(variant)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sqOrange(_ variant: Int = 60) -> Color {
|
||||||
|
return Color("ORANGE_\(variant)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sqGreen(_ variant: Int = 60) -> Color {
|
||||||
|
return Color("GREEN_\(variant)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sqYellow(_ variant: Int = 60) -> Color {
|
||||||
|
return Color("YELLOW_\(variant)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sqGold(_ variant: Int = 60) -> Color {
|
||||||
|
return Color("GOLD_\(variant)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sqBlue(_ variant: Int = 60) -> Color {
|
||||||
|
return Color("BLUE_\(variant)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sqGrape(_ variant: Int = 60) -> Color {
|
||||||
|
return Color("GRAPE_\(variant)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static let sqSemanticRed = Color("SEMANTIC_RED")
|
||||||
|
static let sqSemanticOrange = Color("SEMANTIC_CRITICAL")
|
||||||
|
static let sqSemanticGreen = Color("SEMANTIC_GREEN")
|
||||||
|
static let sqSemanticBlue = Color("SEMANTIC_BLUE")
|
||||||
|
static let sqSemanticCritical = Color("SEMANTIC_CRITICAL")
|
||||||
|
static let sqSemanticWarning = Color("SEMANTIC_WARNING")
|
||||||
|
static let sqSemanticPositive = Color("SEMANTIC_GREEN")
|
||||||
|
static let sqSemanticNegative = Color("SEMANTIC_RED")
|
||||||
|
}
|
||||||
41
AlloVoisinsSwiftUI/Extensions/Extension+View.swift
Normal file
41
AlloVoisinsSwiftUI/Extensions/Extension+View.swift
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// Extension+View.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Victor on 19/06/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
//extension View {
|
||||||
|
// func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
|
||||||
|
// clipShape( RoundedCorner(radius: radius, corners: corners) )
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//struct RoundedCorner: Shape {
|
||||||
|
// let radius: CGFloat
|
||||||
|
// let corners: UIRectCorner
|
||||||
|
//
|
||||||
|
// init(radius: CGFloat = .infinity, corners: UIRectCorner = .allCorners) {
|
||||||
|
// self.radius = radius
|
||||||
|
// self.corners = corners
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func path(in rect: CGRect) -> Path {
|
||||||
|
// let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
|
||||||
|
// return Path(path.cgPath)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func sqFont(_ font: SQTextFont, size: CGFloat = 16) -> some View {
|
||||||
|
self.font(.custom(font.rawValue, size: size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Font {
|
||||||
|
static func sq(_ font: SQTextFont, size: CGFloat = 16) -> Font {
|
||||||
|
.custom(font.rawValue, size: size)
|
||||||
|
}
|
||||||
|
}
|
||||||
14
AlloVoisinsSwiftUI/Info.plist
Normal file
14
AlloVoisinsSwiftUI/Info.plist
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>UIAppFonts</key>
|
||||||
|
<array>
|
||||||
|
<string>TTChocolates-Bold.otf</string>
|
||||||
|
<string>TTChocolates-Bold-Italic.otf</string>
|
||||||
|
<string>TTChocolates-DemiBold.otf</string>
|
||||||
|
<string>TTChocolates-Medium.otf</string>
|
||||||
|
<string>TTChocolates-MediumIt.otf</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -8,13 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct BoosterConfirmationScreen: View {
|
struct BoosterConfirmationScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -22,7 +15,7 @@ struct BoosterConfirmationScreen: View {
|
|||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
VStack {
|
VStack {
|
||||||
VStack(spacing: 32) {
|
VStack(spacing: 32) {
|
||||||
Image("booster_logo", bundle: Bundle.module)
|
Image("booster_logo")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 210, height: 180)
|
.frame(width: 210, height: 180)
|
||||||
SQText("C’est confirmé !", size: 18, font: .bold)
|
SQText("C’est confirmé !", size: 18, font: .bold)
|
||||||
|
|||||||
@@ -8,13 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct BoosterKnowAboutScreen: View {
|
struct BoosterKnowAboutScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView(showsIndicators: false) {
|
||||||
|
|||||||
@@ -8,11 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct BoosterSubscriptionManagementScreen: View {
|
struct BoosterSubscriptionManagementScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@State var selectedValue = 0
|
@State var selectedValue = 0
|
||||||
@State var presentControls = false
|
@State var presentControls = false
|
||||||
@@ -43,9 +38,9 @@ struct BoosterSubscriptionManagementScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.bottomSheet(isShowing: $presentControls, content: {
|
// .bottomSheet(isShowing: $presentControls, content: {
|
||||||
BoosterSubscriptionOptionsView()
|
// BoosterSubscriptionOptionsView()
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,12 +39,6 @@ struct BoosterSubscriptionSelectionScreen: View {
|
|||||||
var cancellation: Bool = true
|
var cancellation: Bool = true
|
||||||
@State var mode: BoosterSubscriptionMode = .edit
|
@State var mode: BoosterSubscriptionMode = .edit
|
||||||
@State var isNotPremier = false
|
@State var isNotPremier = false
|
||||||
|
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
@@ -63,7 +57,7 @@ struct BoosterSubscriptionSelectionScreen: View {
|
|||||||
VStack(alignment: .trailing) {
|
VStack(alignment: .trailing) {
|
||||||
BoosterLockedToPremierView()
|
BoosterLockedToPremierView()
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
Image("booster_logo", bundle: Bundle.module)
|
Image("booster_logo")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 210, height: 180)
|
.frame(width: 210, height: 180)
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct OnlyForPremierModal: View {
|
struct OnlyForPremierModal: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
@@ -23,7 +18,7 @@ struct OnlyForPremierModal: View {
|
|||||||
.fill(Color.sqOrange(50))
|
.fill(Color.sqOrange(50))
|
||||||
.frame(height: 200)
|
.frame(height: 200)
|
||||||
VStack(alignment: .leading, spacing: 32) {
|
VStack(alignment: .leading, spacing: 32) {
|
||||||
Image("only_for_premier", bundle: .module)
|
Image("only_for_premier")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 307, height: 108)
|
.frame(width: 307, height: 108)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct RegulatedProfessionEditProfilModal: View {
|
struct RegulatedProfessionEditProfilModal: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
|
|||||||
@@ -8,11 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct RegulatedProfessionModal: View {
|
struct RegulatedProfessionModal: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack() {
|
VStack() {
|
||||||
|
|||||||
@@ -8,86 +8,84 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AlloVoisinReputationScreen: View {
|
struct AlloVoisinReputationScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 32) {
|
VStack(spacing: 16) {
|
||||||
VStack {
|
VStack(spacing: 32) {
|
||||||
SQText("Le saviez-vous ?", size: 20, font: .bold)
|
|
||||||
SQText("AlloVoisins en France, c’est :", size: 20, font: .bold)
|
|
||||||
}
|
|
||||||
VStack {
|
|
||||||
SQText("4,5 millions", size: 32, font: .bold)
|
|
||||||
SQText("de membres, partout en France")
|
|
||||||
}
|
|
||||||
.padding(16)
|
|
||||||
.background(Color.white)
|
|
||||||
.cornerRadius(8)
|
|
||||||
VStack(spacing: 8) {
|
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
SQText("Le saviez-vous ?", size: 20, font: .bold)
|
||||||
SQIcon(.star, size: .xxl, type: .solid, color: .sqGold(50))
|
SQText("AlloVoisins en France, c’est :", size: 20, font: .bold)
|
||||||
SQText("4,6/5", size: 32, font: .bold)
|
|
||||||
}
|
|
||||||
SQText("Calculé à partir de 107,1 k avis")
|
|
||||||
}
|
}
|
||||||
HStack(spacing: 16) {
|
VStack {
|
||||||
HStack(spacing: 2) {
|
SQText("4,5 millions", size: 32, font: .bold)
|
||||||
SQIcon(.apple_brand, size: .s)
|
SQText("de membres, partout en France")
|
||||||
SQText("App Store", size: 10)
|
}
|
||||||
|
.padding(16)
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(8)
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
SQIcon(.star, size: .xxl, type: .solid, color: .sqGold(50))
|
||||||
|
SQText("4,6/5", size: 32, font: .bold)
|
||||||
|
}
|
||||||
|
SQText("Calculé à partir de 107,1 k avis")
|
||||||
}
|
}
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 16) {
|
||||||
SQIcon(.play_store_brand, size: .s)
|
HStack(spacing: 2) {
|
||||||
SQText("Google Play", size: 10)
|
SQIcon(.apple_brand, size: .s)
|
||||||
|
SQText("App Store", size: 10)
|
||||||
|
}
|
||||||
|
HStack(spacing: 2) {
|
||||||
|
SQIcon(.play_store_brand, size: .s)
|
||||||
|
SQText("Google Play", size: 10)
|
||||||
|
}
|
||||||
|
HStack(spacing: 2) {
|
||||||
|
SQIcon(.star_trustpilot_brand, size: .s)
|
||||||
|
SQText("Trustpilot", size: 10)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
HStack(spacing: 2) {
|
}
|
||||||
SQIcon(.star_trustpilot_brand, size: .s)
|
.padding(16)
|
||||||
SQText("Trustpilot", size: 10)
|
.background(Color.white)
|
||||||
|
.cornerRadius(8)
|
||||||
|
VStack {
|
||||||
|
SQText("La presse en parle", size: 24, font: .bold)
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
Image("m6")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(height: 24)
|
||||||
|
Image("rtl")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(height: 24)
|
||||||
|
Image("tf1")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(height: 24)
|
||||||
|
Image("bfm")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(height: 24)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(16)
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(8)
|
||||||
|
SQButton("Conserver mon abonnement", color: .sqNeutral(100), textColor: .white) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(16)
|
.padding(16)
|
||||||
.background(Color.white)
|
.background(Color.sqBlue(10))
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
VStack {
|
|
||||||
SQText("La presse en parle", size: 24, font: .bold)
|
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||||
HStack(spacing: 16) {
|
|
||||||
Image("m6", bundle: Bundle.module)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(height: 24)
|
|
||||||
Image("rtl", bundle: Bundle.module)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(height: 24)
|
|
||||||
Image("tf1", bundle: Bundle.module)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(height: 24)
|
|
||||||
Image("bfm", bundle: Bundle.module)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(height: 24)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(16)
|
|
||||||
.background(Color.white)
|
|
||||||
.cornerRadius(8)
|
|
||||||
SQButton("Conserver mon abonnement", color: .sqNeutral(100), textColor: .white) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(16)
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
.background(Color.sqBlue(10))
|
|
||||||
.cornerRadius(8)
|
|
||||||
|
|
||||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ struct ContinueAsParticularScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
.padding(16)
|
.padding(16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct GetMoreRatingsScreen: View {
|
struct GetMoreRatingsScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
@@ -47,6 +40,7 @@ struct GetMoreRatingsScreen: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MoreTimeScreen: View {
|
struct MoreTimeScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
@@ -39,6 +32,7 @@ struct MoreTimeScreen: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,13 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct OnlyProRequestsScreen: View {
|
struct OnlyProRequestsScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
VStack(spacing: 32) {
|
VStack(spacing: 32) {
|
||||||
SQText("Demandes réservées aux pros", size: 20, font: .bold)
|
SQText("Demandes réservées aux pros", size: 20, font: .bold)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
Image("only_for_pro", bundle: Bundle.module)
|
Image("only_for_pro")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(height: 60)
|
.frame(height: 60)
|
||||||
@@ -47,6 +40,7 @@ struct OnlyProRequestsScreen: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,13 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PersonalizedSupportScreen: View {
|
struct PersonalizedSupportScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
VStack(spacing: 32) {
|
VStack(spacing: 32) {
|
||||||
SQText("Bénéficiez d’un accompagnement personnalisé.", size: 20, font: .bold)
|
SQText("Bénéficiez d’un accompagnement personnalisé.", size: 20, font: .bold)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
Image("assistance", bundle: Bundle.module)
|
Image("assistance")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(height: 100)
|
.frame(height: 100)
|
||||||
@@ -42,6 +35,7 @@ struct PersonalizedSupportScreen: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ struct ProfileCompletionScreen: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// ResiliationCheckStepView.swift
|
// ResiliationCheckStepsScreen.swift
|
||||||
// Sequoia
|
// Sequoia
|
||||||
//
|
//
|
||||||
// Created by Victor on 10/10/2024.
|
// Created by Victor on 10/10/2024.
|
||||||
@@ -7,58 +7,21 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
private struct ResiliationCheckStepView: View {
|
|
||||||
var checkStep: ResiliationCheckStep
|
|
||||||
@Binding var isSelected: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
|
||||||
.fill(isSelected ? Color.sqNeutral(100) : .clear)
|
|
||||||
.stroke(isSelected ? Color.sqNeutral(100) : Color.sqNeutral(30), lineWidth: 1)
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Spacer()
|
|
||||||
Image(checkStep.image, bundle: Bundle.main)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(height: 100)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
SQText(checkStep.text)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.stroke(Color.sqNeutral(30), lineWidth: 1)
|
|
||||||
)
|
|
||||||
.onTapGesture {
|
|
||||||
self.isSelected.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ResiliationCheckStepsScreen: View {
|
struct ResiliationCheckStepsScreen: View {
|
||||||
|
@State var nextStep: Bool = false
|
||||||
@StateObject var viewModel: ResiliationViewModel
|
@StateObject var viewModel: ResiliationViewModel
|
||||||
var steps: [ResiliationCheckStep] {
|
|
||||||
viewModel.resiliationCheckSteps
|
|
||||||
}
|
|
||||||
@State private var selectedSteps: Set<ResiliationCheckStep> = []
|
@State private var selectedSteps: Set<ResiliationCheckStep> = []
|
||||||
|
|
||||||
var allStepsSelected: Bool {
|
var allStepsSelected: Bool {
|
||||||
selectedSteps.count == steps.count
|
selectedSteps.count == viewModel.resiliationCheckSteps.count
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
ForEach(steps, id: \.self) { step in
|
SQText("En résiliant votre abonnement vous renoncez à :", size: 18, font: .bold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
ForEach(viewModel.resiliationCheckSteps, id: \.self) { step in
|
||||||
ResiliationCheckStepView(
|
ResiliationCheckStepView(
|
||||||
checkStep: step,
|
checkStep: step,
|
||||||
isSelected: Binding(
|
isSelected: Binding(
|
||||||
@@ -73,9 +36,28 @@ struct ResiliationCheckStepsScreen: View {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewModel.resiliationType == .apProWithTrial || viewModel.resiliationType == .apProWithTrialWithBooster {
|
||||||
|
TrialWarningCellView()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
SQButton("Annuler", color: .sqNeutral(100), isFull: false) {
|
||||||
|
|
||||||
|
}
|
||||||
|
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
|
||||||
|
nextStep.toggle()
|
||||||
|
}
|
||||||
|
.disabled(!allStepsSelected)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
.navigationDestination(isPresented: $nextStep) {
|
||||||
|
ResiliationReasonScreen(viewModel: viewModel)
|
||||||
|
}
|
||||||
|
.sqNavigationBar(title: "Demander la résiliation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// ResiliationProcess.swift
|
// ResiliationNavigationView.swift
|
||||||
// AlloVoisinsSwiftUI
|
// AlloVoisinsSwiftUI
|
||||||
//
|
//
|
||||||
// Created by Victor on 14/10/2024.
|
// Created by Victor on 14/10/2024.
|
||||||
@@ -32,57 +32,16 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ResiliationNavigationView: View {
|
struct ResiliationNavigationView: View {
|
||||||
@StateObject private var coordinator = ResiliationNavigationCoordinator()
|
|
||||||
@StateObject private var viewModel: ResiliationViewModel
|
@StateObject private var viewModel: ResiliationViewModel
|
||||||
|
|
||||||
init(resiliationType: ResiliationType) {
|
init(resiliationType: ResiliationType) {
|
||||||
let coordinator = ResiliationNavigationCoordinator()
|
|
||||||
self._coordinator = StateObject(wrappedValue: coordinator)
|
|
||||||
self._viewModel = StateObject(wrappedValue: ResiliationViewModel(resiliationType: resiliationType))
|
self._viewModel = StateObject(wrappedValue: ResiliationViewModel(resiliationType: resiliationType))
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack(path: $coordinator.path) {
|
NavigationStack {
|
||||||
ResiliationCheckStepsScreen(viewModel: viewModel)
|
ResiliationCheckStepsScreen(viewModel: viewModel)
|
||||||
.navigationDestination(for: ResiliationNavigationCoordinator.Screen.self) { screen in
|
.navigationBarTitle("Résiliation", displayMode: .inline)
|
||||||
switch screen {
|
|
||||||
case .reason:
|
|
||||||
ResiliationReasonScreen(viewModel: viewModel)
|
|
||||||
case .promotional(let promotionalScreen):
|
|
||||||
getPromotionalScreen(for: promotionalScreen)
|
|
||||||
case .final:
|
|
||||||
EmptyView()
|
|
||||||
case .checkSteps:
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.environmentObject(coordinator)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private func getPromotionalScreen(for screen: ResiliationPromotionalScreen) -> some View {
|
|
||||||
switch screen {
|
|
||||||
case .webinarPresentation:
|
|
||||||
WebinaireScreen()
|
|
||||||
case .profileCompletion:
|
|
||||||
ProfileCompletionScreen()
|
|
||||||
case .editPerimeter:
|
|
||||||
ResizePerimeterScreen()
|
|
||||||
case .onlyProSearches:
|
|
||||||
OnlyProRequestsScreen()
|
|
||||||
case .oneMonthBoosterOffered:
|
|
||||||
BoosterSubscriptionSelectionScreen()
|
|
||||||
case .allovoisinsPromotion:
|
|
||||||
AlloVoisinReputationScreen()
|
|
||||||
case .extendMyTrialPeriod:
|
|
||||||
MoreTimeScreen()
|
|
||||||
case .askPartProStatusChangeWithCancellation, .askPartProStatusChange:
|
|
||||||
StatusChangeScreen()
|
|
||||||
case .customerSupportForPro, .customerSupportForPart:
|
|
||||||
PersonalizedSupportScreen()
|
|
||||||
default:
|
|
||||||
EmptyView()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,42 +8,42 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ResiliationReasonScreen: View {
|
struct ResiliationReasonScreen: View {
|
||||||
@State var selectedIndex: Int? = nil
|
@Environment(\.dismiss) var dismiss
|
||||||
@State var resiliationOtherMotif: String = ""
|
@ObservedObject var viewModel: ResiliationViewModel
|
||||||
|
@State private var selectedIndex: Int? = nil
|
||||||
|
@State private var resiliationOtherMotif: String = ""
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 16) {
|
ScrollView {
|
||||||
SQRadio(title: "Aidez-nous à nous améliorer : précisez le motif de votre résiliation",
|
VStack(spacing: 16) {
|
||||||
options: [
|
SQRadio(title: "Aidez-nous à nous améliorer : précisez le motif de votre résiliation",
|
||||||
"Je ne suis pas un professionnel, c’est une erreur",
|
options: viewModel.resiliationReasons.map { $0.text },
|
||||||
"Je reçois des demandes mais je n’ai pas assez de réponses positives à mes propositions",
|
selectedIndex: $selectedIndex)
|
||||||
"Je ne reçois pas assez de demandes",
|
if selectedIndex == viewModel.resiliationReasons.count - 1 {
|
||||||
"Je n’ai pas eu suffisamment de temps pour me faire une opinion",
|
|
||||||
"J’ai des demandes, mais elles ne m’intéressent pas",
|
|
||||||
"Je n’ai pas compris le fonctionnement de l’abonnement Premier",
|
|
||||||
"Je suis sur d’autres plateformes plus intéressantes",
|
|
||||||
"Autre",
|
|
||||||
],
|
|
||||||
selectedIndex: $selectedIndex)
|
|
||||||
if selectedIndex == 7 {
|
|
||||||
SQTextField("", placeholder: "Précisez-nous le motif de votre résiliation", text: $resiliationOtherMotif)
|
SQTextField("", placeholder: "Précisez-nous le motif de votre résiliation", text: $resiliationOtherMotif)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
SQButton("Annuler", color: .sqNeutral(100), isFull: false) {
|
SQButton("Annuler", color: .sqNeutral(100), isFull: false) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
|
||||||
|
// TODO: Aller au prochain promotional screen
|
||||||
|
if let index = selectedIndex {
|
||||||
|
let selectedReason = viewModel.resiliationReasons[index]
|
||||||
|
viewModel.setSelectedReason(selectedReason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(selectedIndex == nil)
|
||||||
}
|
}
|
||||||
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
|
Spacer()
|
||||||
|
|
||||||
}
|
|
||||||
.disabled(selectedIndex == nil)
|
|
||||||
}
|
}
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
.sqNavigationBar(title: "Demander la résiliation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ResiliationReasonScreen()
|
ResiliationReasonScreen(viewModel: ResiliationViewModel(resiliationType: .apProWithTrial))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,29 +9,32 @@ import SwiftUI
|
|||||||
|
|
||||||
struct ResizePerimeterScreen: View {
|
struct ResizePerimeterScreen: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 32) {
|
VStack(spacing: 16) {
|
||||||
SQText("Augmentez votre périmètre afin de recevoir plus de demandes", size: 20, font: .bold)
|
VStack(spacing: 32) {
|
||||||
.multilineTextAlignment(.center)
|
SQText("Augmentez votre périmètre afin de recevoir plus de demandes", size: 20, font: .bold)
|
||||||
Image("perimeter", bundle: Bundle.module)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(height: 140)
|
|
||||||
VStack {
|
|
||||||
SQText("Le saviez-vous ?", font: .demiBold)
|
|
||||||
SQText("Si vous le souhaitez, vous pouvez modifier à tout moment votre abonnement en élargissant votre périmètre d’intervention.")
|
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
Image("perimeter")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(height: 140)
|
||||||
|
VStack {
|
||||||
|
SQText("Le saviez-vous ?", font: .demiBold)
|
||||||
|
SQText("Si vous le souhaitez, vous pouvez modifier à tout moment votre abonnement en élargissant votre périmètre d’intervention.")
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
SQButton("Modifier mon périmètre", color: .sqOrange(50), textColor: .white) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SQButton("Modifier mon périmètre", color: .sqOrange(50), textColor: .white) {
|
.padding(16)
|
||||||
|
.background(Color.sqOrange(10))
|
||||||
|
.cornerRadius(8)
|
||||||
|
|
||||||
|
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(16)
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
.background(Color.sqOrange(10))
|
|
||||||
.cornerRadius(8)
|
|
||||||
|
|
||||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ struct SoftwarePresentationScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct StatusChangeScreen: View {
|
struct StatusChangeScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@@ -30,6 +25,7 @@ struct StatusChangeScreen: View {
|
|||||||
.background(Color.sqOrange(10))
|
.background(Color.sqOrange(10))
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
}
|
}
|
||||||
|
.sqNavigationBar(title: "Changer de statut")
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,13 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct WebinaireScreen: View {
|
struct WebinaireScreen: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
VStack(spacing: 32) {
|
VStack(spacing: 32) {
|
||||||
SQText("Webinaire spécial Pro", size: 20, font: .bold)
|
SQText("Webinaire spécial Pro", size: 20, font: .bold)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
Image("webinaire", bundle: Bundle.module)
|
Image("webinaire")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(height: 140)
|
.frame(height: 140)
|
||||||
@@ -43,6 +36,7 @@ struct WebinaireScreen: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sqNavigationBar(title: "Ne partez pas !")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,4 @@
|
|||||||
// Created by Victor on 14/10/2024.
|
// Created by Victor on 14/10/2024.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import SwiftUI
|
||||||
|
|||||||
@@ -6,3 +6,150 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SwiftUICore
|
||||||
|
|
||||||
|
class ResiliationViewModel: ObservableObject {
|
||||||
|
@Published var resiliationType: ResiliationType
|
||||||
|
@Published var resiliationCheckSteps: [ResiliationCheckStep]
|
||||||
|
@Published var resiliationReasons: [ResiliationReason]
|
||||||
|
@Published var selectedReason: ResiliationReason?
|
||||||
|
@Published var promotionalScreens: [ResiliationPromotionalScreen] = []
|
||||||
|
@Published var currentScreen: Screen = .checkSteps
|
||||||
|
|
||||||
|
enum Screen: Equatable {
|
||||||
|
case checkSteps
|
||||||
|
case reason
|
||||||
|
case promotional(ResiliationPromotionalScreen)
|
||||||
|
case final
|
||||||
|
}
|
||||||
|
|
||||||
|
init(resiliationType: ResiliationType) {
|
||||||
|
self.resiliationType = resiliationType
|
||||||
|
self.resiliationCheckSteps = ResiliationCheckStep.getCheckSteps(for: resiliationType)
|
||||||
|
self.resiliationReasons = ResiliationReason.getRadioResiliation(for: resiliationType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveToReason() {
|
||||||
|
currentScreen = .reason
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSelectedReason(_ reason: ResiliationReason) {
|
||||||
|
selectedReason = reason
|
||||||
|
promotionalScreens = selectResiliationPromotions(
|
||||||
|
availablePromotions: getAvailablePromotionalScreens(for: reason),
|
||||||
|
eligibleScreens: getEligiblePromotionalScreens()
|
||||||
|
)
|
||||||
|
|
||||||
|
if promotionalScreens.isEmpty {
|
||||||
|
currentScreen = .final
|
||||||
|
} else {
|
||||||
|
currentScreen = .promotional(promotionalScreens.removeFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveToNextPromotionalScreen() {
|
||||||
|
if !promotionalScreens.isEmpty {
|
||||||
|
currentScreen = .promotional(promotionalScreens.removeFirst())
|
||||||
|
} else {
|
||||||
|
currentScreen = .final
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func selectResiliationPromotions(availablePromotions: [ResiliationPromotionalScreen], eligibleScreens: [ResiliationPromotionalScreen]) -> [ResiliationPromotionalScreen] {
|
||||||
|
let shuffledArray1 = availablePromotions.shuffled()
|
||||||
|
|
||||||
|
let commonPromotions = shuffledArray1.filter { promotion in
|
||||||
|
eligibleScreens.contains(promotion)
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = commonPromotions.map { $0 }
|
||||||
|
|
||||||
|
return Array(result.prefix(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getAvailablePromotionalScreens(for reason: ResiliationReason) -> [ResiliationPromotionalScreen] {
|
||||||
|
switch reason {
|
||||||
|
case .noPositiveResponses:
|
||||||
|
return [.externalReview, .webinarPresentation, .profileCompletion]
|
||||||
|
case .uninterestingRequests:
|
||||||
|
return [.webinarPresentation, .editPerimeter, .onlyProSearches]
|
||||||
|
case .lowDemand:
|
||||||
|
return [.externalReview, .profileCompletion, .editPerimeter, .oneMonthBoosterOffered]
|
||||||
|
case .betterPlatforms:
|
||||||
|
// TODO: Rajouter présentation Logiciel
|
||||||
|
return [.oneMonthBoosterOffered, .allovoisinsPromotion, .extendMyTrialPeriod, .webinarPresentation]
|
||||||
|
case .notPro:
|
||||||
|
return [.askPartProStatusChange]
|
||||||
|
case .insufficientTime:
|
||||||
|
return [.extendMyTrialPeriod]
|
||||||
|
case .misunderstoodPremier:
|
||||||
|
// TODO: Check si c'est le bon .cusrommerSupport*
|
||||||
|
return [.webinarPresentation, .customerSupportForPro]
|
||||||
|
default:
|
||||||
|
return [.allovoisinsPromotion]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getEligiblePromotionalScreens() -> [ResiliationPromotionalScreen] {
|
||||||
|
return [.extendMyTrialPeriod, .profileCompletion, .allovoisinsPromotion]
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getPromotionalScreen(for screen: ResiliationPromotionalScreen) -> any View {
|
||||||
|
switch screen {
|
||||||
|
case .externalReview:
|
||||||
|
break
|
||||||
|
case .webinarPresentation:
|
||||||
|
return WebinaireScreen()
|
||||||
|
case .profileCompletion:
|
||||||
|
return ProfileCompletionScreen()
|
||||||
|
case .editPerimeter:
|
||||||
|
return ResizePerimeterScreen()
|
||||||
|
case .onlyProSearches:
|
||||||
|
return OnlyProRequestsScreen()
|
||||||
|
case .oneMonthBoosterOffered:
|
||||||
|
return BoosterSubscriptionSelectionScreen()
|
||||||
|
case .allovoisinsPromotion:
|
||||||
|
return AlloVoisinReputationScreen()
|
||||||
|
case .extendMyTrialPeriod:
|
||||||
|
return MoreTimeScreen()
|
||||||
|
case .askPartProStatusChangeWithCancellation:
|
||||||
|
return StatusChangeScreen()
|
||||||
|
case .customerSupportForPro:
|
||||||
|
return PersonalizedSupportScreen()
|
||||||
|
case .customerSupportForPart:
|
||||||
|
return PersonalizedSupportScreen()
|
||||||
|
case .askPartProStatusChange:
|
||||||
|
return StatusChangeScreen()
|
||||||
|
case .ordersPresentation:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return EmptyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func getPromotionalScreenNew(for screen: ResiliationPromotionalScreen) -> some View {
|
||||||
|
switch screen {
|
||||||
|
case .webinarPresentation:
|
||||||
|
WebinaireScreen()
|
||||||
|
case .profileCompletion:
|
||||||
|
ProfileCompletionScreen()
|
||||||
|
case .editPerimeter:
|
||||||
|
ResizePerimeterScreen()
|
||||||
|
case .onlyProSearches:
|
||||||
|
OnlyProRequestsScreen()
|
||||||
|
case .oneMonthBoosterOffered:
|
||||||
|
BoosterSubscriptionSelectionScreen()
|
||||||
|
case .allovoisinsPromotion:
|
||||||
|
AlloVoisinReputationScreen()
|
||||||
|
case .extendMyTrialPeriod:
|
||||||
|
MoreTimeScreen()
|
||||||
|
case .askPartProStatusChangeWithCancellation, .askPartProStatusChange:
|
||||||
|
StatusChangeScreen()
|
||||||
|
case .customerSupportForPro, .customerSupportForPart:
|
||||||
|
PersonalizedSupportScreen()
|
||||||
|
default:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,16 +8,10 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct BoosterActiveHeaderView: View {
|
struct BoosterActiveHeaderView: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Image("booster_logo", bundle: Bundle.module)
|
Image("booster_logo")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 93, height: 80)
|
.frame(width: 93, height: 80)
|
||||||
VStack {
|
VStack {
|
||||||
@@ -30,7 +24,7 @@ struct BoosterActiveHeaderView: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.overlay(
|
.overlay(
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
Image("booster_corner", bundle: Bundle.module)
|
Image("booster_corner")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 50, height: 50)
|
||||||
.cornerRadius(8, corners: [.topLeft])
|
.cornerRadius(8, corners: [.topLeft])
|
||||||
|
|||||||
@@ -10,13 +10,6 @@ import SwiftUI
|
|||||||
struct BoosterHistoryCellView: View {
|
struct BoosterHistoryCellView: View {
|
||||||
var isEnded: Bool = false
|
var isEnded: Bool = false
|
||||||
|
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
@@ -28,7 +21,7 @@ struct BoosterHistoryCellView: View {
|
|||||||
}
|
}
|
||||||
.overlay(
|
.overlay(
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
Image("booster_corner", bundle: Bundle.module)
|
Image("booster_corner")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 50, height: 50)
|
||||||
.cornerRadius(8, corners: [.topLeft, .bottomLeft])
|
.cornerRadius(8, corners: [.topLeft, .bottomLeft])
|
||||||
|
|||||||
@@ -8,13 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct BoosterLockedToPremierView: View {
|
struct BoosterLockedToPremierView: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
SQIcon(.lock_keyhole, size: .xs, type: .solid, color: .white)
|
SQIcon(.lock_keyhole, size: .xs, type: .solid, color: .white)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ struct BoosterPromotionView: View {
|
|||||||
.foregroundColor(.sqRoyal())
|
.foregroundColor(.sqRoyal())
|
||||||
.overlay(
|
.overlay(
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
Image("booster_corner", bundle: Bundle.module)
|
Image("booster_corner")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 50, height: 50)
|
||||||
.cornerRadius(8, corners: .topLeft)
|
.cornerRadius(8, corners: .topLeft)
|
||||||
|
|||||||
@@ -8,13 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct BoosterStatsView: View {
|
struct BoosterStatsView: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ struct BoosterSubscriptionCardView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(id: Int, selectedId: Binding<Int>, currentOption: Bool = false, isFree: Bool = false) {
|
init(id: Int, selectedId: Binding<Int>, currentOption: Bool = false, isFree: Bool = false) {
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
self.id = id
|
self.id = id
|
||||||
self._selectedId = selectedId
|
self._selectedId = selectedId
|
||||||
self.currentOption = currentOption
|
self.currentOption = currentOption
|
||||||
@@ -61,7 +57,7 @@ struct BoosterSubscriptionCardView: View {
|
|||||||
.padding(.top, 30)
|
.padding(.top, 30)
|
||||||
.padding([.leading, .trailing])
|
.padding([.leading, .trailing])
|
||||||
|
|
||||||
Image("booster_corner_light", bundle: Bundle.module)
|
Image("booster_corner_light")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 50, height: 50)
|
||||||
.position(x: 25, y: 25)
|
.position(x: 25, y: 25)
|
||||||
|
|||||||
@@ -8,13 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct BoosterSubscriptionOptionsView: View {
|
struct BoosterSubscriptionOptionsView: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// ComplimentPillView.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Victor on 31/07/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ComplimentPillView: View {
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
VStack {
|
||||||
|
SQIcon(.gen_euro_sign, size: .s, color: .sqGreen(100))
|
||||||
|
}
|
||||||
|
.padding(3)
|
||||||
|
.frame(width: 32, height: 32, alignment: .center)
|
||||||
|
.background(Color.sqGreen(10))
|
||||||
|
.cornerRadius(32)
|
||||||
|
SQText("Excellent rapport qualité/prix", size: 12, font: .demiBold)
|
||||||
|
}
|
||||||
|
.padding(.leading, 1)
|
||||||
|
.padding(.trailing, 16)
|
||||||
|
.padding(.vertical, 1)
|
||||||
|
.cornerRadius(40)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 40)
|
||||||
|
.inset(by: 0.5)
|
||||||
|
.stroke(Color.sqNeutral(20), lineWidth: 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ComplimentPillView()
|
||||||
|
}
|
||||||
46
AlloVoisinsSwiftUI/Views/Compliments/ComplimentView.swift
Normal file
46
AlloVoisinsSwiftUI/Views/Compliments/ComplimentView.swift
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// ComplimentView.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Victor on 31/07/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ComplimentView: View {
|
||||||
|
var body: some View {
|
||||||
|
ZStack(alignment: .topTrailing) {
|
||||||
|
VStack(spacing: 4) {
|
||||||
|
VStack {
|
||||||
|
SQIcon(.gen_euro_sign, size: .xl, color: .sqGreen(100))
|
||||||
|
}
|
||||||
|
.frame(width: 56, height: 56, alignment: .center)
|
||||||
|
.background(Color.sqGreen(10))
|
||||||
|
.cornerRadius(100)
|
||||||
|
SQText("Excellent rapport qualité/prix", size: 11, font: .demiBold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundColor(Color.sqNeutral(90))
|
||||||
|
}
|
||||||
|
.padding(.top, 4)
|
||||||
|
.frame(width: 87)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
SQText("1", size: 11, font: .bold)
|
||||||
|
.foregroundColor(Color.sqNeutral())
|
||||||
|
.padding(.horizontal, 6)
|
||||||
|
.frame(height: 16)
|
||||||
|
.background(.white)
|
||||||
|
.cornerRadius(30)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 30)
|
||||||
|
.stroke(Color.sqNeutral(60), lineWidth: 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(.trailing, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ComplimentView()
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// ProfileComplimentListView.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Victor on 31/07/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
private struct Compliment: Identifiable {
|
||||||
|
let id: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProfileComplimentListView: View {
|
||||||
|
private let compliments: [Compliment] = [
|
||||||
|
Compliment(id: 1),
|
||||||
|
Compliment(id: 2),
|
||||||
|
Compliment(id: 3),
|
||||||
|
Compliment(id: 4),
|
||||||
|
Compliment(id: 5)
|
||||||
|
]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
SQText("Compliments reçus", size: 18, font: .bold)
|
||||||
|
.padding(.horizontal)
|
||||||
|
VStack(alignment: .center, spacing: 16) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
ForEach(Array(stride(from: 0, to: compliments.count, by: 3)), id: \.self) { index in
|
||||||
|
HStack(spacing: 24) {
|
||||||
|
ForEach(index ..< min(index + 3, compliments.count), id: \.self) { _ in
|
||||||
|
ComplimentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle()
|
||||||
|
.frame(height: 16)
|
||||||
|
.foregroundColor(Color.sqNeutral(10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ProfileComplimentListView()
|
||||||
|
}
|
||||||
@@ -8,11 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct NeighborBanner: View {
|
struct NeighborBanner: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ struct SQIcon: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Image(type == .solid ? "\(name.rawValue)_solid" : name.rawValue, bundle: .module)
|
Image(type == .solid ? "\(name.rawValue)_solid" : name.rawValue)
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(height: customSize ?? size.rawValue)
|
.frame(height: customSize ?? size.rawValue)
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// SQNavigationBar.swift
|
||||||
|
// AlloVoisinsSwiftUI
|
||||||
|
//
|
||||||
|
// Created by Victor on 14/10/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SQNavigationBar: ViewModifier {
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
let title: String
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.navigationBarBackButtonHidden(true)
|
||||||
|
.navigationBarTitle(title, displayMode: .inline)
|
||||||
|
.navigationBarItems(leading: backButton)
|
||||||
|
.onAppear {
|
||||||
|
configureNavigationBarAppearance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var backButton: some View {
|
||||||
|
Button(action: {
|
||||||
|
self.presentationMode.wrappedValue.dismiss()
|
||||||
|
}) {
|
||||||
|
Image(systemName: "chevron.left")
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.imageScale(.large)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func configureNavigationBarAppearance() {
|
||||||
|
let appearance = UINavigationBarAppearance()
|
||||||
|
appearance.configureWithOpaqueBackground()
|
||||||
|
appearance.backgroundColor = .white
|
||||||
|
appearance.shadowColor = .gray.withAlphaComponent(0.3)
|
||||||
|
appearance.titleTextAttributes = [.foregroundColor: UIColor.black]
|
||||||
|
|
||||||
|
UINavigationBar.appearance().standardAppearance = appearance
|
||||||
|
UINavigationBar.appearance().compactAppearance = appearance
|
||||||
|
UINavigationBar.appearance().scrollEdgeAppearance = appearance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func sqNavigationBar(title: String) -> some View {
|
||||||
|
self.modifier(SQNavigationBar(title: title))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
36
AlloVoisinsSwiftUI/Views/Components/SQSocialBar.swift
Normal file
36
AlloVoisinsSwiftUI/Views/Components/SQSocialBar.swift
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// SQSocialBar.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Victor on 18/07/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SQSocialBar: View {
|
||||||
|
var style: SQSocialBarStyle = .full
|
||||||
|
|
||||||
|
init(style: SQSocialBarStyle) {
|
||||||
|
self.style = style
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SQSocialBarStyle {
|
||||||
|
case full
|
||||||
|
case compact
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SQSocialBar(style: .full)
|
||||||
|
}
|
||||||
46
AlloVoisinsSwiftUI/Views/Components/SQText.swift
Normal file
46
AlloVoisinsSwiftUI/Views/Components/SQText.swift
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// SQText.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Victor on 12/06/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
enum SQTextFont: String {
|
||||||
|
case medium = "TTChocolates-Medium"
|
||||||
|
case mediumItalic = "TTChocolates-MediumIt"
|
||||||
|
case demiBold = "TTChocolates-DemiBold"
|
||||||
|
case bold = "TTChocolates-Bold"
|
||||||
|
case boldItalic = "TTChocolates-Bold-Italic"
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SQText: View {
|
||||||
|
var text: String
|
||||||
|
var size: CGFloat
|
||||||
|
var font: SQTextFont
|
||||||
|
var textColor: Color
|
||||||
|
|
||||||
|
init(_ text: String, size: CGFloat = 16, font: SQTextFont = .medium, textColor: Color = .sqNeutral(90)) {
|
||||||
|
self.text = text
|
||||||
|
self.size = size
|
||||||
|
self.font = font
|
||||||
|
self.textColor = textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text(text)
|
||||||
|
.font(.custom(font.rawValue, size: size))
|
||||||
|
.foregroundStyle(textColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
SQText("Hello world!", font: .medium)
|
||||||
|
SQText("Hello world!", font: .mediumItalic)
|
||||||
|
SQText("Hello world!", size: 18, font: .demiBold)
|
||||||
|
SQText("Hello world!", font: .bold)
|
||||||
|
SQText("Hello world!", size: 18, font: .boldItalic)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,9 +27,6 @@ struct SQToast: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(_ title: String = "", content: String, style: SQToastStyle = .success, hasClose: Bool = false) {
|
init(_ title: String = "", content: String, style: SQToastStyle = .success, hasClose: Bool = false) {
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {}
|
|
||||||
self.title = title
|
self.title = title
|
||||||
self.content = content
|
self.content = content
|
||||||
self.style = style
|
self.style = style
|
||||||
|
|||||||
@@ -8,13 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct OnlyForPremierView: View {
|
struct OnlyForPremierView: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
|||||||
@@ -8,13 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct RatingStarsView: View {
|
struct RatingStarsView: View {
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
try FontRegistration().registerFonts()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .bottom) {
|
HStack(alignment: .bottom) {
|
||||||
|
|||||||
@@ -8,11 +8,46 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ResiliationCheckStepView: View {
|
struct ResiliationCheckStepView: View {
|
||||||
|
var checkStep: ResiliationCheckStep
|
||||||
|
@Binding var isSelected: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
VStack(alignment: .leading) {
|
||||||
|
if isSelected {
|
||||||
|
Image("checked_neutral")
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
} else {
|
||||||
|
Image("checkbox_unchecked")
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
}
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
Spacer()
|
||||||
|
Image(checkStep.image)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(height: 100)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
SQText(checkStep.text)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.stroke(Color.sqNeutral(30), lineWidth: 1)
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
self.isSelected.toggle()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ResiliationCheckStepView()
|
ResiliationCheckStepView(checkStep: .marketing, isSelected: .constant(true))
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
//
|
|
||||||
// ResiliationCheckStepView.swift
|
|
||||||
// Sequoia
|
|
||||||
//
|
|
||||||
// Created by Victor on 10/10/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
private struct ResiliationCheckStepView: View {
|
|
||||||
var checkStep: ResiliationCheckStep
|
|
||||||
@Binding var isSelected: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
|
||||||
.fill(isSelected ? Color.sqNeutral(100) : .clear)
|
|
||||||
.stroke(isSelected ? Color.sqNeutral(100) : Color.sqNeutral(30), lineWidth: 1)
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Spacer()
|
|
||||||
Image(checkStep.image, bundle: Bundle.main)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(height: 100)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
SQText(checkStep.text)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.stroke(Color.sqNeutral(30), lineWidth: 1)
|
|
||||||
)
|
|
||||||
.onTapGesture {
|
|
||||||
self.isSelected.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ResiliationCheckStepsScreen: View {
|
|
||||||
@StateObject var viewModel: ResiliationViewModel
|
|
||||||
var steps: [ResiliationCheckStep] {
|
|
||||||
viewModel.resiliationCheckSteps
|
|
||||||
}
|
|
||||||
@State private var selectedSteps: Set<ResiliationCheckStep> = []
|
|
||||||
|
|
||||||
var allStepsSelected: Bool {
|
|
||||||
selectedSteps.count == steps.count
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ScrollView {
|
|
||||||
VStack(spacing: 16) {
|
|
||||||
ForEach(steps, id: \.self) { step in
|
|
||||||
ResiliationCheckStepView(
|
|
||||||
checkStep: step,
|
|
||||||
isSelected: Binding(
|
|
||||||
get: { selectedSteps.contains(step) },
|
|
||||||
set: { isSelected in
|
|
||||||
if isSelected {
|
|
||||||
selectedSteps.insert(step)
|
|
||||||
} else {
|
|
||||||
selectedSteps.remove(step)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
ResiliationCheckStepsScreen(viewModel: ResiliationViewModel(resiliationType: .apPart))
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,12 @@ import SwiftUI
|
|||||||
|
|
||||||
struct TrialWarningCellView: View {
|
struct TrialWarningCellView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
VStack(spacing: 8) {
|
||||||
|
SQIcon(.triangle_exclamation, size: .xxl, type: .solid, color: .SEMANTIC_RED)
|
||||||
|
SQText("Votre période d’essai gratuite* se termine le 14 mars 2024. Toute résiliation avant cette date entraînera la coupure immédiate du service.", font: .demiBold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
SQText("* Offre d’essai valable une seule fois par utilisateur.", size: 13)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// NewPerimeterCellView.swift
|
||||||
|
// Sequoia
|
||||||
|
//
|
||||||
|
// Created by Victor on 26/09/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct NewPerimeterCellView: View {
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
SQText("Vous souhaitez ajouter un nouveau périmètre ?", size: 24, font: .bold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
SQText("Si vous souhaitez couvrir une zone géographique différente de votre périmètre actuel, souscrivez à un nouvel abonnement pour proposer vos services dans ce nouveau secteur.")
|
||||||
|
SQButton("Souscrire à un nouveau périmètre", color: .sqOrange(50), textColor: .sqOrange(50), isFull: false) {}
|
||||||
|
SQText("À partir de 9,99€ / mois, sans engagement", size: 13)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 32)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.cornerRadius(8)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.inset(by: 0.5)
|
||||||
|
.stroke(Color.sqNeutral(20), lineWidth: 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
NewPerimeterCellView()
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user