🚀 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; };
|
||||
/* 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 */
|
||||
57282ACD2CBD2810000C443E /* AlloVoisinsSwiftUI */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
57282BA72CBD579A000C443E /* Exceptions for "AlloVoisinsSwiftUI" folder in "AlloVoisinsSwiftUI" target */,
|
||||
);
|
||||
path = AlloVoisinsSwiftUI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -255,6 +268,7 @@
|
||||
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = AlloVoisinsSwiftUI/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@@ -284,6 +298,7 @@
|
||||
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = AlloVoisinsSwiftUI/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = 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 {
|
||||
var body: some Scene {
|
||||
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
|
||||
|
||||
struct BoosterConfirmationScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -22,7 +15,7 @@ struct BoosterConfirmationScreen: View {
|
||||
.ignoresSafeArea()
|
||||
VStack {
|
||||
VStack(spacing: 32) {
|
||||
Image("booster_logo", bundle: Bundle.module)
|
||||
Image("booster_logo")
|
||||
.resizable()
|
||||
.frame(width: 210, height: 180)
|
||||
SQText("C’est confirmé !", size: 18, font: .bold)
|
||||
|
||||
@@ -8,13 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct BoosterKnowAboutScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct BoosterSubscriptionManagementScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@State var selectedValue = 0
|
||||
@State var presentControls = false
|
||||
@@ -43,9 +38,9 @@ struct BoosterSubscriptionManagementScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.bottomSheet(isShowing: $presentControls, content: {
|
||||
BoosterSubscriptionOptionsView()
|
||||
})
|
||||
// .bottomSheet(isShowing: $presentControls, content: {
|
||||
// BoosterSubscriptionOptionsView()
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,12 +40,6 @@ struct BoosterSubscriptionSelectionScreen: View {
|
||||
@State var mode: BoosterSubscriptionMode = .edit
|
||||
@State var isNotPremier = false
|
||||
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack(alignment: .top) {
|
||||
@@ -63,7 +57,7 @@ struct BoosterSubscriptionSelectionScreen: View {
|
||||
VStack(alignment: .trailing) {
|
||||
BoosterLockedToPremierView()
|
||||
VStack(spacing: 16) {
|
||||
Image("booster_logo", bundle: Bundle.module)
|
||||
Image("booster_logo")
|
||||
.resizable()
|
||||
.frame(width: 210, height: 180)
|
||||
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct OnlyForPremierModal: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
@@ -23,7 +18,7 @@ struct OnlyForPremierModal: View {
|
||||
.fill(Color.sqOrange(50))
|
||||
.frame(height: 200)
|
||||
VStack(alignment: .leading, spacing: 32) {
|
||||
Image("only_for_premier", bundle: .module)
|
||||
Image("only_for_premier")
|
||||
.resizable()
|
||||
.frame(width: 307, height: 108)
|
||||
}
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct RegulatedProfessionEditProfilModal: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct RegulatedProfessionModal: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack() {
|
||||
|
||||
@@ -8,86 +8,84 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AlloVoisinReputationScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 32) {
|
||||
VStack {
|
||||
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(spacing: 16) {
|
||||
VStack(spacing: 32) {
|
||||
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")
|
||||
SQText("Le saviez-vous ?", size: 20, font: .bold)
|
||||
SQText("AlloVoisins en France, c’est :", size: 20, font: .bold)
|
||||
}
|
||||
HStack(spacing: 16) {
|
||||
HStack(spacing: 2) {
|
||||
SQIcon(.apple_brand, size: .s)
|
||||
SQText("App Store", size: 10)
|
||||
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 {
|
||||
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) {
|
||||
SQIcon(.play_store_brand, size: .s)
|
||||
SQText("Google Play", size: 10)
|
||||
HStack(spacing: 16) {
|
||||
HStack(spacing: 2) {
|
||||
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)
|
||||
SQText("Trustpilot", size: 10)
|
||||
}
|
||||
.padding(16)
|
||||
.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)
|
||||
.background(Color.white)
|
||||
.background(Color.sqBlue(10))
|
||||
.cornerRadius(8)
|
||||
VStack {
|
||||
SQText("La presse en parle", size: 24, font: .bold)
|
||||
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) {
|
||||
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.sqBlue(10))
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||
|
||||
}
|
||||
.sqNavigationBar(title: "Ne partez pas !")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ struct ContinueAsParticularScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqNavigationBar(title: "Ne partez pas !")
|
||||
.padding(16)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct GetMoreRatingsScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
@@ -47,6 +40,7 @@ struct GetMoreRatingsScreen: View {
|
||||
|
||||
}
|
||||
}
|
||||
.sqNavigationBar(title: "Ne partez pas !")
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct MoreTimeScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
@@ -39,6 +32,7 @@ struct MoreTimeScreen: View {
|
||||
|
||||
}
|
||||
}
|
||||
.sqNavigationBar(title: "Ne partez pas !")
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,13 @@
|
||||
import SwiftUI
|
||||
|
||||
struct OnlyProRequestsScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
VStack(spacing: 32) {
|
||||
SQText("Demandes réservées aux pros", size: 20, font: .bold)
|
||||
.multilineTextAlignment(.center)
|
||||
Image("only_for_pro", bundle: Bundle.module)
|
||||
Image("only_for_pro")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(height: 60)
|
||||
@@ -47,6 +40,7 @@ struct OnlyProRequestsScreen: View {
|
||||
|
||||
}
|
||||
}
|
||||
.sqNavigationBar(title: "Ne partez pas !")
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,13 @@
|
||||
import SwiftUI
|
||||
|
||||
struct PersonalizedSupportScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
VStack(spacing: 32) {
|
||||
SQText("Bénéficiez d’un accompagnement personnalisé.", size: 20, font: .bold)
|
||||
.multilineTextAlignment(.center)
|
||||
Image("assistance", bundle: Bundle.module)
|
||||
Image("assistance")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(height: 100)
|
||||
@@ -42,6 +35,7 @@ struct PersonalizedSupportScreen: View {
|
||||
|
||||
}
|
||||
}
|
||||
.sqNavigationBar(title: "Ne partez pas !")
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ struct ProfileCompletionScreen: View {
|
||||
|
||||
}
|
||||
}
|
||||
.sqNavigationBar(title: "Ne partez pas !")
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// ResiliationCheckStepView.swift
|
||||
// ResiliationCheckStepsScreen.swift
|
||||
// Sequoia
|
||||
//
|
||||
// Created by Victor on 10/10/2024.
|
||||
@@ -7,58 +7,21 @@
|
||||
|
||||
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 {
|
||||
@State var nextStep: Bool = false
|
||||
@StateObject var viewModel: ResiliationViewModel
|
||||
var steps: [ResiliationCheckStep] {
|
||||
viewModel.resiliationCheckSteps
|
||||
}
|
||||
@State private var selectedSteps: Set<ResiliationCheckStep> = []
|
||||
|
||||
var allStepsSelected: Bool {
|
||||
selectedSteps.count == steps.count
|
||||
selectedSteps.count == viewModel.resiliationCheckSteps.count
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
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(
|
||||
checkStep: step,
|
||||
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()
|
||||
}
|
||||
.navigationDestination(isPresented: $nextStep) {
|
||||
ResiliationReasonScreen(viewModel: viewModel)
|
||||
}
|
||||
.sqNavigationBar(title: "Demander la résiliation")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// ResiliationProcess.swift
|
||||
// ResiliationNavigationView.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 14/10/2024.
|
||||
@@ -32,57 +32,16 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ResiliationNavigationView: View {
|
||||
@StateObject private var coordinator = ResiliationNavigationCoordinator()
|
||||
@StateObject private var viewModel: ResiliationViewModel
|
||||
|
||||
init(resiliationType: ResiliationType) {
|
||||
let coordinator = ResiliationNavigationCoordinator()
|
||||
self._coordinator = StateObject(wrappedValue: coordinator)
|
||||
self._viewModel = StateObject(wrappedValue: ResiliationViewModel(resiliationType: resiliationType))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $coordinator.path) {
|
||||
NavigationStack {
|
||||
ResiliationCheckStepsScreen(viewModel: viewModel)
|
||||
.navigationDestination(for: ResiliationNavigationCoordinator.Screen.self) { screen in
|
||||
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()
|
||||
.navigationBarTitle("Résiliation", displayMode: .inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,42 +8,42 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ResiliationReasonScreen: View {
|
||||
@State var selectedIndex: Int? = nil
|
||||
@State var resiliationOtherMotif: String = ""
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@ObservedObject var viewModel: ResiliationViewModel
|
||||
@State private var selectedIndex: Int? = nil
|
||||
@State private var resiliationOtherMotif: String = ""
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
SQRadio(title: "Aidez-nous à nous améliorer : précisez le motif de votre résiliation",
|
||||
options: [
|
||||
"Je ne suis pas un professionnel, c’est une erreur",
|
||||
"Je reçois des demandes mais je n’ai pas assez de réponses positives à mes propositions",
|
||||
"Je ne reçois pas assez de demandes",
|
||||
"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 {
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
SQRadio(title: "Aidez-nous à nous améliorer : précisez le motif de votre résiliation",
|
||||
options: viewModel.resiliationReasons.map { $0.text },
|
||||
selectedIndex: $selectedIndex)
|
||||
if selectedIndex == viewModel.resiliationReasons.count - 1 {
|
||||
SQTextField("", placeholder: "Précisez-nous le motif de votre résiliation", text: $resiliationOtherMotif)
|
||||
}
|
||||
|
||||
HStack {
|
||||
SQButton("Annuler", color: .sqNeutral(100), isFull: false) {
|
||||
|
||||
HStack {
|
||||
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) {
|
||||
|
||||
}
|
||||
.disabled(selectedIndex == nil)
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.sqNavigationBar(title: "Demander la résiliation")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ResiliationReasonScreen()
|
||||
}
|
||||
#Preview {
|
||||
ResiliationReasonScreen(viewModel: ResiliationViewModel(resiliationType: .apProWithTrial))
|
||||
}
|
||||
|
||||
@@ -9,29 +9,32 @@ import SwiftUI
|
||||
|
||||
struct ResizePerimeterScreen: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 32) {
|
||||
SQText("Augmentez votre périmètre afin de recevoir plus de demandes", size: 20, font: .bold)
|
||||
.multilineTextAlignment(.center)
|
||||
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.")
|
||||
VStack(spacing: 16) {
|
||||
VStack(spacing: 32) {
|
||||
SQText("Augmentez votre périmètre afin de recevoir plus de demandes", size: 20, font: .bold)
|
||||
.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)
|
||||
.background(Color.sqOrange(10))
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||
|
||||
}
|
||||
.sqNavigationBar(title: "Ne partez pas !")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ struct SoftwarePresentationScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqNavigationBar(title: "Ne partez pas !")
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct StatusChangeScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
@@ -30,6 +25,7 @@ struct StatusChangeScreen: View {
|
||||
.background(Color.sqOrange(10))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.sqNavigationBar(title: "Changer de statut")
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,13 @@
|
||||
import SwiftUI
|
||||
|
||||
struct WebinaireScreen: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
VStack(spacing: 32) {
|
||||
SQText("Webinaire spécial Pro", size: 20, font: .bold)
|
||||
.multilineTextAlignment(.center)
|
||||
Image("webinaire", bundle: Bundle.module)
|
||||
Image("webinaire")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.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.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
@@ -6,3 +6,150 @@
|
||||
//
|
||||
|
||||
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
|
||||
|
||||
struct BoosterActiveHeaderView: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image("booster_logo", bundle: Bundle.module)
|
||||
Image("booster_logo")
|
||||
.resizable()
|
||||
.frame(width: 93, height: 80)
|
||||
VStack {
|
||||
@@ -30,7 +24,7 @@ struct BoosterActiveHeaderView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
.overlay(
|
||||
ZStack(alignment: .topLeading) {
|
||||
Image("booster_corner", bundle: Bundle.module)
|
||||
Image("booster_corner")
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(8, corners: [.topLeft])
|
||||
|
||||
@@ -10,13 +10,6 @@ import SwiftUI
|
||||
struct BoosterHistoryCellView: View {
|
||||
var isEnded: Bool = false
|
||||
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
var body: some View {
|
||||
ZStack {
|
||||
HStack(alignment: .center) {
|
||||
@@ -28,7 +21,7 @@ struct BoosterHistoryCellView: View {
|
||||
}
|
||||
.overlay(
|
||||
ZStack(alignment: .topLeading) {
|
||||
Image("booster_corner", bundle: Bundle.module)
|
||||
Image("booster_corner")
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(8, corners: [.topLeft, .bottomLeft])
|
||||
|
||||
@@ -8,13 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct BoosterLockedToPremierView: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
var body: some View {
|
||||
HStack {
|
||||
SQIcon(.lock_keyhole, size: .xs, type: .solid, color: .white)
|
||||
|
||||
@@ -25,7 +25,7 @@ struct BoosterPromotionView: View {
|
||||
.foregroundColor(.sqRoyal())
|
||||
.overlay(
|
||||
ZStack(alignment: .topLeading) {
|
||||
Image("booster_corner", bundle: Bundle.module)
|
||||
Image("booster_corner")
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(8, corners: .topLeft)
|
||||
|
||||
@@ -8,13 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct BoosterStatsView: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
|
||||
@@ -18,10 +18,6 @@ struct BoosterSubscriptionCardView: View {
|
||||
}
|
||||
|
||||
init(id: Int, selectedId: Binding<Int>, currentOption: Bool = false, isFree: Bool = false) {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
|
||||
self.id = id
|
||||
self._selectedId = selectedId
|
||||
self.currentOption = currentOption
|
||||
@@ -61,7 +57,7 @@ struct BoosterSubscriptionCardView: View {
|
||||
.padding(.top, 30)
|
||||
.padding([.leading, .trailing])
|
||||
|
||||
Image("booster_corner_light", bundle: Bundle.module)
|
||||
Image("booster_corner_light")
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.position(x: 25, y: 25)
|
||||
|
||||
@@ -8,13 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct BoosterSubscriptionOptionsView: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
var body: some View {
|
||||
VStack {
|
||||
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
|
||||
|
||||
struct NeighborBanner: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 8) {
|
||||
|
||||
@@ -201,7 +201,7 @@ struct SQIcon: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Image(type == .solid ? "\(name.rawValue)_solid" : name.rawValue, bundle: .module)
|
||||
Image(type == .solid ? "\(name.rawValue)_solid" : name.rawValue)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.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) {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {}
|
||||
self.title = title
|
||||
self.content = content
|
||||
self.style = style
|
||||
|
||||
@@ -8,13 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct OnlyForPremierView: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
|
||||
@@ -8,13 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct RatingStarsView: View {
|
||||
init() {
|
||||
do {
|
||||
try FontRegistration().registerFonts()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .bottom) {
|
||||
|
||||
@@ -8,11 +8,46 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ResiliationCheckStepView: View {
|
||||
var checkStep: ResiliationCheckStep
|
||||
@Binding var isSelected: Bool
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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