mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-04 08:37:30 +00:00
ios: badge previews show fixed prices (#7917)
* ios: badge previews show fixed prices * ci: references of badge previews with prices and of channel members * ios: badge price list computes annual savings from the amounts * ios: badge previews pass what is for sale and earlier subscriptions with the prices
This commit is contained in:
1 parent
26c6b3172b
commit
c7a6c65960
5 files changed
+73
-22
No files matched your search
@@ -53,6 +53,38 @@ enum BadgePrice {
|
||||
}
|
||||
}
|
||||
|
||||
struct BadgePriceList {
|
||||
let oneMonth: BadgePrice
|
||||
let monthly: BadgePrice
|
||||
let annual: BadgePrice
|
||||
let monthlyAmount: Decimal?
|
||||
let annualAmount: Decimal?
|
||||
let subscriptionsForSale: Bool
|
||||
let subscribedBefore: Bool
|
||||
|
||||
func price(_ period: BadgePeriod) -> BadgePrice {
|
||||
switch period {
|
||||
case .oneMonth: oneMonth
|
||||
case .monthly: monthly
|
||||
case .annual: annual
|
||||
}
|
||||
}
|
||||
|
||||
func forSale(_ period: BadgePeriod) -> Bool {
|
||||
period == .oneMonth || subscriptionsForSale
|
||||
}
|
||||
|
||||
// percentage the annual subscription saves against 12 monthly payments
|
||||
var annualSavings: Int? {
|
||||
guard let monthlyAmount, let annualAmount else { return nil }
|
||||
let year = monthlyAmount * 12
|
||||
guard year > 0, annualAmount < year else { return nil }
|
||||
let saved = (year - annualAmount) / year * 100
|
||||
let percent = Int(NSDecimalNumber(decimal: saved).doubleValue.rounded())
|
||||
return percent > 0 ? percent : nil
|
||||
}
|
||||
}
|
||||
|
||||
struct BadgeStoreReceipt {
|
||||
// the signed token the badge service verifies - never transaction.jsonRepresentation
|
||||
let jws: String
|
||||
@@ -169,16 +201,16 @@ final class BadgeStore: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// percentage the annual subscription saves against 12 monthly payments
|
||||
func annualSavings(_ level: BadgeLevel) -> Int? {
|
||||
guard let monthly = products[badgeProductId(level, .monthly)],
|
||||
let annual = products[badgeProductId(level, .annual)]
|
||||
else { return nil }
|
||||
let year = monthly.price * 12
|
||||
guard year > 0, annual.price < year else { return nil }
|
||||
let saved = (year - annual.price) / year * 100
|
||||
let percent = Int(NSDecimalNumber(decimal: saved).doubleValue.rounded())
|
||||
return percent > 0 ? percent : nil
|
||||
func priceList(_ level: BadgeLevel, compact: Bool = true) -> BadgePriceList {
|
||||
BadgePriceList(
|
||||
oneMonth: price(level, .oneMonth, compact: compact),
|
||||
monthly: price(level, .monthly, compact: compact),
|
||||
annual: price(level, .annual, compact: compact),
|
||||
monthlyAmount: products[badgeProductId(level, .monthly)]?.price,
|
||||
annualAmount: products[badgeProductId(level, .annual)]?.price,
|
||||
subscriptionsForSale: forSale(level, .monthly),
|
||||
subscribedBefore: subscribedBefore
|
||||
)
|
||||
}
|
||||
|
||||
func load() async {
|
||||
|
||||
@@ -19,11 +19,16 @@ struct BadgesCheckOrderView: View {
|
||||
let level: BadgeLevel
|
||||
let period: BadgePeriod
|
||||
var startDate = Date()
|
||||
var priceList: BadgePriceList? = nil
|
||||
@State private var purchasing = false
|
||||
@State private var privacyPolicyActive = false
|
||||
// presented from this view, not AlertManager: its host is behind the sheet these views open in
|
||||
@State private var alert: SomeAlert?
|
||||
|
||||
private var prices: BadgePriceList {
|
||||
priceList ?? store.priceList(level, compact: false)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { g in
|
||||
ScrollView {
|
||||
@@ -41,7 +46,7 @@ struct BadgesCheckOrderView: View {
|
||||
Divider()
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 6)
|
||||
orderRow("Total", period.priceText(store.price(level, period, compact: false)))
|
||||
orderRow("Total", period.priceText(prices.price(period)))
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 12)
|
||||
@@ -49,7 +54,7 @@ struct BadgesCheckOrderView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||
.padding(.top, 20)
|
||||
|
||||
if case let .price(p) = store.price(level, period, compact: false) {
|
||||
if case let .price(p) = prices.price(period) {
|
||||
switch period {
|
||||
case .monthly: footnote("Payment is charged to your Apple ID when you confirm the purchase. The subscription renews automatically at \(p) per month unless you cancel it at least 24 hours before the end of the current period. You can manage and cancel it in your App Store account settings.")
|
||||
case .annual: footnote("Payment is charged to your Apple ID when you confirm the purchase. The subscription renews automatically at \(p) per year unless you cancel it at least 24 hours before the end of the current period. You can manage and cancel it in your App Store account settings.")
|
||||
@@ -57,7 +62,7 @@ struct BadgesCheckOrderView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if !store.forSale(level, period) && store.subscribedBefore {
|
||||
if !prices.forSale(period) && prices.subscribedBefore {
|
||||
footnote("This Apple ID already has a badge subscription. Only the profile that bought it can continue it.")
|
||||
}
|
||||
|
||||
@@ -124,8 +129,8 @@ struct BadgesCheckOrderView: View {
|
||||
}
|
||||
|
||||
private func payButton() -> some View {
|
||||
let price = store.price(level, period, compact: false)
|
||||
let disabled = !price.canPurchase || purchasing || !store.canBuy(chatModel.currentUser?.userId) || !store.forSale(level, period) || !AppStore.canMakePayments
|
||||
let price = prices.price(period)
|
||||
let disabled = !price.canPurchase || purchasing || !store.canBuy(chatModel.currentUser?.userId) || !prices.forSale(period) || !AppStore.canMakePayments
|
||||
return Button {
|
||||
purchase()
|
||||
} label: {
|
||||
@@ -212,7 +217,12 @@ struct RestorePurchasesButton: View {
|
||||
struct BadgesCheckOrderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
BadgesCheckOrderView(level: .supporter, period: .oneMonth, startDate: previewDate)
|
||||
BadgesCheckOrderView(
|
||||
level: .supporter,
|
||||
period: .oneMonth,
|
||||
startDate: previewDate,
|
||||
priceList: BadgePriceList(oneMonth: .price("$7.00"), monthly: .price("$7.00"), annual: .price("$42.00"), monthlyAmount: 7, annualAmount: 42, subscriptionsForSale: true, subscribedBefore: false)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,12 +75,17 @@ struct BadgesHowLongView: View {
|
||||
@ObservedObject private var badgeModel = BadgeModel.shared
|
||||
let level: BadgeLevel
|
||||
var startDate = Date()
|
||||
var priceList: BadgePriceList? = nil
|
||||
@State private var chosenPeriod: BadgePeriod? = nil
|
||||
@State private var continueActive = false
|
||||
|
||||
private var prices: BadgePriceList {
|
||||
priceList ?? store.priceList(level)
|
||||
}
|
||||
|
||||
// monthly once subscriptions turn out to be for sale, unless a period was chosen before that
|
||||
private var selectedPeriod: BadgePeriod {
|
||||
chosenPeriod ?? (store.forSale(level, .monthly) ? .monthly : .oneMonth)
|
||||
chosenPeriod ?? (prices.forSale(.monthly) ? .monthly : .oneMonth)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -112,7 +117,7 @@ struct BadgesHowLongView: View {
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
if !store.forSale(level, .monthly) && store.subscribedBefore {
|
||||
if !prices.forSale(.monthly) && prices.subscribedBefore {
|
||||
Text("This Apple ID already has a badge subscription. Only the profile that bought it can continue it.")
|
||||
.font(.footnote)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
@@ -142,7 +147,7 @@ struct BadgesHowLongView: View {
|
||||
|
||||
private func periodCard(_ period: BadgePeriod) -> some View {
|
||||
let isSelected = period == selectedPeriod
|
||||
let forSale = store.forSale(level, period)
|
||||
let forSale = prices.forSale(period)
|
||||
return Button {
|
||||
chosenPeriod = period
|
||||
} label: {
|
||||
@@ -155,7 +160,7 @@ struct BadgesHowLongView: View {
|
||||
.opacity(forSale ? 1 : 0.4)
|
||||
Text(period.label)
|
||||
.font(.subheadline)
|
||||
period.priceText(store.price(level, period))
|
||||
period.priceText(prices.price(period))
|
||||
.font(.headline)
|
||||
if let percent = savingsPercent(period) {
|
||||
Text("\(percent)% off")
|
||||
@@ -180,7 +185,7 @@ struct BadgesHowLongView: View {
|
||||
}
|
||||
|
||||
private func savingsPercent(_ period: BadgePeriod) -> Int? {
|
||||
period == .annual ? store.annualSavings(level) : nil
|
||||
period == .annual ? prices.annualSavings : nil
|
||||
}
|
||||
|
||||
private func continueButton() -> some View {
|
||||
@@ -234,7 +239,11 @@ struct BadgeBillingFooter: View {
|
||||
struct BadgesHowLongView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
BadgesHowLongView(level: .supporter, startDate: previewDate)
|
||||
BadgesHowLongView(
|
||||
level: .supporter,
|
||||
startDate: previewDate,
|
||||
priceList: BadgePriceList(oneMonth: .price("$7"), monthly: .price("$7"), annual: .price("$42"), monthlyAmount: 7, annualAmount: 42, subscriptionsForSale: true, subscribedBefore: false)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
Reference in new issue
Block a user