2 Commits

Author SHA1 Message Date
83af44002c chore: restructure and update dependencies
Some checks failed
Build and Release / build (push) Has been cancelled
2026-05-24 09:32:25 +07:00
44acbfa6b7 chore: fix app crash when create new room via QR (#3)
Reviewed-on: #3
2026-05-24 01:30:48 +00:00
8 changed files with 68 additions and 38 deletions

View File

@@ -19,15 +19,12 @@ kotlin {
androidMain.dependencies { androidMain.dependencies {
implementation(libs.compose.uiToolingPreview) implementation(libs.compose.uiToolingPreview)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation("androidx.navigation:navigation-compose:2.8.8") implementation(libs.androidx.navigation.compose)
implementation("androidx.datastore:datastore-preferences:1.2.1") implementation(libs.androidx.lifecycle.process)
implementation("androidx.datastore:datastore-preferences-core:1.2.1")
implementation("org.jetbrains.compose.material3:material3:1.11.0-alpha07")
implementation("io.coil-kt.coil3:coil-compose:3.4.0") implementation("io.coil-kt.coil3:coil-compose:3.4.0")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.4.0") implementation("io.coil-kt.coil3:coil-network-okhttp:3.4.0")
implementation("su.reya:nostr-sdk-kmp:0.2.3") implementation("su.reya:nostr-sdk-kmp:0.2.3")
implementation("io.github.kalinjul.easyqrscan:scanner:0.7.0") implementation("io.github.kalinjul.easyqrscan:scanner:0.7.0")
implementation("androidx.lifecycle:lifecycle-process:2.8.0")
implementation("io.github.alexzhirkevich:qrose:1.1.2") implementation("io.github.alexzhirkevich:qrose:1.1.2")
} }
commonMain.dependencies { commonMain.dependencies {
@@ -39,6 +36,8 @@ kotlin {
implementation(libs.compose.uiToolingPreview) implementation(libs.compose.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodelCompose) implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose) implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.datastore)
implementation(projects.shared) implementation(projects.shared)
} }
commonTest.dependencies { commonTest.dependencies {

View File

@@ -15,8 +15,10 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.MotionScheme
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.dynamicLightColorScheme
@@ -100,6 +102,8 @@ fun App() {
MaterialExpressiveTheme( MaterialExpressiveTheme(
colorScheme = colorScheme, colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
) { ) {
CompositionLocalProvider( CompositionLocalProvider(
LocalNostrViewModel provides viewModel, LocalNostrViewModel provides viewModel,

View File

@@ -68,8 +68,19 @@ fun ChatScreen(
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
val viewModel = LocalNostrViewModel.current val viewModel = LocalNostrViewModel.current
val room = viewModel.getChatRoom(id)
val listState = rememberLazyListState() val listState = rememberLazyListState()
val chatRooms by viewModel.chatRooms.collectAsState()
val room = remember(chatRooms, id) { chatRooms.firstOrNull { it.id == id } }
if (room == null) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
LoadingIndicator()
}
return
}
val displayName by remember(room) { room.displayNameFlow(viewModel) }.collectAsState("Loading...") val displayName by remember(room) { room.displayNameFlow(viewModel) }.collectAsState("Loading...")
val picture by remember(room) { room.pictureFlow(viewModel) }.collectAsState(null) val picture by remember(room) { room.pictureFlow(viewModel) }.collectAsState(null)

View File

@@ -58,7 +58,6 @@ 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.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coop.composeapp.generated.resources.Res import coop.composeapp.generated.resources.Res
@@ -85,7 +84,6 @@ fun HomeScreen(
onOpenChat: (Long) -> Unit, onOpenChat: (Long) -> Unit,
onNewChat: () -> Unit, onNewChat: () -> Unit,
) { ) {
val clipboard = LocalClipboard.current
val navController = LocalNavController.current val navController = LocalNavController.current
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
val viewModel = LocalNostrViewModel.current val viewModel = LocalNostrViewModel.current
@@ -112,15 +110,21 @@ fun HomeScreen(
?: remember { mutableStateOf(null) } ?: remember { mutableStateOf(null) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (qrResult == null) {
viewModel.getChatRooms() viewModel.getChatRooms()
} }
}
LaunchedEffect(qrResult) { LaunchedEffect(qrResult) {
qrResult?.let { result -> qrResult?.let { result ->
runCatching { PublicKey.parse(result) } runCatching { PublicKey.parse(result) }
.onSuccess { pubkey -> .onSuccess { pubkey ->
try {
val roomId = viewModel.createChatRoom(listOf(pubkey)) val roomId = viewModel.createChatRoom(listOf(pubkey))
navController.navigate(Screen.Chat(roomId)) navController.navigate(Screen.Chat(roomId))
} catch (e: Exception) {
e.message?.let { snackbarHostState.showSnackbar(it) }
}
} }
.onFailure { e -> println("Failed to parse QR: ${e.message}") } .onFailure { e -> println("Failed to parse QR: ${e.message}") }

View File

@@ -76,7 +76,6 @@ fun ScanScreen(
ScannerWithPermissions( ScannerWithPermissions(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
onScanned = { onScanned = {
println("Scanned: $it");
onResult(it) onResult(it)
true true
}, },

View File

@@ -8,16 +8,19 @@ androidx-appcompat = "1.7.1"
androidx-core = "1.18.0" androidx-core = "1.18.0"
androidx-espresso = "3.7.0" androidx-espresso = "3.7.0"
androidx-lifecycle = "2.10.0" androidx-lifecycle = "2.10.0"
androidx-navigation = "2.8.8" androidx-navigation = "2.9.8"
androidx-testExt = "1.3.0" androidx-testExt = "1.3.0"
composeMultiplatform = "1.10.3" composeMultiplatform = "1.11.0"
datastorePreferences = "1.2.1"
junit = "4.13.2" junit = "4.13.2"
kotlin = "2.3.20" kotlin = "2.3.21"
kotlinx-serialization = "1.8.0" kotlinx-serialization = "1.11.0"
material3 = "1.10.0-alpha05" material3 = "1.11.0-alpha07"
ktor = "3.4.3" ktor = "3.5.0"
[libraries] [libraries]
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastorePreferences" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
junit = { module = "junit:junit", version.ref = "junit" } junit = { module = "junit:junit", version.ref = "junit" }
@@ -31,6 +34,7 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa
compose-uiTooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "composeMultiplatform" } compose-uiTooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "composeMultiplatform" }
androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "androidx-lifecycle" }
compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" } compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" }
compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" } compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" }
compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "material3" } compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "material3" }

View File

@@ -25,15 +25,15 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
implementation("org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0")
implementation("su.reya:nostr-sdk-kmp:0.2.3")
implementation("com.squareup.okio:okio:3.16.2")
implementation(libs.ktor.client.core) implementation(libs.ktor.client.core)
implementation(libs.ktor.client.websockets) implementation(libs.ktor.client.websockets)
implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0")
implementation("su.reya:nostr-sdk-kmp:0.2.3")
implementation("com.squareup.okio:okio:3.16.2")
} }
androidMain.dependencies { androidMain.dependencies {
implementation(libs.ktor.client.okhttp) implementation(libs.ktor.client.okhttp)

View File

@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
@@ -406,6 +407,7 @@ class NostrViewModel(
} }
fun createChatRoom(to: List<PublicKey>): Long { fun createChatRoom(to: List<PublicKey>): Long {
try {
if (nostr.signer.currentUser == null) throw IllegalStateException("User not signed in") if (nostr.signer.currentUser == null) throw IllegalStateException("User not signed in")
if (to.isEmpty()) throw IllegalArgumentException("At least one recipient is required") if (to.isEmpty()) throw IllegalArgumentException("At least one recipient is required")
@@ -417,9 +419,14 @@ class NostrViewModel(
// Create a room from the rumor event // Create a room from the rumor event
val room = Room.new(rumor, nostr.signer.currentUser!!) val room = Room.new(rumor, nostr.signer.currentUser!!)
_chatRooms.value += room _chatRooms.update { currentRooms ->
currentRooms + room
}
return room.id return room.id
} catch (e: Exception) {
throw IllegalArgumentException("Failed to create room: ${e.message}")
}
} }
fun getChatRoom(id: Long): Room { fun getChatRoom(id: Long): Room {
@@ -429,10 +436,12 @@ class NostrViewModel(
fun getChatRooms() { fun getChatRooms() {
viewModelScope.launch { viewModelScope.launch {
try { val rooms = nostr.getChatRooms() ?: emptySet()
_chatRooms.value = nostr.getChatRooms() ?: emptySet() _chatRooms.update { currentRooms ->
} catch (e: Exception) { val virtualRooms = currentRooms.filter { local ->
showError("Error: ${e.message}") rooms.none { db -> db.id == local.id }
}
rooms + virtualRooms
} }
} }
} }