fix: onboarding flow, settings screen polish, iOS settings parity

This commit is contained in:
null 2026-06-21 17:37:14 -05:00
parent e2fd65f070
commit 6b50e84f60
3 changed files with 246 additions and 176 deletions

View File

@ -143,7 +143,7 @@ fun OnboardingScreen(
)
2 -> ValueSlide(
visual = { GrowerVisual() },
headline = "Grow closer",
headline = "Grow Closer",
body = "Every answer opens a real conversation. Not a quiz — a moment."
)
else -> CtaSlide(onNavigate = onNavigate)

View File

@ -100,7 +100,9 @@ fun SettingsSubpage(
)
}
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
)
)
}
) { padding -> content(padding) }
@ -197,7 +199,9 @@ fun SettingsScreen(
color = SettingsInk
)
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
)
)
}
) { padding ->
@ -394,81 +398,72 @@ fun SettingsScreen(
Spacer(Modifier.height(4.dp))
// General section
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = SettingsCard)
) {
Column {
SettingsRow(
icon = Icons.Filled.Done,
label = "Answer History",
onClick = { onNavigate(AppRoute.ANSWER_HISTORY) }
)
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 0.5.dp)
SettingsRow(
icon = Icons.AutoMirrored.Filled.TrendingUp,
label = "Your Progress",
onClick = { onNavigate(AppRoute.YOUR_PROGRESS) }
)
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 0.5.dp)
SettingsRow(
icon = Icons.Filled.Palette,
label = "Appearance",
onClick = { onNavigate(AppRoute.APPEARANCE) }
)
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 0.5.dp)
SettingsRow(
icon = Icons.Filled.Notifications,
label = "Notifications",
onClick = { onNavigate(AppRoute.NOTIFICATIONS) }
)
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 0.5.dp)
SettingsRow(
icon = Icons.Filled.Star,
label = "Subscription",
onClick = { onNavigate(AppRoute.SUBSCRIPTION) }
)
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 0.5.dp)
SettingsRow(
icon = Icons.Filled.Lock,
label = "Privacy & Terms",
onClick = { onNavigate(AppRoute.PRIVACY) }
)
}
SettingsSection(title = "For the two of you") {
SettingsRow(
icon = Icons.Filled.Done,
label = "Answer History",
subtitle = "Revisit the moments you have shared",
onClick = { onNavigate(AppRoute.ANSWER_HISTORY) }
)
SettingsSectionDivider()
SettingsRow(
icon = Icons.AutoMirrored.Filled.TrendingUp,
label = "Your Progress",
subtitle = "See patterns, check-ins, and growth",
onClick = { onNavigate(AppRoute.YOUR_PROGRESS) }
)
}
Spacer(Modifier.height(8.dp))
SettingsSection(title = "Your rhythm") {
SettingsRow(
icon = Icons.Filled.Notifications,
label = "Notifications",
subtitle = "Set gentle reminders that fit your day",
onClick = { onNavigate(AppRoute.NOTIFICATIONS) }
)
SettingsSectionDivider()
SettingsRow(
icon = Icons.Filled.Palette,
label = "Appearance",
subtitle = "Make Closer feel comfortable to open",
onClick = { onNavigate(AppRoute.APPEARANCE) }
)
}
// Security
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = SettingsCard)
) {
SettingsSection(title = "Premium") {
SettingsRow(
icon = Icons.Filled.Favorite,
label = "Subscription",
subtitle = "Manage one plan for both partners",
onClick = { onNavigate(AppRoute.SUBSCRIPTION) },
tint = SettingsPrimaryDeep
)
}
SettingsSection(title = "Privacy and safety") {
SettingsRow(
icon = Icons.Filled.Lock,
label = "Security",
subtitle = "Recovery phrase and account protection",
onClick = { onNavigate(AppRoute.SECURITY) }
)
SettingsSectionDivider()
SettingsRow(
icon = Icons.Filled.Lock,
label = "Privacy & Terms",
subtitle = "Your data, policies, and legal details",
onClick = { onNavigate(AppRoute.PRIVACY) }
)
}
Spacer(Modifier.height(8.dp))
// Account lifecycle
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = SettingsCard)
) {
Column {
SettingsRow(
icon = Icons.Filled.Warning,
label = "Delete account",
onClick = { onNavigate(AppRoute.DELETE_ACCOUNT) },
tint = SettingsDanger
)
}
SettingsSection(title = "Account") {
SettingsRow(
icon = Icons.Filled.Warning,
label = "Delete account",
subtitle = "Permanently remove your Closer account",
onClick = { onNavigate(AppRoute.DELETE_ACCOUNT) },
tint = SettingsDanger
)
}
Spacer(Modifier.height(8.dp))
@ -500,6 +495,7 @@ fun SettingsScreen(
private fun SettingsRow(
icon: ImageVector,
label: String,
subtitle: String? = null,
onClick: () -> Unit,
tint: androidx.compose.ui.graphics.Color = SettingsMuted
) {
@ -507,19 +503,32 @@ private fun SettingsRow(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 14.dp),
.padding(horizontal = 16.dp, vertical = if (subtitle == null) 14.dp else 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(icon, contentDescription = null, tint = tint)
Text(
text = label,
style = MaterialTheme.typography.bodyLarge,
color = if (tint == SettingsMuted) SettingsInk else tint,
Column(
modifier = Modifier.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
Text(
text = label,
style = MaterialTheme.typography.bodyLarge,
color = if (tint == SettingsMuted) SettingsInk else tint,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
subtitle?.let {
Text(
text = it,
style = MaterialTheme.typography.bodySmall,
color = SettingsMuted,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
Icon(
Icons.AutoMirrored.Filled.ArrowForwardIos,
contentDescription = null,

View File

@ -14,132 +14,136 @@ struct SettingsView: View {
var body: some View {
NavigationStack {
List {
// Profile section
Section {
VStack(spacing: CloserSpacing.sm) {
Circle()
.fill(Color.closerPrimary.opacity(0.2))
.frame(width: 72, height: 72)
.overlay(
Text(initials)
.font(CloserFont.title1)
.foregroundColor(.closerPrimary)
if isLoggedInAnonymously {
NavigationLink {
SignUpView()
} label: {
SettingsProfileHeader(
initials: initials,
name: appState.currentUser?.displayName ?? "You",
detail: "Create an account to save your Closer space"
)
Text(appState.currentUser?.displayName ?? "You")
.font(CloserFont.title3)
.foregroundColor(.closerText)
if let email = appState.currentUser?.email {
Text(email)
.font(CloserFont.callout)
.foregroundColor(.closerTextSecondary)
}
}
.frame(maxWidth: .infinity)
.padding(.vertical)
}
// Connection
Section("Connection") {
if let partner = appState.currentPartner {
HStack {
Circle()
.fill(Color.closerSuccess)
.frame(width: 12, height: 12)
Text("Connected with \(partner.displayName ?? "Partner")")
.font(CloserFont.body)
}
} else {
Button(action: { isPairingActive = true }) {
Label("Pair with Partner", systemImage: "link")
}
}
NavigationLink {
CreateInviteView()
} label: {
Label("Invite Partner", systemImage: "square.and.arrow.up")
}
}
// Account
Section("Account") {
if !isLoggedInAnonymously {
NavigationLink {
EditProfileView()
} label: {
Label("Edit Profile", systemImage: "person")
SettingsProfileHeader(
initials: initials,
name: appState.currentUser?.displayName ?? "You",
detail: appState.currentUser?.email ?? "Edit your profile"
)
}
}
}
Section("For the two of you") {
if let partner = appState.currentPartner {
SettingsLinkLabel(
icon: "heart.fill",
title: "Connected with \(partner.displayName ?? "Partner")",
subtitle: "Your shared Closer space is active",
tint: .closerPrimary
)
} else {
Button(action: { isPairingActive = true }) {
SettingsLinkLabel(
icon: "heart",
title: "Invite your partner",
subtitle: "Start answering together",
tint: .closerPrimary
)
}
.buttonStyle(.plain)
}
NavigationLink {
AnswerHistoryView()
} label: {
SettingsLinkLabel(
icon: "checkmark.circle",
title: "Answer History",
subtitle: "Revisit the moments you have shared"
)
}
}
Section("Your rhythm") {
NavigationLink {
NavigationSettingsView()
} label: {
SettingsLinkLabel(
icon: "bell.badge",
title: "Notifications",
subtitle: "Set gentle reminders that fit your day"
)
}
}
Section("Premium") {
Button {
showPaywall = true
} label: {
PremiumSettingsCTA()
}
.disabled(isLoggedInAnonymously)
if isLoggedInAnonymously {
NavigationLink {
SignUpView()
} label: {
Label("Create Account (Save Data)", systemImage: "lock")
}
}
.buttonStyle(.plain)
}
// Notifications
Section("Notifications") {
Toggle("Push Notifications", isOn: .constant(true))
Toggle("Daily Question Reminders", isOn: .constant(true))
Toggle("Partner Activity", isOn: .constant(true))
Toggle("Gentle Reminders", isOn: .constant(true))
NavigationLink {
NavigationSettingsView()
} label: {
Label("Notification Settings", systemImage: "bell.badge")
}
}
// Privacy
Section("Privacy") {
NavigationLink {
Text("Privacy Policy")
} label: {
Label("Privacy Policy", systemImage: "hand.raised")
}
NavigationLink {
Text("Terms of Service")
} label: {
Label("Terms of Service", systemImage: "doc.text")
}
Section("Privacy and safety") {
NavigationLink {
DataExportView()
} label: {
Label("Export Data", systemImage: "square.and.arrow.up")
SettingsLinkLabel(
icon: "square.and.arrow.up",
title: "Export Data",
subtitle: "Download a copy of your Closer data"
)
}
NavigationLink {
Text("Privacy Policy")
} label: {
SettingsLinkLabel(
icon: "hand.raised",
title: "Privacy Policy",
subtitle: "How your data is handled"
)
}
NavigationLink {
Text("Terms of Service")
} label: {
SettingsLinkLabel(
icon: "doc.text",
title: "Terms of Service",
subtitle: "The agreement for using Closer"
)
}
Toggle("Share Anonymous Usage Data", isOn: .constant(true))
}
// Support
Section("Support") {
NavigationLink {
HelpCenterView()
} label: {
Label("Help Center", systemImage: "questionmark.circle")
SettingsLinkLabel(
icon: "questionmark.circle",
title: "Help Center",
subtitle: "Answers for common questions"
)
}
NavigationLink {
Text("Contact Us")
} label: {
Label("Contact Us", systemImage: "envelope")
SettingsLinkLabel(
icon: "envelope",
title: "Contact Us",
subtitle: "Get help from the Closer team"
)
}
HStack {
Label("Version", systemImage: "info.circle")
Spacer()
@ -148,13 +152,12 @@ struct SettingsView: View {
.foregroundColor(.closerTextSecondary)
}
}
// Danger zone
Section {
Section("Account") {
Button(role: .destructive, action: { showLogoutConfirm = true }) {
Label("Sign Out", systemImage: "rectangle.portrait.and.arrow.right")
}
Button(role: .destructive, action: { showDeleteConfirm = true }) {
Label("Delete Account", systemImage: "trash")
}
@ -795,6 +798,64 @@ struct PaywallView: View {
}
}
// MARK: - Settings Row Helpers
struct SettingsProfileHeader: View {
let initials: String
let name: String
let detail: String
var body: some View {
VStack(spacing: CloserSpacing.sm) {
Circle()
.fill(Color.closerPrimary.opacity(0.18))
.frame(width: 72, height: 72)
.overlay(
Text(initials)
.font(CloserFont.title1)
.foregroundColor(.closerPrimary)
)
Text(name)
.font(CloserFont.title3)
.foregroundColor(.closerText)
Text(detail)
.font(CloserFont.callout)
.foregroundColor(.closerTextSecondary)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity)
.padding(.vertical)
}
}
struct SettingsLinkLabel: View {
let icon: String
let title: String
let subtitle: String
var tint: Color = .closerTextSecondary
var body: some View {
HStack(spacing: CloserSpacing.md) {
Image(systemName: icon)
.font(.headline)
.foregroundColor(tint)
.frame(width: 28)
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(CloserFont.body)
.foregroundColor(.closerText)
Text(subtitle)
.font(CloserFont.caption)
.foregroundColor(.closerTextSecondary)
}
}
.padding(.vertical, CloserSpacing.xs)
}
}
// MARK: - Premium Settings CTA
struct PremiumSettingsCTA: View {