fix: onboarding flow, settings screen polish, iOS settings parity
This commit is contained in:
parent
e3f8b99994
commit
cc445b74ca
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue