add simple create identity flow

This commit is contained in:
2026-04-27 12:14:54 +07:00
parent 3240382498
commit feffda519f
14 changed files with 301 additions and 116 deletions

View File

@@ -1,6 +1,7 @@
package su.reya.coop
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -15,7 +16,13 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import kotlinx.coroutines.flow.flow
import su.reya.coop.coop.storage.SecretStore
import su.reya.coop.screens.ChatScreen
import su.reya.coop.screens.HomeScreen
import su.reya.coop.screens.ImportScreen
import su.reya.coop.screens.NewIdentityScreen
import su.reya.coop.screens.OnboardingScreen
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun App(dbPath: String) {
val context = LocalContext.current
@@ -27,7 +34,7 @@ fun App(dbPath: String) {
viewModel.initAndConnect(dbPath)
}
MaterialTheme {
MaterialExpressiveTheme {
rememberCoroutineScope()
val navController = rememberNavController()
@@ -41,24 +48,32 @@ fun App(dbPath: String) {
if (hasSecret == null) {
// Loading state
return@MaterialTheme
return@MaterialExpressiveTheme
}
NavHost(
navController = navController,
startDestination = if (hasSecret == true) Screen.Onboarding else Screen.Home
startDestination = if (hasSecret == true) Screen.Home else Screen.Onboarding
) {
composable<Screen.Onboarding> { backStackEntry ->
OnboardingScreen(
onOpenImport = { navController.navigate(Screen.Import) },
onOpenNew = { navController.navigate(Screen.New) }
onOpenNew = { navController.navigate(Screen.NewIdentity) }
)
}
composable<Screen.Import> { backStackEntry ->
ImportScreen()
}
composable<Screen.New> { backStackEntry ->
NewScreen()
composable<Screen.NewIdentity> { backStackEntry ->
val isCreating by viewModel.isCreating.collectAsState()
NewIdentityScreen(
isLoading = isCreating,
onSave = { name, bio, uri ->
viewModel.createIdentity(name, bio, uri?.toString())
navController.navigate(Screen.Home)
}
)
}
composable<Screen.Home> { backStackEntry ->
HomeScreen(

View File

@@ -4,8 +4,6 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import java.io.File
class MainActivity : ComponentActivity() {
@@ -22,9 +20,3 @@ class MainActivity : ComponentActivity() {
}
}
}
@Preview
@Composable
fun AppAndroidPreview() {
App()
}

View File

@@ -0,0 +1,20 @@
package su.reya.coop
import kotlinx.serialization.Serializable
sealed interface Screen {
@Serializable
data object Home : Screen
@Serializable
data class Chat(val id: String) : Screen
@Serializable
data object Onboarding : Screen
@Serializable
data object Import : Screen
@Serializable
data object NewIdentity : Screen
}

View File

@@ -1,82 +0,0 @@
package su.reya.coop
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.serialization.Serializable
sealed interface Screen {
@Serializable
data object Home : Screen
@Serializable
data class Chat(val id: String) : Screen
@Serializable
data object Onboarding : Screen
@Serializable
data object Import : Screen
@Serializable
data object New : Screen
}
@Composable
fun HomeScreen(onOpenChat: (String) -> Unit) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Home Screen")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { onOpenChat("123") }) {
Text("Open Chat 123")
}
}
}
}
@Composable
fun ChatScreen(id: String) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Chat Screen (ID: $id)")
}
}
@Composable
fun OnboardingScreen(onOpenImport: () -> Unit, onOpenNew: () -> Unit) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Onboarding Screen")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onOpenImport) {
Text("Import")
}
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = onOpenNew) {
Text("New")
}
}
}
}
@Composable
fun ImportScreen() {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Import Screen")
}
}
@Composable
fun NewScreen() {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("New Screen")
}
}

View File

@@ -0,0 +1,15 @@
package su.reya.coop.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun ChatScreen(id: String) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Chat Screen (ID: $id)")
}
}

View File

@@ -0,0 +1,26 @@
package su.reya.coop.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun HomeScreen(onOpenChat: (String) -> Unit) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Home Screen")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { onOpenChat("123") }) {
Text("Open Chat 123")
}
}
}
}

View File

@@ -0,0 +1,15 @@
package su.reya.coop.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun ImportScreen() {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Import Screen")
}
}

View File

@@ -0,0 +1,118 @@
package su.reya.coop.screens
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun NewIdentityScreen(
isLoading: Boolean,
onSave: (name: String, bio: String, picture: Uri?) -> Unit
) {
var name by remember { mutableStateOf("") }
var bio by remember { mutableStateOf("") }
var picture by remember { mutableStateOf<Uri?>(null) }
val launcher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri: Uri? ->
picture = uri
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "New Identity",
style = MaterialTheme.typography.headlineMediumEmphasized
)
Box(
modifier = Modifier
.size(120.dp)
.clip(CircleShape),
contentAlignment = Alignment.Center
) {
if (picture != null) {
AsyncImage(
model = picture,
contentDescription = "Profile picture",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
} else {
Surface(
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier.fillMaxSize()
) {
//
}
}
}
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)
OutlinedTextField(
value = bio,
onValueChange = { bio = it },
label = { Text("Bio:") },
modifier = Modifier
.fillMaxWidth()
.height(150.dp),
minLines = 3,
)
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = {
onSave(name, bio, picture)
},
modifier = Modifier.fillMaxWidth(),
enabled = name.isNotBlank() && !isLoading,
) {
if (isLoading) {
LoadingIndicator()
} else {
Text("Save & Continue")
}
}
}
}

View File

@@ -0,0 +1,30 @@
package su.reya.coop.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun OnboardingScreen(onOpenImport: () -> Unit, onOpenNew: () -> Unit) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Onboarding Screen")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onOpenImport) {
Text("Import")
}
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = onOpenNew) {
Text("New")
}
}
}
}