basic home screen

This commit is contained in:
2026-05-03 09:12:43 +07:00
parent 77f4ea71b1
commit e02338fd52
5 changed files with 159 additions and 36 deletions

View File

@@ -1,7 +1,12 @@
package su.reya.coop package su.reya.coop
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -29,11 +34,23 @@ fun App(dbPath: String) {
val secretStore = remember { SecretStore(context) } val secretStore = remember { SecretStore(context) }
val viewModel: NostrViewModel = viewModel { NostrViewModel(nostr, secretStore) } val viewModel: NostrViewModel = viewModel { NostrViewModel(nostr, secretStore) }
val darkMode = isSystemInDarkTheme()
val colorScheme = when {
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S -> {
if (darkMode) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkMode -> darkColorScheme()
else -> lightColorScheme()
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.initAndConnect(dbPath) viewModel.initAndConnect(dbPath)
} }
MaterialExpressiveTheme { MaterialExpressiveTheme(
colorScheme = colorScheme,
) {
rememberCoroutineScope() rememberCoroutineScope()
val navController = rememberNavController() val navController = rememberNavController()
val hasSecret by viewModel.hasSecret.collectAsState(initial = null) val hasSecret by viewModel.hasSecret.collectAsState(initial = null)
@@ -65,7 +82,14 @@ fun App(dbPath: String) {
) )
} }
composable<Screen.Import> { backStackEntry -> composable<Screen.Import> { backStackEntry ->
ImportScreen() val isCreating by viewModel.isCreating.collectAsState()
ImportScreen(
isLoading = isCreating,
onSave = { secret ->
viewModel.import(secret)
}
)
} }
composable<Screen.NewIdentity> { backStackEntry -> composable<Screen.NewIdentity> { backStackEntry ->
val isCreating by viewModel.isCreating.collectAsState() val isCreating by viewModel.isCreating.collectAsState()

View File

@@ -1,26 +1,73 @@
package su.reya.coop.screens package su.reya.coop.screens
import androidx.compose.foundation.layout.Box 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.AppBarWithSearch
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberSearchBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeScreen(onOpenChat: (String) -> Unit) { fun HomeScreen(onOpenChat: (String) -> Unit) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { val scope = rememberCoroutineScope()
Column(horizontalAlignment = Alignment.CenterHorizontally) { val searchState = rememberSearchBarState()
Text("Home Screen") val textState = rememberTextFieldState()
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { onOpenChat("123") }) { val scrollBehavior = SearchBarDefaults.enterAlwaysSearchBarScrollBehavior()
Text("Open Chat 123")
} val inputField =
@Composable {
SearchBarDefaults.InputField(
textFieldState = textState,
searchBarState = searchState,
onSearch = { scope.launch { searchState.animateToCollapsed() } },
placeholder = {
Text(modifier = Modifier.clearAndSetSemantics() {}, text = "Search")
},
)
} }
}
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
AppBarWithSearch(
state = searchState,
inputField = inputField,
scrollBehavior = scrollBehavior,
)
},
content = { innerPadding ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
) {
items(count = 100) { index ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
) {
Text("Chat $index")
}
}
}
},
)
} }

View File

@@ -1,15 +1,63 @@
package su.reya.coop.screens package su.reya.coop.screens
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun ImportScreen() { fun ImportScreen(
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { isLoading: Boolean,
Text("Import Screen") onSave: (secret: String) -> Unit
) {
var secret by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
OutlinedTextField(
value = secret,
onValueChange = { secret = it },
label = { Text("Enter nsec or bunker") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = {
onSave(secret)
},
modifier = Modifier.fillMaxWidth(),
enabled = secret.isNotBlank() && !isLoading,
) {
if (isLoading) {
LoadingIndicator()
} else {
Text("Save & Continue")
}
}
} }
} }

View File

@@ -56,23 +56,23 @@ class Nostr {
} }
suspend fun connect() { suspend fun connect() {
client?.addRelay( try {
url = RelayUrl.parse("wss://relay.damus.io"), client?.addRelay(
capabilities = RelayCapabilities.none() url = RelayUrl.parse("wss://relay.primal.net"),
) capabilities = RelayCapabilities.none()
client?.addRelay( )
url = RelayUrl.parse("wss://relay.primal.net"), client?.addRelay(
capabilities = RelayCapabilities.none() url = RelayUrl.parse("wss://user.kindpag.es"),
) capabilities = RelayCapabilities.none()
client?.addRelay( )
url = RelayUrl.parse("wss://user.kindpag.es"), client?.addRelay(
capabilities = RelayCapabilities.none() url = RelayUrl.parse("wss://indexer.coracle.social"),
) capabilities = RelayCapabilities.gossip()
client?.addRelay( )
url = RelayUrl.parse("https://indexer.coracle.social"), client?.connect()
capabilities = RelayCapabilities.gossip() } catch (e: Exception) {
) println("Failed to connect to relays: ${e.message}")
client?.connect() }
} }
suspend fun disconnect() { suspend fun disconnect() {

View File

@@ -45,7 +45,7 @@ class NostrViewModel(
// Connect to bootstrap relays // Connect to bootstrap relays
nostr.connect() nostr.connect()
// Get user's signer secret // Get user's signer secret
val secret = secretStore.get("user_signer") val secret = secretStore.get("user_signer")
@@ -119,6 +119,10 @@ class NostrViewModel(
} }
} }
fun import(secret: String) {
// TODO: Implement import
}
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
// Ensure all relays are disconnect // Ensure all relays are disconnect