diff --git a/apps/ios/Shared/Views/Badges/BadgeStore.swift b/apps/ios/Shared/Views/Badges/BadgeStore.swift index b25117cdda..d2ce332956 100644 --- a/apps/ios/Shared/Views/Badges/BadgeStore.swift +++ b/apps/ios/Shared/Views/Badges/BadgeStore.swift @@ -141,11 +141,11 @@ final class BadgeStore: ObservableObject { return storePurchases.purchases } - func price(_ level: BadgeLevel, _ period: BadgePeriod) -> BadgePrice { + func price(_ level: BadgeLevel, _ period: BadgePeriod, compact: Bool = true) -> BadgePrice { switch state { case .notLoaded, .loading: return .loading case .loaded, .failed: - if let p = products[badgeProductId(level, period)] { return .price(compactPrice(p)) } + if let p = products[badgeProductId(level, period)] { return .price(compact ? compactPrice(p) : p.displayPrice) } return .unavailable } } diff --git a/apps/ios/Shared/Views/Badges/BadgeUserPreview.swift b/apps/ios/Shared/Views/Badges/BadgeUserPreview.swift index 45d94c3ed6..10b68be473 100644 --- a/apps/ios/Shared/Views/Badges/BadgeUserPreview.swift +++ b/apps/ios/Shared/Views/Badges/BadgeUserPreview.swift @@ -9,15 +9,9 @@ import SwiftUI import SimpleXChat -struct BadgeUserPreview: View { +struct BadgeUserPreview: View { @EnvironmentObject var chatModel: ChatModel let level: BadgeLevel - let trailing: () -> Trailing - - init(level: BadgeLevel, @ViewBuilder trailing: @escaping () -> Trailing = { EmptyView() }) { - self.level = level - self.trailing = trailing - } var body: some View { let user = chatModel.currentUser @@ -29,12 +23,9 @@ struct BadgeUserPreview: View { ) return VStack(spacing: 12) { ProfileImage(imageStr: user?.image, size: 128) - HStack(alignment: .center, spacing: 6) { - NameWithBadge(Text(displayName).font(.largeTitle), previewBadge, .largeTitle) - .lineLimit(1) - .minimumScaleFactor(0.75) - trailing() - } + NameWithBadge(Text(displayName).font(.largeTitle), previewBadge, .largeTitle) + .lineLimit(1) + .minimumScaleFactor(0.75) } } } diff --git a/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift b/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift index a5214ffb3e..5819319dcf 100644 --- a/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift +++ b/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift @@ -30,19 +30,19 @@ struct BadgesCheckOrderView: View { .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) - VStack(spacing: 12) { + VStack(spacing: 0) { orderRow("Your badge", Text(level.title)) orderRow("Duration", Text(period.label)) - orderRow("Total", period.priceText(store.price(level, period))) + Divider() + .padding(.top, 8) + orderRow("Total", period.priceText(store.price(level, period, compact: false)), total: true) } + .padding(.horizontal, 24) + .padding(.vertical, 12) + .background(Color(uiColor: .secondarySystemGroupedBackground)) + .clipShape(RoundedRectangle(cornerRadius: 16)) .padding(.top, 20) - Text("Charged by the App Store to the account on this device.") - .font(.footnote) - .foregroundColor(theme.colors.secondary) - .multilineTextAlignment(.center) - .fixedSize(horizontal: false, vertical: true) - Spacer(minLength: 20) VStack(spacing: 10) { @@ -64,18 +64,22 @@ struct BadgesCheckOrderView: View { .alert(item: $alert) { $0.alert } } - private func orderRow(_ title: LocalizedStringKey, _ value: Text) -> some View { + private func orderRow(_ title: LocalizedStringKey, _ value: Text, total: Bool = false) -> some View { HStack { Text(title) - .foregroundColor(theme.colors.secondary) + .fontWeight(total ? .semibold : .medium) + .foregroundColor(total ? nil : theme.colors.secondary) Spacer() value + .fontWeight(.semibold) } - .font(.body) + .font(total ? .title3 : .body) + .padding(.top, total ? 14 : 8) + .padding(.bottom, 8) } private func payButton() -> some View { - let price = store.price(level, period) + let price = store.price(level, period, compact: false) let disabled = !price.canPurchase || purchasing || !store.canBuy(chatModel.currentUser?.userId) return Button { purchase() diff --git a/apps/ios/Shared/Views/Badges/BadgesChooseBadgeView.swift b/apps/ios/Shared/Views/Badges/BadgesChooseBadgeView.swift index 7b96d0d1ab..8561f6f3a1 100644 --- a/apps/ios/Shared/Views/Badges/BadgesChooseBadgeView.swift +++ b/apps/ios/Shared/Views/Badges/BadgesChooseBadgeView.swift @@ -23,10 +23,17 @@ enum BadgeLevel: String, CaseIterable, Identifiable { } } - var filesDescription: LocalizedStringKey { + var fileSize: LocalizedStringKey { switch self { - case .supporter: "Send 2GB files" - case .legend: "Send 5GB files" + case .supporter: "Files up to 2 GB" + case .legend: "Files up to 5 GB" + } + } + + var fileStorage: LocalizedStringKey { + switch self { + case .supporter: "Stored for 7 days" + case .legend: "Stored for 21 days" } } @@ -68,12 +75,8 @@ struct BadgesChooseBadgeView: View { .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) - BadgeUserPreview(level: selectedLevel) { - Image(systemName: "chevron.down") - .font(.body) - .foregroundColor(theme.colors.primary) - } - .padding(.top, 4) + BadgeUserPreview(level: selectedLevel) + .padding(.top, 4) Spacer(minLength: 20) @@ -127,9 +130,12 @@ struct BadgesChooseBadgeView: View { Text(level.title) .font(.title3) .fontWeight(.bold) - Text(level.filesDescription) - .font(.subheadline) - .foregroundColor(theme.colors.secondary) + VStack(spacing: 2) { + Text(level.fileSize) + Text(level.fileStorage) + } + .font(.subheadline) + .foregroundColor(theme.colors.secondary) BadgePeriod.monthly.priceText(store.price(level, .monthly)) .font(.body) .padding(.bottom, 20) diff --git a/apps/ios/Shared/Views/Badges/BadgesHowLongView.swift b/apps/ios/Shared/Views/Badges/BadgesHowLongView.swift index 0f741f45fc..f918b7df33 100644 --- a/apps/ios/Shared/Views/Badges/BadgesHowLongView.swift +++ b/apps/ios/Shared/Views/Badges/BadgesHowLongView.swift @@ -33,6 +33,14 @@ enum BadgePeriod: String, CaseIterable, Identifiable { } } + var months: Int { + switch self { + case .oneMonth: 1 + case .monthly: 1 + case .annual: 12 + } + } + func priceText(_ price: BadgePrice) -> Text { switch price { case .loading: return Text(verbatim: "…") @@ -92,7 +100,7 @@ struct BadgesHowLongView: View { // fixedSize + maxHeight on the cards so they all match the tallest one - // only Annual carries a savings line, and prices wrap at large fonts HStack(alignment: .top, spacing: 12) { - ForEach(badgePeriodsForSale) { periodCard($0) } + ForEach(BadgePeriod.allCases) { periodCard($0) } } .fixedSize(horizontal: false, vertical: true) @@ -118,6 +126,7 @@ struct BadgesHowLongView: View { private func periodCard(_ period: BadgePeriod) -> some View { let isSelected = period == selectedPeriod + let forSale = badgePeriodsForSale.contains(period) return Button { selectedPeriod = period } label: { @@ -127,17 +136,19 @@ struct BadgesHowLongView: View { .scaledToFit() .frame(width: 32, height: 32) .foregroundColor(isSelected ? theme.colors.primary : theme.colors.secondary) + .opacity(forSale ? 1 : 0.4) Text(period.label) - .font(.title3) - .fontWeight(.bold) - period.priceText(store.price(level, period)) .font(.body) + period.priceText(store.price(level, period)) + .font(.title3) + .fontWeight(.semibold) if let percent = savingsPercent(period) { Text("Save \(percent)%") .font(.footnote) .foregroundColor(isSelected ? theme.colors.primary : theme.colors.secondary) } } + .foregroundColor(forSale ? nil : theme.colors.secondary) .multilineTextAlignment(.center) .padding(.vertical, 25) .padding(.horizontal, 12) @@ -150,6 +161,7 @@ struct BadgesHowLongView: View { ) } .buttonStyle(.plain) + .disabled(!forSale) } private func savingsPercent(_ period: BadgePeriod) -> Int? { @@ -191,10 +203,10 @@ struct BadgeBillingFooter: View { } private var billingFooter: LocalizedStringKey { - // TODO [badges] source the actual date from the purchase state machine when wired. - var comps = DateComponents(); comps.year = 2026; comps.month = 7; comps.day = 22 - let stubDate = Calendar.current.date(from: comps) ?? Date() - let date = DateFormatter.localizedString(from: stubDate, dateStyle: .long, timeStyle: .none) + // TODO [badges] from now only because a purchase is refused while a badge is held (refuseWhileBadgeHeld); + // a top-up must count from the end of the existing balance + let endDate = Calendar.current.date(byAdding: .month, value: period.months, to: Date()) ?? Date() + let date = DateFormatter.localizedString(from: endDate, dateStyle: .long, timeStyle: .none) switch period { case .monthly, .annual: return "Renews on \(date). Cancel anytime." case .oneMonth: return "Ends on \(date)." diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgeStore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgeStore.kt index 01552d7365..4d64a00f11 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgeStore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgeStore.kt @@ -184,11 +184,11 @@ object BadgeStore { return if (rhId == chatModel.remoteHostId() && readFor == userId) purchases else emptyList() } - fun price(level: BadgeLevel, period: BadgePeriod): BadgePrice = when (state.value) { + fun price(level: BadgeLevel, period: BadgePeriod, compact: Boolean = true): BadgePrice = when (state.value) { LoadState.NotLoaded, LoadState.Loading -> BadgePrice.Loading LoadState.Loaded, LoadState.Failed -> { val p = products.value[badgeStoreProductId(level, period)] - if (p != null) BadgePrice.Price(compactPrice(p)) else BadgePrice.Unavailable + if (p != null) BadgePrice.Price(if (compact) compactPrice(p) else p.displayPrice) else BadgePrice.Unavailable } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgeUserPreview.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgeUserPreview.kt index 03fabf817e..53c9f98f60 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgeUserPreview.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgeUserPreview.kt @@ -19,7 +19,7 @@ import chat.simplex.res.MR import kotlinx.datetime.Instant @Composable -fun BadgeUserPreview(level: BadgeLevel, modifier: Modifier = Modifier, trailing: @Composable () -> Unit = {}) { +fun BadgeUserPreview(level: BadgeLevel, modifier: Modifier = Modifier) { val user = chatModel.currentUser.value val displayName = user?.displayName ?: stringResource(MR.strings.badges_preview_my_nickname) val previewBadge = LocalBadge( @@ -29,15 +29,12 @@ fun BadgeUserPreview(level: BadgeLevel, modifier: Modifier = Modifier, trailing: ) Column(modifier, horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp)) { ProfileImage(size = 128.dp, image = user?.image) - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) { - NameWithBadge( - name = displayName, - badge = previewBadge, - style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - trailing() - } + NameWithBadge( + name = displayName, + badge = previewBadge, + style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesCheckOrderView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesCheckOrderView.kt index fd60c363fe..bec027c3e4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesCheckOrderView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesCheckOrderView.kt @@ -2,10 +2,13 @@ package chat.simplex.common.views.badges import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -39,20 +42,20 @@ fun BadgesCheckOrderView(level: BadgeLevel, period: BadgePeriod, modalManager: M modifier = Modifier.fillMaxWidth() ) - Column(Modifier.fillMaxWidth().padding(top = 20.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Column( + Modifier + .fillMaxWidth() + .padding(top = 20.dp) + .clip(RoundedCornerShape(16.dp)) + .background(sectionCardColor()) + .padding(horizontal = 24.dp, vertical = 12.dp) + ) { OrderRow(MR.strings.badges_your_badge, stringResource(level.title)) OrderRow(MR.strings.badges_order_duration, stringResource(period.label)) - OrderRow(MR.strings.badges_order_total, period.priceText(BadgeStore.price(level, period))) + Divider(Modifier.padding(top = 8.dp)) + OrderRow(MR.strings.badges_order_total, period.priceText(BadgeStore.price(level, period, compact = false)), total = true) } - Text( - stringResource(MR.strings.badges_charged_by_play), - style = MaterialTheme.typography.body2, - color = MaterialTheme.colors.secondary, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - Spacer(Modifier.weight(1f).heightIn(min = 20.dp)) Column(horizontalAlignment = Alignment.CenterHorizontally) { @@ -63,17 +66,23 @@ fun BadgesCheckOrderView(level: BadgeLevel, period: BadgePeriod, modalManager: M } @Composable -private fun OrderRow(title: StringResource, value: String) { - Row(Modifier.fillMaxWidth()) { - Text(stringResource(title), style = MaterialTheme.typography.body1, color = MaterialTheme.colors.secondary) +private fun OrderRow(title: StringResource, value: String, total: Boolean = false) { + val style = if (total) MaterialTheme.typography.h3 else MaterialTheme.typography.body1 + Row(Modifier.fillMaxWidth().padding(top = if (total) 14.dp else 8.dp, bottom = 8.dp)) { + Text( + stringResource(title), + style = style, + fontWeight = if (total) FontWeight.SemiBold else FontWeight.Medium, + color = if (total) Color.Unspecified else MaterialTheme.colors.secondary + ) Spacer(Modifier.weight(1f)) - Text(value, style = MaterialTheme.typography.body1) + Text(value, style = style, fontWeight = FontWeight.SemiBold) } } @Composable private fun PayButton(level: BadgeLevel, period: BadgePeriod, purchasing: MutableState) { - val price = BadgeStore.price(level, period) + val price = BadgeStore.price(level, period, compact = false) val (labelId, labelArg) = period.payLabel(price) OnboardingActionButton( modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesChooseBadgeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesChooseBadgeView.kt index 51fc6e05e6..22d61d20da 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesChooseBadgeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesChooseBadgeView.kt @@ -34,10 +34,16 @@ enum class BadgeLevel { Legend -> MR.strings.badges_level_legend } - val filesDescription: StringResource + val fileSize: StringResource get() = when (this) { - Supporter -> MR.strings.badges_level_supporter_files - Legend -> MR.strings.badges_level_legend_files + Supporter -> MR.strings.badges_level_supporter_file_size + Legend -> MR.strings.badges_level_legend_file_size + } + + val fileStorage: StringResource + get() = when (this) { + Supporter -> MR.strings.badges_level_supporter_file_storage + Legend -> MR.strings.badges_level_legend_file_storage } val summary: StringResource @@ -83,13 +89,7 @@ fun BadgesChooseBadgeView(modalManager: ModalManager) { modifier = Modifier.fillMaxWidth() ) - BadgeUserPreview(level = selectedLevel, modifier = Modifier.padding(top = 4.dp)) { - Icon( - painterResource(MR.images.ic_keyboard_arrow_down), - contentDescription = null, - tint = MaterialTheme.colors.primary - ) - } + BadgeUserPreview(level = selectedLevel, modifier = Modifier.padding(top = 4.dp)) Spacer(Modifier.weight(1f).heightIn(min = 20.dp)) @@ -144,7 +144,10 @@ private fun LevelCard(level: BadgeLevel, selectedLevel: BadgeLevel, modifier: Mo modifier = Modifier.size(60.dp) ) Text(stringResource(level.title), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center) - Text(stringResource(level.filesDescription), style = MaterialTheme.typography.body2, color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center) + Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text(stringResource(level.fileSize), style = MaterialTheme.typography.body2, color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center) + Text(stringResource(level.fileStorage), style = MaterialTheme.typography.body2, color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center) + } Text(BadgePeriod.Monthly.priceText(BadgeStore.price(level, BadgePeriod.Monthly)), style = MaterialTheme.typography.body1, textAlign = TextAlign.Center) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesHowLongView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesHowLongView.kt index fee8b41b7a..c21a17f6ce 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesHowLongView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesHowLongView.kt @@ -9,7 +9,9 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -42,6 +44,13 @@ enum class BadgePeriod { Annual -> MR.strings.badges_period_annual } + val months: Int + get() = when (this) { + OneMonth -> 1 + Monthly -> 1 + Annual -> 12 + } + @Composable fun priceText(price: BadgePrice): String = when (price) { is BadgePrice.Loading -> "…" @@ -104,7 +113,7 @@ fun BadgesHowLongView(level: BadgeLevel, modalManager: ModalManager) { Modifier.fillMaxWidth().height(IntrinsicSize.Max), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - badgePeriodsForSale.forEach { period -> + BadgePeriod.entries.forEach { period -> PeriodCard(level, period, selectedPeriod, Modifier.weight(1f).fillMaxHeight()) { selectedPeriod = it } } } @@ -124,7 +133,7 @@ fun BadgesHowLongView(level: BadgeLevel, modalManager: ModalManager) { fun BadgeBillingFooter(period: BadgePeriod) { Box(Modifier.padding(top = 7.5.dp, bottom = 7.5.dp).padding(horizontal = 16.dp, vertical = 8.dp)) { Text( - stringResource(billingFooter(period)).format(stubBillingDate()), + stringResource(billingFooter(period)).format(billingDate(period)), Modifier.padding(vertical = 5.dp), style = MaterialTheme.typography.body2, color = MaterialTheme.colors.secondary, @@ -136,6 +145,8 @@ fun BadgeBillingFooter(period: BadgePeriod) { @Composable private fun PeriodCard(level: BadgeLevel, period: BadgePeriod, selectedPeriod: BadgePeriod, modifier: Modifier, onSelect: (BadgePeriod) -> Unit) { val isSelected = period == selectedPeriod + val forSale = period in badgePeriodsForSale + val textColor = if (forSale) Color.Unspecified else MaterialTheme.colors.secondary val borderColor = if (isSelected) MaterialTheme.colors.primary else MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.92f) // Light: transparent so card matches page background. Dark: subtle gray tint for visible contrast. val cardBackground = if (isInDarkTheme()) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f) @@ -146,7 +157,7 @@ private fun PeriodCard(level: BadgeLevel, period: BadgePeriod, selectedPeriod: B .clip(shape) .background(cardBackground, shape) .border(2.dp, borderColor, shape) - .clickable { onSelect(period) } + .clickable(enabled = forSale) { onSelect(period) } .padding(vertical = 20.dp, horizontal = 12.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp) @@ -155,10 +166,10 @@ private fun PeriodCard(level: BadgeLevel, period: BadgePeriod, selectedPeriod: B painterResource(period.icon), contentDescription = null, tint = if (isSelected) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - modifier = Modifier.size(32.dp) + modifier = Modifier.size(32.dp).alpha(if (forSale) 1f else 0.4f) ) - Text(stringResource(period.label), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center) - Text(period.priceText(BadgeStore.price(level, period)), style = MaterialTheme.typography.body1, textAlign = TextAlign.Center) + Text(stringResource(period.label), style = MaterialTheme.typography.body1, color = textColor, textAlign = TextAlign.Center) + Text(period.priceText(BadgeStore.price(level, period)), style = MaterialTheme.typography.h3, fontWeight = FontWeight.SemiBold, color = textColor, textAlign = TextAlign.Center) val percent = savingsPercent(level, period) if (percent != null) { Text( @@ -191,9 +202,10 @@ private fun billingFooter(period: BadgePeriod): StringResource = when (period) { BadgePeriod.OneMonth -> MR.strings.badges_billing_footer_one_month } -// TODO [badges] source the actual date from the purchase state machine when wired. -private fun stubBillingDate(): String { - val date = java.time.LocalDate.of(2026, 7, 22) +// TODO [badges] from now only because a purchase is refused while a badge is held (refuseWhileBadgeHeld); +// a top-up must count from the end of the existing balance +private fun billingDate(period: BadgePeriod): String { + val date = java.time.LocalDate.now().plusMonths(period.months.toLong()) val formatter = java.time.format.DateTimeFormatter.ofLocalizedDate(java.time.format.FormatStyle.LONG) return date.format(formatter) } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index e1ea4f6e3f..193898eede 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -3142,8 +3142,10 @@ The badge is signed with a key that this version of the app does not recognize. Update the app to verify this badge. Supporter Legend - Send 2GB files - Send 5GB files + Files up to 2 GB + Stored for 7 days + Files up to 5 GB + Stored for 21 days %1$s/month %1$s/year Pay %1$s/month @@ -3184,7 +3186,6 @@ Check your order Duration Total - Charged by Google Play to the account on this device. Issuing your badge The store has taken the payment. Keeps trying if you close the app. Waiting for approval