11 · Android
android/app/src/main/kotlin/com/sophax/sophaxchat/ui/onboarding/OnboardingScreen.kt
159 lines · Kotlin
1package com.sophax.sophaxchat.ui.onboarding
2
3import androidx.compose.foundation.ExperimentalFoundationApi
4import androidx.compose.foundation.background
5import androidx.compose.foundation.layout.*
6import androidx.compose.foundation.pager.HorizontalPager
7import androidx.compose.foundation.pager.rememberPagerState
8import androidx.compose.foundation.shape.CircleShape
9import androidx.compose.foundation.shape.RoundedCornerShape
10import androidx.compose.foundation.text.KeyboardActions
11import androidx.compose.foundation.text.KeyboardOptions
12import androidx.compose.material3.*
13import androidx.compose.runtime.*
14import androidx.compose.ui.Alignment
15import androidx.compose.ui.Modifier
16import androidx.compose.ui.draw.clip
17import androidx.compose.ui.graphics.Color
18import androidx.compose.ui.text.font.FontWeight
19import androidx.compose.ui.text.input.ImeAction
20import androidx.compose.ui.text.style.TextAlign
21import androidx.compose.ui.unit.dp
22import androidx.compose.ui.unit.sp
23import kotlinx.coroutines.launch
24
25private data class OnboardingPage(
26 val icon: String,
27 val iconColor: Color,
28 val title: String,
29 val body: String
30)
31
32private val pages = listOf(
33 OnboardingPage("🔒", Color(0xFF007AFF), "Private by design",
34 "No servers. No accounts. Messages travel directly between devices, encrypted end-to-end."),
35 OnboardingPage("📡", Color(0xFF34C759), "Find people nearby",
36 "Open the app on the same WiFi or Bluetooth range. Nearby devices appear instantly."),
37 OnboardingPage("🌐", Color(0xFFFF9500), "Chat globally",
38 "Enable Tor in Settings to reach anyone in the world — no phone number, no VPN."),
39 OnboardingPage("⚠️", Color(0xFFFF9500), "Your keys, your device",
40 "Your identity and messages are stored only on this device. No backup exists — if you lose your phone, your history cannot be recovered."),
41 OnboardingPage("🛡️", Color(0xFF34C759), "Verify your contacts",
42 "Always compare Safety Numbers with people you trust. Tap the shield icon in any conversation to confirm you're talking to the right person."),
43 OnboardingPage("👤", Color(0xFF007AFF), "Choose your name",
44 "")
45)
46
47@OptIn(ExperimentalFoundationApi::class)
48@Composable
49fun OnboardingScreen(onComplete: (username: String) -> Unit) {
50 val pagerState = rememberPagerState(pageCount = { pages.size })
51 val scope = rememberCoroutineScope()
52 var username by remember { mutableStateOf("") }
53 val isLastPage = pagerState.currentPage == pages.size - 1
54
55 Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
56
57 HorizontalPager(state = pagerState, modifier = Modifier.fillMaxSize()) { pageIndex ->
58 val page = pages[pageIndex]
59 Column(
60 modifier = Modifier
61 .fillMaxSize()
62 .padding(horizontal = 40.dp),
63 horizontalAlignment = Alignment.CenterHorizontally,
64 verticalArrangement = Arrangement.Center
65 ) {
66 // Icon circle
67 Box(
68 modifier = Modifier
69 .size(100.dp)
70 .clip(CircleShape)
71 .background(page.iconColor.copy(alpha = 0.12f)),
72 contentAlignment = Alignment.Center
73 ) {
74 Text(page.icon, fontSize = 48.sp)
75 }
76
77 Spacer(Modifier.height(32.dp))
78
79 Text(
80 page.title,
81 style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold),
82 textAlign = TextAlign.Center,
83 color = MaterialTheme.colorScheme.onBackground
84 )
85
86 Spacer(Modifier.height(16.dp))
87
88 if (page.body.isNotEmpty()) {
89 Text(
90 page.body,
91 style = MaterialTheme.typography.bodyLarge,
92 textAlign = TextAlign.Center,
93 color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
94 lineHeight = 26.sp
95 )
96 }
97
98 // Username field on last page
99 if (pageIndex == pages.size - 1) {
100 Spacer(Modifier.height(32.dp))
101 OutlinedTextField(
102 value = username,
103 onValueChange = { if (it.length <= 64) username = it },
104 label = { Text("Username") },
105 placeholder = { Text("e.g. alice") },
106 singleLine = true,
107 modifier = Modifier.fillMaxWidth(),
108 shape = RoundedCornerShape(12.dp),
109 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
110 keyboardActions = KeyboardActions(
111 onDone = { if (username.isNotBlank()) onComplete(username.trim()) }
112 )
113 )
114 Spacer(Modifier.height(24.dp))
115 Button(
116 onClick = { if (username.isNotBlank()) onComplete(username.trim()) },
117 enabled = username.isNotBlank(),
118 modifier = Modifier.fillMaxWidth().height(52.dp),
119 shape = RoundedCornerShape(14.dp)
120 ) {
121 Text("Start Chatting", fontWeight = FontWeight.SemiBold, fontSize = 17.sp)
122 }
123 }
124 }
125 }
126
127 // Page dots
128 Row(
129 modifier = Modifier
130 .align(Alignment.BottomCenter)
131 .padding(bottom = 48.dp),
132 horizontalArrangement = Arrangement.spacedBy(8.dp)
133 ) {
134 repeat(pages.size) { i ->
135 Box(
136 modifier = Modifier
137 .size(if (i == pagerState.currentPage) 10.dp else 7.dp)
138 .clip(CircleShape)
139 .background(
140 if (i == pagerState.currentPage)
141 MaterialTheme.colorScheme.primary
142 else
143 MaterialTheme.colorScheme.primary.copy(alpha = 0.3f)
144 )
145 )
146 }
147 }
148
149 // Skip button (pages 1–3)
150 if (!isLastPage) {
151 TextButton(
152 onClick = { scope.launch { pagerState.scrollToPage(pages.size - 1) } },
153 modifier = Modifier.align(Alignment.TopEnd).padding(top = 16.dp, end = 16.dp)
154 ) {
155 Text("Skip", color = MaterialTheme.colorScheme.primary)
156 }
157 }
158 }
159}