💄 More UI

This commit is contained in:
Victor Bodinaud
2024-10-17 15:19:18 +02:00
parent c211091406
commit d962262874
25 changed files with 871 additions and 85 deletions

View File

@@ -12,7 +12,7 @@ struct AlloVoisinsSwiftUIApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ResiliationNavigationScreen(resiliationType: .apProWithTrial)
ResiliationNavigationScreen(viewModel: ResiliationViewModel(resiliationType: .apProWithTrial))
}
}
}

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "allomoji_sad.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,365 @@
%PDF-1.7
1 0 obj
<< /Length 2 0 R
/Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ]
/Domain [ 0.000000 1.000000 ]
/FunctionType 4
>>
stream
{ 0.436170 exch 0.867448 exch 0.925356 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub -0.217539 mul 0.436170 add exch dup 0.000000 sub -0.193018 mul 0.867448 add exch dup 0.000000 sub 0.074644 mul 0.925356 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.218632 exch 0.674430 exch 1.000000 exch } if pop }
endstream
endobj
2 0 obj
338
endobj
3 0 obj
<< /Pattern << /P1 << /Matrix [ -149.568039 -159.712708 159.712708 -149.568039 -64.525940 417.659363 ]
/Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ]
/ColorSpace /DeviceRGB
/Function 1 0 R
/Domain [ 0.000000 1.000000 ]
/ShadingType 2
/Extend [ true true ]
>>
/PatternType 2
/Type /Pattern
>> >>
/ExtGState << /E1 << /ca 0.097959 >> >>
>>
endobj
4 0 obj
<< /Length 5 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
1.000000 0.000000 -0.000000 1.000000 53.714600 13.998047 cm
0.000000 0.000000 0.000000 scn
90.547752 7.723469 m
90.547752 3.458379 70.278847 0.000010 45.275021 0.000010 c
20.271200 0.000010 0.000000 3.458379 0.000000 7.723469 c
0.000000 11.990851 20.271200 15.449219 45.275021 15.449219 c
70.278847 15.449219 90.547752 11.990851 90.547752 7.723469 c
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 20.402832 36.408203 cm
/Pattern cs
/P1 scn
159.382812 78.224625 m
159.217804 88.803696 154.723541 98.578331 147.476791 104.113091 c
90.969528 147.272766 l
82.748749 153.552368 72.391983 153.320892 64.427895 146.681473 c
10.877094 102.034409 l
3.937446 96.247543 -0.158046 86.436234 0.004673 75.974052 c
0.678470 32.376640 l
0.942030 15.355255 12.194895 1.725754 25.815214 1.938904 c
135.868683 3.641724 l
149.488998 3.852570 160.315598 17.821259 160.052032 34.842651 c
159.382812 78.224625 l
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 115.383301 100.441895 cm
1.000000 1.000000 1.000000 scn
10.930661 0.748501 m
8.290481 0.737041 5.648008 0.723290 3.005536 0.714124 c
1.428762 0.709539 0.074294 1.928792 0.005539 3.503277 c
-0.028838 4.339794 0.094920 5.224439 0.429526 6.143462 c
1.252291 8.403203 3.232426 10.158743 5.595296 10.610232 c
10.013932 11.449041 13.889405 8.100682 13.914616 3.826425 c
13.916907 3.737043 13.914616 3.647662 13.912324 3.558280 c
13.855028 1.981503 12.507436 0.755376 10.930661 0.748501 c
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 71.195068 100.230957 cm
1.000000 1.000000 1.000000 scn
0.000070 3.647342 m
0.004654 4.238633 0.087159 4.848259 0.261338 5.476219 c
0.944303 7.923890 2.979445 9.885694 5.468369 10.401355 c
9.937431 11.329544 13.888540 7.958268 13.913751 3.642758 c
13.913751 3.610673 l
13.918334 2.024729 12.522612 0.787143 10.936667 0.784850 c
8.287316 0.782558 5.640256 0.777975 2.990904 0.775683 c
1.384333 0.773392 -0.011389 2.040771 0.000070 3.647342 c
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 89.196533 74.000000 cm
1.000000 1.000000 1.000000 scn
3.004584 6.093983 m
5.156945 7.444341 8.546254 8.799805 12.205587 8.799805 c
15.864919 8.799805 19.254234 7.444343 21.406603 6.093986 c
22.428444 5.452901 23.424566 4.657184 23.954453 3.813828 c
24.146761 3.507753 24.766466 2.425399 24.132900 1.254358 c
23.488171 0.062685 22.213228 -0.000194 21.805601 -0.000194 c
20.861500 -0.000194 20.007589 0.226774 19.263977 0.487279 c
18.773603 0.659066 18.232281 0.884323 17.729542 1.093524 c
17.516951 1.181988 17.311260 1.267580 17.119238 1.345045 c
15.740918 1.901081 14.239561 2.399805 12.205587 2.399805 c
10.171617 2.399805 8.670266 1.901080 7.291955 1.345045 c
7.099933 1.267580 6.894242 1.181987 6.681652 1.093523 c
6.178916 0.884323 5.637596 0.659066 5.147226 0.487279 c
4.403615 0.226773 3.549706 -0.000196 2.605604 -0.000196 c
2.197973 -0.000196 0.923040 0.062685 0.278309 1.254348 c
-0.355261 2.425384 0.264434 3.507739 0.456743 3.813818 c
0.986627 4.657178 1.982746 5.452897 3.004584 6.093983 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 112.800293 90.399902 cm
0.168362 0.160503 0.179167 scn
31.999998 16.000000 m
31.999998 7.163446 24.836554 0.000002 15.999999 0.000002 c
7.163444 0.000002 0.000000 7.163446 0.000000 16.000000 c
0.000000 24.836555 7.163444 32.000000 15.999999 32.000000 c
24.836554 32.000000 31.999998 24.836555 31.999998 16.000000 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 119.200439 110.399902 cm
1.000000 1.000000 1.000000 scn
8.000000 4.000000 m
8.000000 1.790862 6.209138 0.000000 4.000000 0.000000 c
1.790861 0.000000 0.000000 1.790862 0.000000 4.000000 c
0.000000 6.209139 1.790861 8.000000 4.000000 8.000000 c
6.209138 8.000000 8.000000 6.209139 8.000000 4.000000 c
h
f
n
Q
q
-1.000000 -0.000000 -0.000000 1.000000 130.400391 116.799805 cm
1.000000 1.000000 1.000000 scn
3.200000 1.600195 m
3.200000 0.716540 2.483656 0.000195 1.600000 0.000195 c
0.716344 0.000195 0.000000 0.716540 0.000000 1.600195 c
0.000000 2.483851 0.716344 3.200195 1.600000 3.200195 c
2.483656 3.200195 3.200000 2.483851 3.200000 1.600195 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 116.197754 92.000000 cm
0.298039 0.266667 0.341176 scn
0.210692 9.427586 m
0.819494 10.927945 2.263791 11.200195 3.002827 11.200195 c
4.263373 11.200195 5.321895 10.698198 6.093330 10.247780 c
6.611842 9.945037 7.198322 9.538357 7.710639 9.183104 c
7.915307 9.041182 8.108138 8.907468 8.280070 8.791867 c
9.648875 7.871538 10.930213 7.200195 12.602841 7.200195 c
14.275468 7.200195 15.556796 7.871536 16.925592 8.791864 c
17.097525 8.907465 17.290358 9.041180 17.495024 9.183102 c
18.007338 9.538354 18.593817 9.945034 19.112326 10.247778 c
19.883757 10.698196 20.942278 11.200195 22.202826 11.200195 c
22.941864 11.200195 24.386152 10.927944 24.994959 9.427594 c
25.463491 8.272933 25.033445 7.210503 24.829237 6.777150 c
24.329792 5.717262 23.346052 4.650410 22.280838 3.759346 c
20.099072 1.934270 16.546749 0.000196 12.602841 0.000196 c
8.658937 0.000196 5.106606 1.934269 2.924833 3.759343 c
1.859617 4.650406 0.875873 5.717257 0.376423 6.777142 c
0.172215 7.210493 -0.257837 8.272923 0.210692 9.427586 c
h
f*
n
Q
q
-1.000000 -0.000000 -0.000000 1.000000 140.000488 97.600098 cm
1.000000 1.000000 1.000000 scn
4.000000 2.000000 m
4.000000 0.895431 3.104569 0.000000 2.000000 0.000000 c
0.895430 0.000000 0.000000 0.895431 0.000000 2.000000 c
0.000000 3.104569 0.895430 4.000000 2.000000 4.000000 c
3.104569 4.000000 4.000000 3.104569 4.000000 2.000000 c
h
f
n
Q
q
1.000000 -0.000000 0.000000 1.000000 116.197998 88.799805 cm
0.839216 0.941176 1.000000 scn
2.186750 7.132430 m
2.481733 7.187536 2.767972 7.200195 3.002226 7.200195 c
22.202225 7.200195 l
22.436480 7.200195 22.722717 7.187535 23.017700 7.132430 c
23.223743 7.093940 23.849474 6.967839 24.399368 6.459650 c
24.724783 6.158916 25.043146 5.702694 25.159731 5.094691 c
25.273575 4.500982 25.151371 3.990361 25.002514 3.640070 c
24.743748 3.031134 24.310673 2.673494 24.152058 2.547267 c
23.757078 2.232945 23.281164 2.001373 22.906059 1.838783 c
22.485668 1.656564 21.994732 1.478374 21.458691 1.310222 c
19.338472 0.645127 16.070730 0.000196 12.602242 0.000196 c
9.133754 0.000196 5.866003 0.645127 3.745779 1.310222 c
3.209736 1.478374 2.718797 1.656562 2.298405 1.838781 c
1.923299 2.001371 1.447385 2.232941 1.052409 2.547260 c
0.893792 2.673485 0.460720 3.031119 0.201949 3.640047 c
0.053092 3.990332 -0.069120 4.500952 0.044717 5.094664 c
0.161296 5.702672 0.479656 6.158901 0.805074 6.459641 c
1.354972 6.967839 1.980710 7.093940 2.186750 7.132430 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 56.800049 90.399902 cm
0.168362 0.160503 0.179167 scn
31.999998 16.000000 m
31.999998 7.163446 24.836554 0.000002 15.999999 0.000002 c
7.163444 0.000002 0.000000 7.163446 0.000000 16.000000 c
0.000000 24.836555 7.163444 32.000000 15.999999 32.000000 c
24.836554 32.000000 31.999998 24.836555 31.999998 16.000000 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 63.200439 110.399902 cm
1.000000 1.000000 1.000000 scn
8.000000 4.000000 m
8.000000 1.790862 6.209138 0.000000 4.000000 0.000000 c
1.790861 0.000000 0.000000 1.790862 0.000000 4.000000 c
0.000000 6.209139 1.790861 8.000000 4.000000 8.000000 c
6.209138 8.000000 8.000000 6.209139 8.000000 4.000000 c
h
f
n
Q
q
-1.000000 -0.000000 -0.000000 1.000000 74.400391 116.799805 cm
1.000000 1.000000 1.000000 scn
3.200000 1.600195 m
3.200000 0.716540 2.483656 0.000195 1.600000 0.000195 c
0.716344 0.000195 0.000000 0.716540 0.000000 1.600195 c
0.000000 2.483851 0.716344 3.200195 1.600000 3.200195 c
2.483656 3.200195 3.200000 2.483851 3.200000 1.600195 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 60.197754 92.000000 cm
0.298039 0.266667 0.341176 scn
0.210692 9.427586 m
0.819494 10.927945 2.263791 11.200195 3.002826 11.200195 c
4.263373 11.200195 5.321895 10.698198 6.093330 10.247780 c
6.611842 9.945036 7.198323 9.538356 7.710640 9.183103 c
7.915307 9.041182 8.108138 8.907468 8.280070 8.791867 c
9.648875 7.871538 10.930213 7.200195 12.602841 7.200195 c
14.275467 7.200195 15.556796 7.871536 16.925592 8.791864 c
17.097525 8.907465 17.290358 9.041180 17.495024 9.183101 c
18.007338 9.538354 18.593815 9.945034 19.112326 10.247778 c
19.883757 10.698197 20.942278 11.200195 22.202826 11.200195 c
22.941864 11.200195 24.386152 10.927944 24.994959 9.427594 c
25.463491 8.272934 25.033445 7.210504 24.829237 6.777150 c
24.329792 5.717262 23.346052 4.650410 22.280838 3.759346 c
20.099072 1.934270 16.546749 0.000196 12.602841 0.000196 c
8.658937 0.000196 5.106606 1.934269 2.924833 3.759343 c
1.859617 4.650406 0.875873 5.717257 0.376423 6.777142 c
0.172215 7.210493 -0.257837 8.272923 0.210692 9.427586 c
h
f*
n
Q
q
-1.000000 -0.000000 -0.000000 1.000000 84.000244 97.600098 cm
1.000000 1.000000 1.000000 scn
4.000000 2.000000 m
4.000000 0.895431 3.104569 0.000000 2.000000 0.000000 c
0.895430 0.000000 0.000000 0.895431 0.000000 2.000000 c
0.000000 3.104569 0.895430 4.000000 2.000000 4.000000 c
3.104569 4.000000 4.000000 3.104569 4.000000 2.000000 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 60.800049 88.799805 cm
0.839216 0.941176 1.000000 scn
2.186750 7.132430 m
2.481733 7.187536 2.767971 7.200195 3.002226 7.200195 c
22.202225 7.200195 l
22.436480 7.200195 22.722717 7.187535 23.017700 7.132430 c
23.223743 7.093940 23.849474 6.967839 24.399368 6.459650 c
24.724783 6.158916 25.043146 5.702694 25.159731 5.094691 c
25.273575 4.500982 25.151371 3.990361 25.002514 3.640070 c
24.743748 3.031134 24.310673 2.673494 24.152058 2.547267 c
23.757078 2.232945 23.281164 2.001373 22.906059 1.838783 c
22.485668 1.656564 21.994732 1.478374 21.458691 1.310222 c
19.338472 0.645127 16.070730 0.000196 12.602242 0.000196 c
9.133754 0.000196 5.866003 0.645127 3.745779 1.310222 c
3.209736 1.478374 2.718797 1.656562 2.298405 1.838781 c
1.923299 2.001371 1.447385 2.232941 1.052409 2.547260 c
0.893792 2.673485 0.460720 3.031119 0.201949 3.640047 c
0.053092 3.990332 -0.069120 4.500952 0.044717 5.094664 c
0.161295 5.702673 0.479656 6.158901 0.805074 6.459641 c
1.354972 6.967839 1.980710 7.093940 2.186750 7.132430 c
h
f*
n
Q
endstream
endobj
5 0 obj
10051
endobj
6 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 200.000000 200.000000 ]
/Resources 3 0 R
/Contents 4 0 R
/Parent 7 0 R
>>
endobj
7 0 obj
<< /Kids [ 6 0 R ]
/Count 1
/Type /Pages
>>
endobj
8 0 obj
<< /Pages 7 0 R
/Type /Catalog
>>
endobj
xref
0 9
0000000000 65535 f
0000000010 00000 n
0000000532 00000 n
0000000554 00000 n
0000001236 00000 n
0000011343 00000 n
0000011367 00000 n
0000011542 00000 n
0000011616 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 8 0 R
/Size 9
>>
startxref
11675
%%EOF

View File

@@ -8,6 +8,8 @@
import SwiftUI
struct AlloVoisinReputationScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 16) {
@@ -74,7 +76,7 @@ struct AlloVoisinReputationScreen: View {
.background(Color.white)
.cornerRadius(8)
SQButton("Conserver mon abonnement", color: .sqNeutral(100), textColor: .white) {
navigateToNext = true
}
}
.padding(16)
@@ -82,7 +84,14 @@ struct AlloVoisinReputationScreen: View {
.cornerRadius(8)
SQButton("Jai compris, mais je souhaite résilier", color: .white, textSize: 13) {
navigateToNext = true
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
@@ -90,5 +99,5 @@ struct AlloVoisinReputationScreen: View {
}
#Preview {
AlloVoisinReputationScreen()
AlloVoisinReputationScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -8,7 +8,10 @@
import SwiftUI
struct AskIfWillComeBackScreen: View {
@ObservedObject var viewModel: ResiliationViewModel
@State private var navigateToNext = false
@State var selectedIndex: Int? = nil
var body: some View {
VStack {
SQRadio(title: "Pensez-vous redevenir abonné Premier ?", orientation: .horizontal, options: ["1\nNon,Jamais", "2", "3", "4", "5\nOui, sûrement"], selectedIndex: $selectedIndex)
@@ -17,5 +20,5 @@ struct AskIfWillComeBackScreen: View {
}
#Preview {
AskIfWillComeBackScreen()
AskIfWillComeBackScreen(viewModel: ResiliationViewModel(resiliationType: .apProWithTrial))
}

View File

@@ -8,6 +8,9 @@
import SwiftUI
struct ContinueAsParticularScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 32) {
SQText("Souhaitez-vous poursuivre en tant que particulier ?", size: 18, font: .bold)
@@ -16,18 +19,25 @@ struct ContinueAsParticularScreen: View {
.multilineTextAlignment(.center)
VStack {
SQButton("Changer de statut", color: .sqNeutral(100), textColor: .white) {
navigateToNext = true
}
SQButton("Jai compris, mais je souhaite résilier", color: .white) {
navigateToNext = true
}
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
.padding(16)
}
}
#Preview {
ContinueAsParticularScreen()
ContinueAsParticularScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -0,0 +1,163 @@
//
// GenericResiliationScreen.swift
// AlloVoisinsSwiftUI
//
// Created by Victor on 14/10/2024.
//
import SwiftUI
protocol ResiliationContentView: View {
var screenType: ResiliationScreenType { get }
}
enum ResiliationScreenType {
case alloVoisinReputation
case askIfWillComeBack
case continueAsParticular
case getMoreRatings
case moreTime
case onlyProRequests
case personalizedSupport
case profileCompletion
case resizePerimeter
case softwarePresentation
case statusChange
case webinaire
var title: String {
switch self {
case .statusChange: return "Changer de statut"
default: return "Ne partez pas !"
}
}
var mainColor: Color {
switch self {
case .alloVoisinReputation, .moreTime: return Color.sqBlue(10)
case .continueAsParticular, .resizePerimeter, .statusChange: return Color.sqOrange(10)
case .getMoreRatings: return Color.sqGold(10)
case .onlyProRequests, .personalizedSupport, .webinaire: return Color.sqGrape(10)
case .profileCompletion: return Color.sqNeutral(10)
case .softwarePresentation: return Color.white
default: return Color.white
}
}
var buttonColor: Color {
switch self {
case .alloVoisinReputation, .continueAsParticular, .profileCompletion, .softwarePresentation:
return Color.sqNeutral(100)
case .getMoreRatings: return Color.sqGold(50)
case .moreTime: return Color.sqBlue(50)
case .onlyProRequests, .personalizedSupport, .webinaire: return Color.sqGrape(80)
case .resizePerimeter, .statusChange: return Color.sqOrange(50)
default: return Color.sqNeutral(100)
}
}
var buttonTitle: String {
switch self {
case .alloVoisinReputation: return "Conserver mon abonnement"
case .continueAsParticular: return "Changer de statut"
case .getMoreRatings: return "Recueillir des avis"
case .moreTime: return "Je prolonge ma période d'essai"
case .onlyProRequests: return "Voir les demandes"
case .personalizedSupport: return "Je souhaite être appelé"
case .profileCompletion: return "Je complète mon profil"
case .resizePerimeter: return "Modifier mon périmètre"
case .softwarePresentation: return "Découvrir le logiciel"
case .statusChange: return "Résilier et changer de statut"
case .webinaire: return "Je m'inscris"
default: return "Continuer"
}
}
var cancelButtonTitle: String {
switch self {
case .moreTime, .personalizedSupport, .webinaire:
return "Non merci, je souhaite résilier"
default:
return "J'ai compris, mais je souhaite résilier"
}
}
}
struct GenericResiliationScreen<Content: ResiliationContentView>: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
let content: Content
let buttonAction: () -> Void
var body: some View {
VStack(spacing: 16) {
VStack(spacing: 32) {
content
.padding(16)
.background(content.screenType.mainColor)
.cornerRadius(8)
SQButton(content.screenType.buttonTitle, color: content.screenType.buttonColor, textColor: .white, action: buttonAction)
}
if content.screenType != .statusChange {
SQButton(content.screenType.cancelButtonTitle, color: .white, textSize: 13) {
navigateToNext = true
}
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: content.screenType.title)
.padding()
}
}
//#Preview {
// GenericResiliationScreen(viewModel: ResiliationViewModel(resiliationType: .apProWithTrial), content: ResiliationContentView) {
//
// }
//}
struct AlloVoisinReputationScreens: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
var body: some View {
GenericResiliationScreen(
viewModel: viewModel,
content: AlloVoisinReputationContent(),
buttonAction: {
// Handle button action
}
)
}
}
#Preview {
AlloVoisinReputationScreens(viewModel: PromotionalScreenViewModel())
}
struct AlloVoisinReputationContent: ResiliationContentView {
let screenType: ResiliationScreenType = .alloVoisinReputation
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)
// ... Rest of the content
}
}
}

View File

@@ -8,6 +8,8 @@
import SwiftUI
struct GetMoreRatingsScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 16) {
@@ -29,7 +31,7 @@ struct GetMoreRatingsScreen: View {
.multilineTextAlignment(.center)
}
SQButton("Recueillir des avis", color: .sqGold(50), textColor: .white) {
navigateToNext = true
}
}
.padding(16)
@@ -37,7 +39,14 @@ struct GetMoreRatingsScreen: View {
.cornerRadius(8)
SQButton("Jai compris, mais je souhaite résilier", color: .white, textSize: 13) {
navigateToNext = true
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
@@ -46,5 +55,5 @@ struct GetMoreRatingsScreen: View {
}
#Preview {
GetMoreRatingsScreen()
GetMoreRatingsScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -8,7 +8,9 @@
import SwiftUI
struct MoreTimeScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 16) {
VStack(spacing: 32) {
@@ -21,7 +23,7 @@ struct MoreTimeScreen: View {
.multilineTextAlignment(.center)
}
SQButton("Je prolonge ma période dessai", color: .sqBlue(50), textColor: .white) {
navigateToNext = true
}
}
.padding(16)
@@ -29,7 +31,14 @@ struct MoreTimeScreen: View {
.cornerRadius(8)
SQButton("Non merci, je souhaite résilier", color: .white, textSize: 13) {
navigateToNext = true
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
@@ -38,5 +47,5 @@ struct MoreTimeScreen: View {
}
#Preview {
MoreTimeScreen()
MoreTimeScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -8,7 +8,9 @@
import SwiftUI
struct OnlyProRequestsScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 16) {
VStack(spacing: 32) {
@@ -29,7 +31,7 @@ struct OnlyProRequestsScreen: View {
.multilineTextAlignment(.center)
}
SQButton("Voir les demandes", color: .sqGrape(80), textColor: .white) {
navigateToNext = true
}
}
.padding(16)
@@ -37,7 +39,14 @@ struct OnlyProRequestsScreen: View {
.cornerRadius(8)
SQButton("Jai compris, mais je souhaite résilier", color: .white, textSize: 13) {
navigateToNext = true
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
@@ -46,5 +55,5 @@ struct OnlyProRequestsScreen: View {
}
#Preview {
OnlyProRequestsScreen()
OnlyProRequestsScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -8,6 +8,8 @@
import SwiftUI
struct PersonalizedSupportScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 16) {
@@ -24,7 +26,7 @@ struct PersonalizedSupportScreen: View {
.multilineTextAlignment(.center)
}
SQButton("Je souhaite être appelé", color: .sqGrape(80), textColor: .white) {
navigateToNext = true
}
}
.padding(16)
@@ -32,7 +34,14 @@ struct PersonalizedSupportScreen: View {
.cornerRadius(8)
SQButton("Non merci, je souhaite résilier", color: .white, textSize: 13) {
navigateToNext = true
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
@@ -41,5 +50,5 @@ struct PersonalizedSupportScreen: View {
}
#Preview {
PersonalizedSupportScreen()
PersonalizedSupportScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -8,6 +8,9 @@
import SwiftUI
struct ProfileCompletionScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 16) {
VStack(spacing: 32) {
@@ -52,7 +55,7 @@ struct ProfileCompletionScreen: View {
SQText("des demandeurs comparent systématiquement les profils des offreurs pour faire leur choix.")
.multilineTextAlignment(.center)
SQButton("Je complète mon profil", color: .sqNeutral(100), textColor: .white) {
navigateToNext = true
}
}
.padding(16)
@@ -60,7 +63,14 @@ struct ProfileCompletionScreen: View {
.cornerRadius(8)
SQButton("Jai compris, mais je souhaite résilier", color: .white, textSize: 13) {
navigateToNext = true
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
@@ -69,5 +79,5 @@ struct ProfileCompletionScreen: View {
}
#Preview {
ProfileCompletionScreen()
ProfileCompletionScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -8,9 +8,9 @@
import SwiftUI
struct ResiliationCheckStepsScreen: View {
@State var nextStep: Bool = false
@StateObject var viewModel: ResiliationViewModel
@State private var selectedSteps: Set<ResiliationCheckStep> = []
@State private var navigateToReasonScreen = false
var allStepsSelected: Bool {
selectedSteps.count == viewModel.resiliationCheckSteps.count
@@ -46,7 +46,7 @@ struct ResiliationCheckStepsScreen: View {
}
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
nextStep.toggle()
navigateToReasonScreen = true
}
.disabled(!allStepsSelected)
}
@@ -54,7 +54,7 @@ struct ResiliationCheckStepsScreen: View {
}
.padding()
}
.navigationDestination(isPresented: $nextStep) {
.navigationDestination(isPresented: $navigateToReasonScreen) {
ResiliationReasonScreen(viewModel: viewModel)
}
.sqNavigationBar(title: "Demander la résiliation")

View File

@@ -0,0 +1,33 @@
//
// ResiliationConfirmationScreen.swift
// AlloVoisinsSwiftUI
//
// Created by Victor on 15/10/2024.
//
import SwiftUI
struct ResiliationConfirmationScreen: View {
@State var navigateToNext: Bool = false
var body: some View {
VStack(spacing: 8) {
Image("allomoji_sad")
.resizable()
.scaledToFit()
.frame(height: 200)
SQText("Votre résiliation a été prise en compte. Elle sera effective le JJ/MM/AAAA.", size: 18, font: .bold)
SQButton("Terminer", color: .sqNeutral(100), textColor: .white) {
}
.padding()
Spacer()
}
.padding()
.sqNavigationBar(title: "Demander la résiliation")
}
}
#Preview {
ResiliationConfirmationScreen()
}

View File

@@ -8,16 +8,23 @@
import SwiftUI
struct ResiliationNavigationScreen: View {
@StateObject private var viewModel: ResiliationViewModel
init(resiliationType: ResiliationType) {
self._viewModel = StateObject(wrappedValue: ResiliationViewModel(resiliationType: resiliationType))
}
@StateObject var viewModel: ResiliationViewModel
var body: some View {
NavigationStack {
ResiliationCheckStepsScreen(viewModel: viewModel)
.navigationBarTitle("Résiliation", displayMode: .inline)
}
}
}
struct PromotionalScreenWrapper: View {
@ObservedObject var viewModel: ResiliationViewModel
var body: some View {
if let screen = viewModel.currentPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
}

View File

@@ -12,6 +12,7 @@ struct ResiliationReasonScreen: View {
@ObservedObject var viewModel: ResiliationViewModel
@State private var selectedIndex: Int? = nil
@State private var resiliationOtherMotif: String = ""
@State private var navigateToPromotionalScreen = false
var body: some View {
ScrollView {
@@ -28,10 +29,10 @@ struct ResiliationReasonScreen: View {
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)
navigateToPromotionalScreen = true
}
}
.disabled(selectedIndex == nil)
@@ -41,9 +42,16 @@ struct ResiliationReasonScreen: View {
}
.padding()
.sqNavigationBar(title: "Demander la résiliation")
.navigationDestination(isPresented: $navigateToPromotionalScreen) {
if viewModel.hasNextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: viewModel.currentPromotionalScreen!)
} else {
ResiliationConfirmationScreen()
}
}
}
}
#Preview {
ResiliationReasonScreen(viewModel: ResiliationViewModel(resiliationType: .apProWithTrial))
}
#Preview {
ResiliationReasonScreen(viewModel: ResiliationViewModel(resiliationType: .apProWithTrial))
}

View File

@@ -8,6 +8,9 @@
import SwiftUI
struct ResizePerimeterScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 16) {
VStack(spacing: 32) {
@@ -23,7 +26,7 @@ struct ResizePerimeterScreen: View {
.multilineTextAlignment(.center)
}
SQButton("Modifier mon périmètre", color: .sqOrange(50), textColor: .white) {
navigateToNext = true
}
}
.padding(16)
@@ -31,7 +34,14 @@ struct ResizePerimeterScreen: View {
.cornerRadius(8)
SQButton("Jai compris, mais je souhaite résilier", color: .white, textSize: 13) {
navigateToNext = true
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
@@ -39,5 +49,5 @@ struct ResizePerimeterScreen: View {
}
#Preview {
ResizePerimeterScreen()
ResizePerimeterScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -8,6 +8,9 @@
import SwiftUI
struct SoftwarePresentationScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 32) {
SQText("Saviez-vous que votre abonnement peut vous offrir bien plus que de nouveaux clients ?", size: 18, font: .bold)
@@ -37,18 +40,25 @@ struct SoftwarePresentationScreen: View {
}
VStack {
SQButton("Découvrir le logiciel", color: .sqNeutral(100), textColor: .white) {
navigateToNext = true
}
SQButton("Jai compris, mais je souhaite résilier", color: .white, textSize: 13) {
navigateToNext = true
}
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
.padding()
}
}
#Preview {
SoftwarePresentationScreen()
SoftwarePresentationScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -8,6 +8,8 @@
import SwiftUI
struct StatusChangeScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack {
@@ -19,17 +21,26 @@ struct StatusChangeScreen: View {
SQText("Si vous le souhaitez, vous pourrez vous abonner à labonnement Premier en tant que particulier.")
.multilineTextAlignment(.center)
}
SQButton("Résilier et changer de statut", color: .sqOrange(50), textColor: .white) {}
SQButton("Résilier et changer de statut", color: .sqOrange(50), textColor: .white) {
navigateToNext = true
}
}
.padding(16)
.background(Color.sqOrange(10))
.cornerRadius(8)
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Changer de statut")
.padding()
}
}
#Preview {
StatusChangeScreen()
StatusChangeScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -8,6 +8,8 @@
import SwiftUI
struct WebinaireScreen: View {
@ObservedObject var viewModel: PromotionalScreenViewModel
@State private var navigateToNext = false
var body: some View {
VStack(spacing: 16) {
@@ -24,16 +26,37 @@ struct WebinaireScreen: View {
.multilineTextAlignment(.center)
SQText("“Augmenter mon chiffre d'affaires.“", size: 18, font: .bold)
}
SQButton("Je minscris", color: .sqGrape(80), textColor: .white) {
NavigationLink {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
} label: {
SQButton("Je minscris", color: .sqGrape(80), textColor: .white) {
}
}
}
.padding(16)
.background(Color.sqGrape(10))
.cornerRadius(8)
SQButton("Non merci, je souhaite résilier", color: .white, textSize: 13) {
NavigationLink {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
} label: {
SQButton("Non merci, je souhaite résilier", color: .white, textSize: 13) {
}
}
}
.navigationDestination(isPresented: $navigateToNext) {
if let screen = viewModel.nextPromotionalScreen {
viewModel.getPromotionalScreenNew(for: screen)
} else {
ResiliationConfirmationScreen()
}
}
.sqNavigationBar(title: "Ne partez pas !")
@@ -41,5 +64,5 @@ struct WebinaireScreen: View {
}
#Preview {
WebinaireScreen()
WebinaireScreen(viewModel: PromotionalScreenViewModel())
}

View File

@@ -0,0 +1,43 @@
//
// PromotionalScreenViewModel.swift
// AlloVoisinsSwiftUI
//
// Created by Victor on 15/10/2024.
//
import Foundation
import SwiftUICore
class PromotionalScreenViewModel: ObservableObject {
@Published var nextPromotionalScreen: ResiliationPromotionalScreen?
init(nextPromotionalScreen: ResiliationPromotionalScreen? = nil) {
self.nextPromotionalScreen = nextPromotionalScreen
}
@ViewBuilder
func getPromotionalScreenNew(for screen: ResiliationPromotionalScreen) -> some View {
switch screen {
case .webinarPresentation:
WebinaireScreen(viewModel: PromotionalScreenViewModel())
case .profileCompletion:
ProfileCompletionScreen(viewModel: PromotionalScreenViewModel())
case .editPerimeter:
ResizePerimeterScreen(viewModel: PromotionalScreenViewModel())
case .onlyProSearches:
OnlyProRequestsScreen(viewModel: PromotionalScreenViewModel())
case .oneMonthBoosterOffered:
BoosterSubscriptionSelectionScreen()
case .allovoisinsPromotion:
AlloVoisinReputationScreen(viewModel: PromotionalScreenViewModel())
case .extendMyTrialPeriod:
MoreTimeScreen(viewModel: PromotionalScreenViewModel())
case .askPartProStatusChangeWithCancellation, .askPartProStatusChange:
StatusChangeScreen(viewModel: PromotionalScreenViewModel())
case .customerSupportForPro, .customerSupportForPart:
PersonalizedSupportScreen(viewModel: PromotionalScreenViewModel())
default:
EmptyView()
}
}
}

View File

@@ -14,13 +14,22 @@ class ResiliationViewModel: ObservableObject {
@Published var resiliationReasons: [ResiliationReason]
@Published var selectedReason: ResiliationReason?
@Published var promotionalScreens: [ResiliationPromotionalScreen] = []
@Published var currentScreen: Screen = .checkSteps
@Published var currentPromotionalScreenIndex: Int = 0
@Published var shouldShowConfirmationScreen: Bool = false
enum Screen: Equatable {
case checkSteps
case reason
case promotional(ResiliationPromotionalScreen)
case final
var currentPromotionalScreen: ResiliationPromotionalScreen? {
guard currentPromotionalScreenIndex < promotionalScreens.count else {
return nil
}
return promotionalScreens[currentPromotionalScreenIndex]
}
var hasNextPromotionalScreen: Bool {
currentPromotionalScreenIndex < promotionalScreens.count
}
var nextPromotionalScreen: ResiliationPromotionalScreen? {
hasNextPromotionalScreen ? promotionalScreens[currentPromotionalScreenIndex + 1] : nil
}
init(resiliationType: ResiliationType) {
@@ -35,32 +44,19 @@ class ResiliationViewModel: ObservableObject {
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
}
currentPromotionalScreenIndex = 0
shouldShowConfirmationScreen = false
}
// OK
private func selectResiliationPromotions(availablePromotions: [ResiliationPromotionalScreen], eligibleScreens: [ResiliationPromotionalScreen]) -> [ResiliationPromotionalScreen] {
let shuffledArray1 = availablePromotions.shuffled()
let promotionsShuffuled = availablePromotions.shuffled()
let commonPromotions = shuffledArray1.filter { promotion in
let commonPromotions = promotionsShuffuled.filter { promotion in
eligibleScreens.contains(promotion)
}
let result = commonPromotions.map { $0 }
return Array(result.prefix(2))
return Array(commonPromotions.prefix(2))
}
private func getAvailablePromotionalScreens(for reason: ResiliationReason) -> [ResiliationPromotionalScreen] {
@@ -87,30 +83,30 @@ class ResiliationViewModel: ObservableObject {
}
private func getEligiblePromotionalScreens() -> [ResiliationPromotionalScreen] {
return [.extendMyTrialPeriod, .profileCompletion, .allovoisinsPromotion]
return [.extendMyTrialPeriod, .profileCompletion, .allovoisinsPromotion, .externalReview, .webinarPresentation, .editPerimeter, .onlyProSearches, .askPartProStatusChange, .customerSupportForPro]
}
@ViewBuilder
func getPromotionalScreenNew(for screen: ResiliationPromotionalScreen) -> some View {
switch screen {
case .webinarPresentation:
WebinaireScreen()
WebinaireScreen(viewModel: PromotionalScreenViewModel(nextPromotionalScreen: nextPromotionalScreen))
case .profileCompletion:
ProfileCompletionScreen()
ProfileCompletionScreen(viewModel: PromotionalScreenViewModel(nextPromotionalScreen: nextPromotionalScreen))
case .editPerimeter:
ResizePerimeterScreen()
ResizePerimeterScreen(viewModel: PromotionalScreenViewModel(nextPromotionalScreen: nextPromotionalScreen))
case .onlyProSearches:
OnlyProRequestsScreen()
OnlyProRequestsScreen(viewModel: PromotionalScreenViewModel(nextPromotionalScreen: nextPromotionalScreen))
case .oneMonthBoosterOffered:
BoosterSubscriptionSelectionScreen()
case .allovoisinsPromotion:
AlloVoisinReputationScreen()
AlloVoisinReputationScreen(viewModel: PromotionalScreenViewModel(nextPromotionalScreen: nextPromotionalScreen))
case .extendMyTrialPeriod:
MoreTimeScreen()
MoreTimeScreen(viewModel: PromotionalScreenViewModel(nextPromotionalScreen: nextPromotionalScreen))
case .askPartProStatusChangeWithCancellation, .askPartProStatusChange:
StatusChangeScreen()
StatusChangeScreen(viewModel: PromotionalScreenViewModel(nextPromotionalScreen: nextPromotionalScreen))
case .customerSupportForPro, .customerSupportForPart:
PersonalizedSupportScreen()
PersonalizedSupportScreen(viewModel: PromotionalScreenViewModel(nextPromotionalScreen: nextPromotionalScreen))
default:
EmptyView()
}

View File

@@ -0,0 +1,37 @@
//
// SQProgressBar.swift
// AlloVoisinsSwiftUI
//
// Created by Victor on 17/10/2024.
//
import SwiftUI
struct SQProgressBar: View {
var progress: Double // Valeur entre 0 et 1
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.foregroundColor(Color.sqNeutral(20))
.frame(width: geometry.size.width, height: 12)
.cornerRadius(50)
Rectangle()
.foregroundColor(Color.sqNeutral(60)) // Vous pouvez changer la couleur ici
.frame(width: min(CGFloat(self.progress) * geometry.size.width, geometry.size.width), height: 12)
.cornerRadius(50)
}
}
.frame(height: 12)
.frame(maxWidth: .infinity, alignment: .topLeading)
.background(Color.sqNeutral(20))
.cornerRadius(12)
}
}
#Preview {
SQProgressBar(progress: 0.2)
.padding()
}