More work on subscriptions and fixes

multistore
Laurent 2 years ago
parent 56553329a1
commit e9ec8111b8
  1. 12
      PadelClub/Assets.xcassets/AccentColor.colorset/Contents.json
  2. 52
      PadelClub/Data/DataStore.swift
  3. 6
      PadelClub/Data/User.swift
  4. 9
      PadelClub/Launch Screen.storyboard
  5. 4
      PadelClub/Views/Match/MatchDetailView.swift
  6. 7
      PadelClub/Views/Subscription/Guard.swift
  7. 2
      PadelClub/Views/Subscription/PurchaseListView.swift
  8. 16
      PadelClub/Views/Subscription/SubscriptionView.swift
  9. 2
      PadelClub/Views/Tournament/Screen/BroadcastView.swift
  10. 2
      PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift
  11. 3
      PadelClub/Views/Tournament/TournamentView.swift
  12. 2
      PadelClub/Views/User/AccountView.swift
  13. 2
      PadelClub/Views/User/LoginView.swift
  14. 58
      PadelClubTests/PaymentTests.swift

@ -1,11 +1,23 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.573",
"red" : "0.953"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"localizable" : true
}
}

@ -28,7 +28,9 @@ class DataStore: ObservableObject {
fileprivate(set) var monthData: StoredCollection<MonthData>
fileprivate(set) var dateIntervals: StoredCollection<DateInterval>
fileprivate var _userStorage: OptionalStorage<User> = OptionalStorage<User>(fileName: "user.json")
fileprivate(set) var userStorage: StoredObject<User>
// fileprivate var _userStorage: OptionalStorage<User> = OptionalStorage<User>(fileName: "user.json")
fileprivate var _appSettingsStorage: MicroStorage<AppSettings> = MicroStorage()
var appSettings: AppSettings {
@ -51,11 +53,18 @@ class DataStore: ObservableObject {
}
var user: User? {
return self._userStorage.item
return self.userStorage.item()
// return self._userStorage.item
}
func setUser(_ user: User?) {
self._userStorage.item = user
func setUser(_ user: User) {
do {
try self.userStorage.setItem(user)
self._loadCollections()
} catch {
Logger.error(error)
}
// self._userStorage.item = user
}
init() {
@ -68,18 +77,21 @@ class DataStore: ObservableObject {
// store.addMigration(Migration<TournamentV2, Tournament>(version: 3))
let indexed : Bool = true
self.clubs = store.registerCollection(synchronized: false, indexed: indexed)
self.courts = store.registerCollection(synchronized: false, indexed: indexed)
self.tournaments = store.registerCollection(synchronized: false, indexed: indexed)
self.events = store.registerCollection(synchronized: false, indexed: indexed)
self.groupStages = store.registerCollection(synchronized: false, indexed: indexed)
self.teamScores = store.registerCollection(synchronized: false, indexed: indexed)
self.teamRegistrations = store.registerCollection(synchronized: false, indexed: indexed)
self.playerRegistrations = store.registerCollection(synchronized: false, indexed: indexed)
self.rounds = store.registerCollection(synchronized: false, indexed: indexed)
self.matches = store.registerCollection(synchronized: false, indexed: indexed)
self.monthData = store.registerCollection(synchronized: false, indexed: indexed)
self.dateIntervals = store.registerCollection(synchronized: false, indexed: indexed)
let synchronized : Bool = false
self.clubs = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.courts = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.tournaments = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.events = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.groupStages = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.teamScores = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.teamRegistrations = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.playerRegistrations = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.rounds = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.matches = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.monthData = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.dateIntervals = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.userStorage = store.registerObject(synchronized: synchronized)
NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidLoad, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidChange, object: nil)
@ -108,4 +120,12 @@ class DataStore: ObservableObject {
return .none
}
func disconnect() {
Store.main.disconnect(resetAll: true)
}
fileprivate func _loadCollections() {
Store.main.loadCollections()
}
}

@ -15,7 +15,11 @@ enum UserRight: Int, Codable {
}
@Observable
class User: UserBase {
class User: UserBase, Storable {
static func resourceName() -> String { "users" }
func deleteDependencies() throws { }
public var id: String = Store.randomId()
public var username: String

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -18,7 +17,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="PadelClub_logo_fondfonce_transparent" translatesAutoresizingMaskIntoConstraints="NO" id="Lsd-2D-TDo">
<rect key="frame" x="64" y="-628.66666666666663" width="265" height="2134.3333333333335"/>
<rect key="frame" x="90" y="-628.66666666666663" width="213" height="2134.3333333333335"/>
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
@ -26,7 +25,7 @@
<color key="tintColor" name="AccentColor"/>
<constraints>
<constraint firstItem="Lsd-2D-TDo" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="7Tm-4j-KPz"/>
<constraint firstItem="Lsd-2D-TDo" firstAttribute="width" secondItem="Ze5-6b-2t3" secondAttribute="height" multiplier="265:852" id="Nhb-n4-HGG"/>
<constraint firstItem="Lsd-2D-TDo" firstAttribute="width" secondItem="Ze5-6b-2t3" secondAttribute="height" multiplier="1:4" id="Nhb-n4-HGG"/>
<constraint firstItem="Lsd-2D-TDo" firstAttribute="centerY" secondItem="Bcu-3y-fUS" secondAttribute="centerY" id="xOG-dI-NIb"/>
</constraints>
</view>

@ -181,7 +181,9 @@ struct MatchDetailView: View {
MatchTeamDetailView(match: match).tint(.master)
}
.sheet(isPresented: self.$showSubscriptionView, content: {
SubscriptionView(showLackOfPlanMessage: true)
NavigationStack {
SubscriptionView(showLackOfPlanMessage: true)
}
})
.sheet(item: $scoreType, onDismiss: {
if match.hasEnded() {

@ -185,13 +185,12 @@ import LeStorage
}
return nil
default:
// let subscriptionPayed = DataStore.shared.tournaments.filter { $0.payment?.isSubscription == true }
let unitlyPayed = DataStore.shared.tournaments.filter { $0.payment == .unit && $0.isCanceled == false }.count
if unitlyPayed == 0 {
let freelyPayed = DataStore.shared.tournaments.filter { $0.payment == .free && $0.isCanceled == false }.count
if freelyPayed < 1 {
return Tournament.TournamentPayment.free
}
let tournamentCreditCount = self._purchasedTournamentCount()
let unitlyPayed = DataStore.shared.tournaments.filter { $0.payment == .unit && $0.isCanceled == false }.count
if tournamentCreditCount > unitlyPayed {
return Tournament.TournamentPayment.unit
}

@ -115,7 +115,7 @@ struct PurchaseView: View {
.foregroundColor(.accentColor)
Text(self.purchaseRow.name)
Spacer()
if let quantity = purchaseRow.quantity {
if let _ = purchaseRow.quantity {
let remaining = Guard.main.remainingTournaments
Text("\(remaining)")
}

@ -51,6 +51,7 @@ class SubscriptionModel: ObservableObject, StoreDelegate {
}
@Published var products: [Product] = []
@Published var totalPrice: String = ""
@State var showSuccessfulPurchaseView: Bool = false
func load() {
self.isLoading = true
@ -77,7 +78,9 @@ class SubscriptionModel: ObservableObject, StoreDelegate {
}
Task {
if product.item.isConsumable {
let _ = try await self.storeManager?.purchase(product, quantity: self.quantity)
if let result = try await self.storeManager?.purchase(product, quantity: self.quantity) {
self.showSuccessfulPurchaseView = true
}
} else {
let _ = try await self.storeManager?.purchase(product)
}
@ -121,7 +124,12 @@ struct SubscriptionView: View {
Form {
if self.showLackOfPlanMessage {
Text("Vous ne disposez malheureusement pas d'offre pour continuer votre tournoi. Voici ce que nous proposons:")
HStack {
Image(systemName: "exclamationmark.bubble.fill").foregroundStyle(Color.accentColor)
.font(.title)
Text("Vous ne disposez malheureusement plus d'offre pour continuer votre tournoi. Voici ce que nous proposons:")
.fontWeight(.semibold)
}
}
if self.model.products.count > 0 {
@ -196,7 +204,6 @@ struct SubscriptionView: View {
fileprivate func _restore() {
Task {
do {
self.isRestoring = true
try await Guard.main.refreshPurchasedAppleProducts()
@ -205,7 +212,6 @@ struct SubscriptionView: View {
self.isRestoring = false
Logger.error(error)
}
}
}
@ -292,6 +298,6 @@ struct SubscriptionFooterView: View {
#Preview {
NavigationStack {
SubscriptionView()
SubscriptionView(showLackOfPlanMessage: true)
}
}

@ -26,7 +26,7 @@ struct BroadcastView: View {
List {
Section {
Toggle(isOn: $tournament.isPrivate) {
Text("Tournoi privée")
Text("Tournoi privé")
}
} footer: {
Text("Le tournoi sera masqué sur le site \(URLs.main.rawValue)")

@ -68,7 +68,7 @@ struct TournamentStatusView: View {
Section {
Toggle(isOn: $tournament.isPrivate) {
Text("Tournoi privée")
Text("Tournoi privé")
}
} footer: {
Text("Le tournoi sera masqué sur le site padelclub.app")

@ -157,6 +157,9 @@ struct TournamentView: View {
}
}
}
.onAppear {
Logger.log("Payment = \(String(describing: self.tournament.payment)), canceled = \(self.tournament.isCanceled)")
}
}
private func _save() {

@ -19,7 +19,7 @@ struct AccountView: View {
ChangePasswordView()
}
Button("Disconnect") {
Store.main.disconnect()
DataStore.shared.disconnect()
handler()
}
}.navigationTitle(user.username)

@ -13,7 +13,7 @@ struct LoginView: View {
@EnvironmentObject var dataStore: DataStore
@State var username: String = "laurent"
@State var password: String = "staxstax"
@State var password: String = "StaxKikoo12"
@State var isLoading: Bool = false
@State var showEmailPopup: Bool = false

@ -21,25 +21,57 @@ final class PaymentTests: XCTestCase {
func testPayments() throws {
let tournament = Tournament.fake()
tournament.setPayment(.free)
assert(tournament.decryptPayment() == .free)
tournament.setPayment(.subscriptionUnit)
assert(tournament.decryptPayment() == .subscriptionUnit)
tournament.setPayment(.unit)
assert(tournament.decryptPayment() == .unit)
tournament.setPayment(.unlimited)
assert(tournament.decryptPayment() == .unlimited)
do {
tournament.payment = .free
var encoded = try JSONEncoder().encode(tournament)
var decoded = try JSONDecoder().decode(Tournament.self, from: encoded)
assert(decoded.payment == .free)
tournament.payment = .subscriptionUnit
encoded = try JSONEncoder().encode(tournament)
decoded = try JSONDecoder().decode(Tournament.self, from: encoded)
assert(decoded.payment == .subscriptionUnit)
tournament.payment = .unit
encoded = try JSONEncoder().encode(tournament)
decoded = try JSONDecoder().decode(Tournament.self, from: encoded)
assert(decoded.payment == .unit)
tournament.payment = .unlimited
encoded = try JSONEncoder().encode(tournament)
decoded = try JSONDecoder().decode(Tournament.self, from: encoded)
assert(decoded.payment == .unlimited)
} catch {
assertionFailure(error.localizedDescription)
}
}
func testCanceled() throws {
let tournament = Tournament.fake()
tournament.setCanceled(true)
assert(tournament.decryptCanceled() == true)
tournament.setCanceled(false)
assert(tournament.decryptCanceled() == false)
do {
tournament.isCanceled = false
var encoded = try JSONEncoder().encode(tournament)
var decoded = try JSONDecoder().decode(Tournament.self, from: encoded)
assert(decoded.isCanceled == false)
tournament.isCanceled = true
encoded = try JSONEncoder().encode(tournament)
decoded = try JSONDecoder().decode(Tournament.self, from: encoded)
assert(decoded.isCanceled == true)
} catch {
assertionFailure(error.localizedDescription)
}
// tournament.setCanceled(true)
// assert(tournament.decryptCanceled() == true)
// tournament.setCanceled(false)
// assert(tournament.decryptCanceled() == false)
}

Loading…
Cancel
Save