From 218978281921dcfcfe7fee08003f4c72ed320dd7 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:54:17 +0400 Subject: [PATCH] wip --- .../Shared/Views/Badges/BadgesPayView.swift | 22 ++-- .../Badges/BadgesSupportSimplexView.swift | 38 ++++-- .../Views/Badges/BadgesYourLevelView.swift | 12 +- .../Views/Badges/SupportSimpleXBanner.swift | 3 +- .../common/views/badges/BadgesPayView.kt | 31 +++-- .../views/badges/BadgesSupportSimplexView.kt | 26 ++-- .../views/badges/BadgesYourLevelView.kt | 36 +++--- .../views/badges/SupportSimpleXBanner.kt | 115 ++++++++++-------- 8 files changed, 153 insertions(+), 130 deletions(-) diff --git a/apps/ios/Shared/Views/Badges/BadgesPayView.swift b/apps/ios/Shared/Views/Badges/BadgesPayView.swift index 6ae10f4c7d..0f86849f72 100644 --- a/apps/ios/Shared/Views/Badges/BadgesPayView.swift +++ b/apps/ios/Shared/Views/Badges/BadgesPayView.swift @@ -53,7 +53,7 @@ struct BadgesPayView: View { Text(level.tagline) .font(.body) - .foregroundColor(theme.colors.secondary) + .foregroundColor(theme.colors.onBackground) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .padding(.top, 4) @@ -66,15 +66,17 @@ struct BadgesPayView: View { Spacer(minLength: 20) - payButton() - - Text(billingFooter) - .font(.footnote) - .foregroundColor(theme.colors.secondary) - .multilineTextAlignment(.center) - .fixedSize(horizontal: false, vertical: true) - .padding(.top, 4) - .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) + VStack(spacing: 10) { + payButton() + .padding(.vertical, 10) + Text(billingFooter) + .font(.footnote) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + .frame(height: 22) + } + .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) } .padding(.horizontal, 25) .padding(.top, 8) diff --git a/apps/ios/Shared/Views/Badges/BadgesSupportSimplexView.swift b/apps/ios/Shared/Views/Badges/BadgesSupportSimplexView.swift index c64635c707..45211ef216 100644 --- a/apps/ios/Shared/Views/Badges/BadgesSupportSimplexView.swift +++ b/apps/ios/Shared/Views/Badges/BadgesSupportSimplexView.swift @@ -14,7 +14,7 @@ import SimpleXChat struct BadgesSupportSimplexView: View { @EnvironmentObject var theme: AppTheme @Environment(\.colorScheme) var colorScheme: ColorScheme - @State private var showWhySimpleX = false + @State private var whyBuiltActive = false @State private var chooseLevelActive = false @State private var redeemCodeActive = false @@ -34,10 +34,7 @@ struct BadgesSupportSimplexView: View { .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) - Button { showWhySimpleX = true } label: { - Label("Why SimpleX is built.", systemImage: "info.circle") - .font(.headline) - } + whyBuiltButton() Spacer(minLength: 0) @@ -47,11 +44,14 @@ struct BadgesSupportSimplexView: View { Spacer(minLength: 0) - chooseLevelButton() - - redeemCodeButton() - .padding(.top, 4) - .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) + // Onboarding pattern: nested VStack(spacing: 10) + action button vertical padding 10. + VStack(spacing: 10) { + chooseLevelButton() + .padding(.vertical, 10) + redeemCodeButton() + .frame(height: 22) + } + .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) } .padding(.horizontal, 25) .padding(.top, 28) @@ -63,8 +63,22 @@ struct BadgesSupportSimplexView: View { .frame(maxHeight: .infinity) .modifier(ThemedBackground()) .navigationBarHidden(true) - .sheet(isPresented: $showWhySimpleX) { - WhySimpleX(onboarding: false, titleColor: theme.colors.primary, createProfileNavLinkActive: .constant(false)) + } + + private func whyBuiltButton() -> some View { + ZStack { + Button { whyBuiltActive = true } label: { + Label("Why SimpleX is built.", systemImage: "info.circle") + .font(.headline) + } + NavigationLink(isActive: $whyBuiltActive) { + WhySimpleX(onboarding: false, titleColor: theme.colors.primary, createProfileNavLinkActive: .constant(false)) + .modifier(ThemedBackground()) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() } } diff --git a/apps/ios/Shared/Views/Badges/BadgesYourLevelView.swift b/apps/ios/Shared/Views/Badges/BadgesYourLevelView.swift index b4a1caf57e..63231b133d 100644 --- a/apps/ios/Shared/Views/Badges/BadgesYourLevelView.swift +++ b/apps/ios/Shared/Views/Badges/BadgesYourLevelView.swift @@ -113,11 +113,13 @@ struct BadgesYourLevelView: View { Spacer(minLength: 20) - continueButton() - - howItWorksButton() - .padding(.top, 4) - .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) + VStack(spacing: 10) { + continueButton() + .padding(.vertical, 10) + howItWorksButton() + .frame(height: 22) + } + .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) } .padding(.horizontal, 25) .padding(.top, 8) diff --git a/apps/ios/Shared/Views/Badges/SupportSimpleXBanner.swift b/apps/ios/Shared/Views/Badges/SupportSimpleXBanner.swift index 7fc9bb7f74..0060d0e144 100644 --- a/apps/ios/Shared/Views/Badges/SupportSimpleXBanner.swift +++ b/apps/ios/Shared/Views/Badges/SupportSimpleXBanner.swift @@ -53,7 +53,8 @@ struct SupportSimpleXBanner: View { } .padding(.leading, cardLeadingPadding) .padding(.trailing, cardTrailingPadding) - .frame(height: cardHeight) + .padding(.vertical, 12) + .frame(minHeight: cardHeight) .background(gradientBackground()) .clipShape(RoundedRectangle(cornerRadius: cardCornerRadius)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesPayView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesPayView.kt index 8d3e2f780c..8bf23a3f60 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesPayView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesPayView.kt @@ -46,7 +46,7 @@ fun BadgesPayView(level: BadgeLevel) { var selectedPeriod by remember { mutableStateOf(BadgePeriod.Subscribe) } ColumnWithScrollBar( - Modifier.padding(horizontal = 25.dp).padding(top = 8.dp, bottom = 20.dp), + Modifier.background(MaterialTheme.colors.background).padding(horizontal = 25.dp).padding(top = 8.dp, bottom = 20.dp), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, maxIntrinsicSize = true, @@ -65,7 +65,7 @@ fun BadgesPayView(level: BadgeLevel) { Text( stringResource(level.tagline), style = MaterialTheme.typography.body1, - color = MaterialTheme.colors.secondary, + color = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().padding(top = 4.dp) ) @@ -80,15 +80,20 @@ fun BadgesPayView(level: BadgeLevel) { Spacer(Modifier.weight(1f).heightIn(min = 20.dp)) - PayButton(level, selectedPeriod) - - Text( - stringResource(billingFooter(selectedPeriod)), - style = MaterialTheme.typography.body2, - color = MaterialTheme.colors.secondary, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth().padding(top = 4.dp) - ) + // Plain Text (not TextButtonBelowOnboardingButton) because the billing footer is informational, + // not an action — using TextButtonBelowOnboardingButton would force a Medium-weight bold look. + // 15.5dp top padding matches the visual gap the TextButtonBelowOnboardingButton produces on + // the other two badges views (7.5dp Modifier + 8dp TextButton chip inner padding). + Column(horizontalAlignment = Alignment.CenterHorizontally) { + PayButton(level, selectedPeriod) + Text( + stringResource(billingFooter(selectedPeriod)), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth().padding(top = 15.5.dp) + ) + } } } @@ -103,7 +108,7 @@ private fun PeriodCard(period: BadgePeriod, selectedPeriod: BadgePeriod, modifie .background(MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f), shape) .border(2.dp, borderColor, shape) .clickable { onSelect(period) } - .padding(vertical = 20.dp), + .padding(vertical = 20.dp, horizontal = 12.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp) ) { @@ -113,7 +118,7 @@ private fun PeriodCard(period: BadgePeriod, selectedPeriod: BadgePeriod, modifie tint = if (isSelected) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, modifier = Modifier.size(32.dp) ) - Text(stringResource(period.label), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold) + Text(stringResource(period.label), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesSupportSimplexView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesSupportSimplexView.kt index 7e4df634e1..fa62f18d08 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesSupportSimplexView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesSupportSimplexView.kt @@ -27,6 +27,7 @@ import chat.simplex.common.views.newchat.gradientPoints import chat.simplex.common.views.newchat.lightStops import chat.simplex.common.views.onboarding.HowItWorks import chat.simplex.common.views.onboarding.OnboardingActionButton +import chat.simplex.common.views.onboarding.TextButtonBelowOnboardingButton import chat.simplex.res.MR // Entry point for badges management. Subsequent screens push via ModalManager; the enclosing @@ -35,7 +36,7 @@ import chat.simplex.res.MR fun BadgesSupportSimplexView() { // TODO [badges] gate on user badge status (no badge → this view, active → "Manage your badge") ColumnWithScrollBar( - Modifier.padding(horizontal = 25.dp).padding(top = 28.dp, bottom = 20.dp), + Modifier.background(MaterialTheme.colors.background).padding(horizontal = 25.dp).padding(top = 28.dp, bottom = 20.dp), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, maxIntrinsicSize = true, @@ -72,9 +73,13 @@ fun BadgesSupportSimplexView() { Spacer(Modifier.weight(1f)) - ChooseLevelButton() - - RedeemCodeButton(Modifier.padding(top = 4.dp)) + Column(horizontalAlignment = Alignment.CenterHorizontally) { + ChooseLevelButton() + TextButtonBelowOnboardingButton( + text = stringResource(MR.strings.badges_redeem_code_button), + onClick = { ModalManager.start.showModal { BadgesRedeemCodeView() } } + ) + } } } @@ -90,19 +95,6 @@ private fun ChooseLevelButton() { ) } -@Composable -private fun RedeemCodeButton(modifier: Modifier = Modifier) { - TextButton( - onClick = { ModalManager.start.showModal { BadgesRedeemCodeView() } }, - modifier = modifier - ) { - Text( - stringResource(MR.strings.badges_redeem_code_button), - color = MaterialTheme.colors.primary, - fontWeight = FontWeight.Medium - ) - } -} // Hero image reused across badges views and WhatsNewView v7.1. Fallback (no SIMPLEX_ASSETS) is a // gradient card carrying the small supporter badge glyph. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesYourLevelView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesYourLevelView.kt index b3f34ae125..2f5b0b3706 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesYourLevelView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesYourLevelView.kt @@ -20,6 +20,7 @@ import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.OnboardingActionButton +import chat.simplex.common.views.onboarding.TextButtonBelowOnboardingButton import chat.simplex.res.MR // Draft levels used by the badges UI while the API/state machine is still being designed. TODO [badges]: @@ -88,7 +89,7 @@ fun BadgesYourLevelView() { var selectedLevel by remember { mutableStateOf(BadgeLevel.Supporter) } ColumnWithScrollBar( - Modifier.padding(horizontal = 25.dp).padding(top = 8.dp, bottom = 20.dp), + Modifier.background(MaterialTheme.colors.background).padding(horizontal = 25.dp).padding(top = 8.dp, bottom = 20.dp), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, maxIntrinsicSize = true, @@ -120,9 +121,16 @@ fun BadgesYourLevelView() { Spacer(Modifier.weight(1f).heightIn(min = 20.dp)) - ContinueButton(selectedLevel) - - HowItWorksButton(Modifier.padding(top = 4.dp)) + // Nested Column with no spacing so the TextButtonBelowOnboardingButton sits directly under + // the action button (matches onboarding pattern where its own 7.5dp top padding is the gap). + Column(horizontalAlignment = Alignment.CenterHorizontally) { + ContinueButton(selectedLevel) + TextButtonBelowOnboardingButton( + text = stringResource(MR.strings.badges_how_it_works_button), + icon = painterResource(MR.images.ic_info), + onClick = { ModalManager.start.showModal { BadgesHowItWorksView() } } + ) + } } } @@ -137,7 +145,7 @@ private fun LevelCard(level: BadgeLevel, selectedLevel: BadgeLevel, modifier: Mo .background(MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f), shape) .border(2.dp, borderColor, shape) .clickable { onSelect(level) } - .padding(vertical = 20.dp), + .padding(vertical = 20.dp, horizontal = 12.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(10.dp) ) { @@ -147,9 +155,9 @@ private fun LevelCard(level: BadgeLevel, selectedLevel: BadgeLevel, modifier: Mo contentScale = ContentScale.Fit, modifier = Modifier.size(60.dp) ) - Text(stringResource(level.title), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold) - Text(stringResource(level.filesDescription), style = MaterialTheme.typography.body2, color = MaterialTheme.colors.secondary) - Text(stringResource(level.monthlyPrice), style = MaterialTheme.typography.body1) + 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) + Text(stringResource(level.monthlyPrice), style = MaterialTheme.typography.body1, textAlign = TextAlign.Center) } } @@ -165,15 +173,3 @@ private fun ContinueButton(selectedLevel: BadgeLevel) { ) } -@Composable -private fun HowItWorksButton(modifier: Modifier = Modifier) { - TextButton( - onClick = { ModalManager.start.showModal { BadgesHowItWorksView() } }, - modifier = modifier - ) { - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) { - Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.primary) - Text(stringResource(MR.strings.badges_how_it_works_button), color = MaterialTheme.colors.primary, fontWeight = FontWeight.Medium) - } - } -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/SupportSimpleXBanner.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/SupportSimpleXBanner.kt index ffeeeeef18..ab8d73a344 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/SupportSimpleXBanner.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/SupportSimpleXBanner.kt @@ -32,9 +32,10 @@ import chat.simplex.res.MR @Composable fun SupportSimpleXBanner(onTap: () -> Unit, onDismiss: () -> Unit) { val cardCornerRadius = 16.dp - // grows with system font but never shrinks below the default so small-font users see the same - // banner as today; hero image stays fixed so its above-card overhang shrinks at very large fonts - val cardHeight = (72.dp * fontSizeSqrtMultiplier).coerceAtLeast(72.dp) + // grows linearly with system font but never shrinks below the default so small-font users see the + // same baseline; the card Row uses heightIn(min = cardHeight) and grows further when 2-line text + // wraps at very large fonts. Hero stays fixed so its above-card overhang shrinks at very large fonts. + val cardHeight = (72.dp * fontSizeMultiplier).coerceAtLeast(72.dp) // matches OneHandUICard's segment icon leading so the text aligns with it in the list val cardLeadingPadding = 16.dp val cardTrailingPadding = 8.dp @@ -49,36 +50,66 @@ fun SupportSimpleXBanner(onTap: () -> Unit, onDismiss: () -> Unit) { var cardSize by remember { mutableStateOf(IntSize.Zero) } val brush = remember(isDark, cardSize) { gradientBrush(isDark, cardSize) } - // Root Box is sized to the card; hero and X are children so they can render outside the card's - // rounded-corner clip. Hero anchors bottom-end and its 108dp height extends 36dp above the card top. - Box(Modifier.fillMaxWidth().height(cardHeight)) { - Row( - Modifier - .fillMaxSize() - .clip(RoundedCornerShape(cardCornerRadius)) - .background(brush) - .clickable(onClick = onTap) - .onSizeChanged { cardSize = it } - .padding(start = cardLeadingPadding, end = cardTrailingPadding + heroWidth + heroTrailingPadding + textToHeroGap), - verticalAlignment = Alignment.CenterVertically - ) { - Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - Text( - generalGetString(MR.strings.badges_banner_title), - style = MaterialTheme.typography.body1, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colors.primary, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - Text( - generalGetString(MR.strings.badges_banner_subtitle), - style = MaterialTheme.typography.body2, - color = MaterialTheme.colors.onBackground, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) + // Two-Box structure so text can grow (2-line wrapping at large fonts) while X stays anchored to + // the card's top-right, not the outer wrapper's top-right (which would drift up into hero overhang). + // Outer Box takes max(inner card, hero); inner Box wraps the card Row + X together and aligns to + // Outer bottom so hero overhangs above. + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.BottomStart) { + Box(Modifier.fillMaxWidth()) { + Row( + Modifier + .fillMaxWidth() + .heightIn(min = cardHeight) + .clip(RoundedCornerShape(cardCornerRadius)) + .background(brush) + .clickable(onClick = onTap) + .onSizeChanged { cardSize = it } + .padding( + start = cardLeadingPadding, + end = cardTrailingPadding + heroWidth + heroTrailingPadding + textToHeroGap, + top = 12.dp, + bottom = 12.dp + ), + verticalAlignment = Alignment.CenterVertically + ) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text( + generalGetString(MR.strings.badges_banner_title), + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colors.primary, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + Text( + generalGetString(MR.strings.badges_banner_subtitle), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onBackground, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } } + + // Same X pattern as OneHandUICard: circle-clipped clickable region with inner padding for hit area. + Icon( + painterResource(MR.images.ic_close), + contentDescription = generalGetString(MR.strings.icon_descr_close_button), + tint = MaterialTheme.colors.secondary, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(end = 4.dp, top = 4.dp) + .clip(CircleShape) + .clickable { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.badges_banner_title), + text = generalGetString(MR.strings.badges_banner_dismiss_message), + onConfirm = onDismiss + ) + } + .padding(8.dp) + .size(16.dp) + ) } HeroThumbnail( @@ -88,26 +119,6 @@ fun SupportSimpleXBanner(onTap: () -> Unit, onDismiss: () -> Unit) { trailingPadding = heroTrailingPadding, modifier = Modifier.align(Alignment.BottomEnd) ) - - // Same X pattern as OneHandUICard: circle-clipped clickable region with inner padding for hit area. - Icon( - painterResource(MR.images.ic_close), - contentDescription = generalGetString(MR.strings.icon_descr_close_button), - tint = MaterialTheme.colors.secondary, - modifier = Modifier - .align(Alignment.TopEnd) - .padding(end = 4.dp, top = 4.dp) - .clip(CircleShape) - .clickable { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.badges_banner_title), - text = generalGetString(MR.strings.badges_banner_dismiss_message), - onConfirm = onDismiss - ) - } - .padding(8.dp) - .size(16.dp) - ) } }