@ -43,12 +43,7 @@ class SubscriptionModel: ObservableObject, StoreDelegate {
self . _computePrice ( )
self . _computePrice ( )
}
}
}
}
@ Published var quantity : Int = 1 {
@ Published var quantity : Int = 1
didSet {
self . _computePrice ( )
self . selectedProduct = self . products . first ( where : { $0 . id = = StoreItem . unit . rawValue } )
}
}
@ Published var products : [ Product ] = [ ]
@ Published var products : [ Product ] = [ ]
@ Published var totalPrice : String = " "
@ Published var totalPrice : String = " "
@ -63,6 +58,10 @@ class SubscriptionModel: ObservableObject, StoreDelegate {
}
}
}
}
func isSelected ( product : Product ) -> Bool {
return self . selectedProduct = = product
}
func productsReceived ( products : [ Product ] ) {
func productsReceived ( products : [ Product ] ) {
self . isLoading = false
self . isLoading = false
self . products = products
self . products = products
@ -137,60 +136,22 @@ struct SubscriptionView: View {
if self . showLackOfPlanMessage {
if self . showLackOfPlanMessage {
SubscriptionDetailView ( )
SubscriptionDetailView ( )
. clipShape ( . rect ( cornerRadius : 16.0 ) )
. padding ( )
}
}
List {
List {
if self . model . products . count > 0 {
if self . model . products . count > 0 {
Section {
ProductsSectionView ( model : self . model )
ForEach ( self . model . products ) { product in
let isSelected = self . model . selectedProduct = = product
ProductView ( product : product ,
quantity : self . $ model . quantity ,
selected : isSelected )
. foregroundStyle ( . white )
. frame ( maxWidth : . infinity )
. buttonStyle ( . borderedProminent )
. tint ( Color . master )
. listRowBackground ( Color . clear )
. onTapGesture {
self . model . selectedProduct = product
}
}
} header : {
Text ( " Sélectionnez une offre " )
}
if let product = self . model . selectedProduct {
if let product = self . model . selectedProduct {
Section {
Section {
Button {
Button {
if Store . main . userId != nil {
self . _purchaseIfPossible ( )
self . _purchase ( )
} else {
self . showLoginView = true
}
} label : {
} label : {
HStack {
PurchaseLabelView ( price : self . model . totalPrice , isPurchasing : self . isPurchasing )
if self . isPurchasing {
Spacer ( )
ProgressView ( ) . tint ( . white )
Spacer ( )
} else {
Text ( " Acheter " )
Spacer ( )
Text ( self . model . totalPrice )
}
}
. padding ( 8.0 )
. fontWeight ( . bold )
}
}
. buttonStyle ( . borderedProminent )
. buttonStyle ( . borderedProminent )
. tint ( . orange )
. tint ( . orange )
@ -205,19 +166,8 @@ struct SubscriptionView: View {
}
}
} else {
} else {
if self . model . isLoading {
NoProductView ( )
ProgressView ( )
. isLoading ( self . model . isLoading )
} else {
HStack {
if let plan = Guard . main . currentPlan {
Image ( systemName : plan . systemImage )
} else {
Image ( systemName : " questionmark.diamond.fill " )
}
Text ( " Il n'y a pas de produits à vous proposer " )
}
}
}
}
}
}
. listStyle ( . grouped )
. listStyle ( . grouped )
@ -225,13 +175,9 @@ struct SubscriptionView: View {
. background ( . logoBackground )
. background ( . logoBackground )
. toolbar {
. toolbar {
ToolbarItem ( placement : . navigationBarTrailing ) {
ToolbarItem ( placement : . navigationBarTrailing ) {
if self . isRestoring {
Button ( " Restaurer " ) {
ProgressView ( )
self . _restore ( )
} else {
} . isLoading ( self . isRestoring )
Button ( " Restaurer " ) {
self . _restore ( )
}
}
}
}
}
}
}
}
@ -241,6 +187,14 @@ struct SubscriptionView: View {
}
}
fileprivate func _purchaseIfPossible ( ) {
if Store . main . userId != nil {
self . _purchase ( )
} else {
self . showLoginView = true
}
}
fileprivate func _purchase ( ) {
fileprivate func _purchase ( ) {
self . isPurchasing = true
self . isPurchasing = true
@ -280,11 +234,69 @@ struct SubscriptionView: View {
}
}
fileprivate struct ProductsSectionView : View {
@ ObservedObject var model : SubscriptionModel
var body : some View {
Section {
ForEach ( self . model . products ) { product in
ProductView ( product : product ,
model : self . model )
. onTapGesture {
self . model . selectedProduct = product
}
}
} header : {
Text ( " Sélectionnez une offre " )
}
}
}
fileprivate struct PurchaseLabelView : View {
var price : String
var isPurchasing : Bool
var body : some View {
HStack ( alignment : . center ) {
if self . isPurchasing {
Spacer ( )
ProgressView ( ) . tint ( Color . white )
Spacer ( )
} else {
Text ( " Acheter " )
Spacer ( )
Text ( self . price )
}
}
. padding ( 8.0 )
. fontWeight ( . bold )
}
}
fileprivate struct NoProductView : View {
var body : some View {
HStack {
if let plan = Guard . main . currentPlan {
Image ( systemName : plan . systemImage )
} else {
Image ( systemName : " questionmark.diamond.fill " )
}
Text ( " Il n'y a pas de produits à vous proposer " )
}
}
}
struct ProductView : View {
struct ProductView : View {
var product : Product
var product : Product
@ Binding var quantity : Int
var selected : Bool
@ ObservedObject var model : SubscriptionModel
// @ B i n d i n g v a r q u a n t i t y : I n t
// v a r s e l e c t e d : B o o l
var body : some View {
var body : some View {
HStack {
HStack {
@ -292,22 +304,26 @@ struct ProductView: View {
. font ( . system ( size : 36.0 ) )
. font ( . system ( size : 36.0 ) )
. foregroundColor ( . orange )
. foregroundColor ( . orange )
VStack ( alignment : . leading ) {
VStack ( alignment : . leading ) {
Text ( product . displayName )
Text ( self . product . displayName )
Text ( product . formattedPrice )
Text ( self . product . formattedPrice )
. foregroundColor ( . accentColor )
. foregroundColor ( . accentColor )
if self . _isConsumable {
if self . _isConsumable {
StepperView ( count : self . $ quantity , minimum : 1 )
StepperView ( count : self . $ model . quantity , minimum : 1 , countChanged : { self . model . selectedProduct = self . product } )
. font ( . callout ) . foregroundColor ( . accentColor )
. font ( . callout ) . foregroundColor ( . accentColor )
}
}
}
}
Spacer ( )
Spacer ( )
if self . s elected {
if self . model . isS elected( product : self . product ) {
Image ( systemName : " checkmark " )
Image ( systemName : " checkmark " )
. foregroundColor ( . accentColor )
. foregroundColor ( . accentColor )
}
}
}
}
. contentShape ( . rect )
. contentShape ( . rect )
. foregroundStyle ( . white )
. frame ( maxWidth : . infinity )
. buttonStyle ( . borderedProminent )
. tint ( Color . master )
. listRowBackground ( Color . clear )
}
}
fileprivate var _item : StoreItem ? {
fileprivate var _item : StoreItem ? {
@ -328,7 +344,7 @@ struct ProductView: View {
}
}
struct SubscriptionNoProductView : View {
fileprivate struct SubscriptionNoProductView : View {
@ ObservedObject var model : SubscriptionModel
@ ObservedObject var model : SubscriptionModel
@ -356,14 +372,14 @@ struct SubscriptionNoProductView: View {
}
}
}
}
struct SubscriptionFooterView : View {
fileprivate struct SubscriptionFooterView : View {
var body : some View {
var body : some View {
Text ( " Conditions d’utilisations concernant l’abonnement: \n - Le paiement sera facturé sur votre compte Apple. \n - L’abonnement est renouvelé automatiquement chaque mois, à moins d’avoir été désactivé au moins 24 heures avant la fin de la période de l’abonnement. \n - L’abonnement peut être géré par l’utilisateur et désactivé en allant dans les réglages de son compte après s’être abonné. \n - Le compte sera facturé pour le renouvellement de l'abonnement dans les 24 heures précédent la fin de la période d’abonnement. \n - Un abonnement en cours ne peut être annulé. \n - Toute partie inutilisée de l'offre gratuite, si souscrite, sera abandonnée lorsque l'utilisateur s'abonnera, dans les cas applicables. " )
Text ( " Conditions d’utilisations concernant l’abonnement: \n - Le paiement sera facturé sur votre compte Apple. \n - L’abonnement est renouvelé automatiquement chaque mois, à moins d’avoir été désactivé au moins 24 heures avant la fin de la période de l’abonnement. \n - L’abonnement peut être géré par l’utilisateur et désactivé en allant dans les réglages de son compte après s’être abonné. \n - Le compte sera facturé pour le renouvellement de l'abonnement dans les 24 heures précédent la fin de la période d’abonnement. \n - Un abonnement en cours ne peut être annulé. \n - Toute partie inutilisée de l'offre gratuite, si souscrite, sera abandonnée lorsque l'utilisateur s'abonnera, dans les cas applicables. " )
. padding ( . top )
. padding ( . top )
}
}
}
}
struct SubscriptionDetailView : View {
fileprivate struct SubscriptionDetailView : View {
var body : some View {
var body : some View {
HStack {
HStack {
@ -376,6 +392,8 @@ struct SubscriptionDetailView: View {
. padding ( )
. padding ( )
. background ( . orange )
. background ( . orange )
. foregroundStyle ( . black )
. foregroundStyle ( . black )
. clipShape ( . rect ( cornerRadius : 16.0 ) )
. padding ( )
}
}
}
}