From cab12da4a5c21eae6f66ef92f926df96b9b45605 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Thu, 28 May 2026 08:55:01 +0700 Subject: [PATCH] add notification --- .../androidMain/kotlin/su/reya/coop/App.kt | 8 ++++- .../kotlin/su/reya/coop/MainActivity.kt | 4 ++- .../su/reya/coop/NostrForegroundService.kt | 21 +++++++++-- .../kotlin/su/reya/coop/screens/ChatScreen.kt | 35 +++++++++++++------ .../kotlin/su/reya/coop/NostrViewModel.kt | 16 ++++++++- 5 files changed, 67 insertions(+), 17 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt index e8628c0..dbb56bf 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt @@ -72,7 +72,7 @@ val LocalNavController = staticCompositionLocalOf { @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable -fun App() { +fun App(openRoomId: Long? = null) { val context = LocalContext.current val navController = rememberNavController() val scope = rememberCoroutineScope() @@ -101,6 +101,12 @@ fun App() { } } + LaunchedEffect(openRoomId) { + if (openRoomId != null) { + navController.navigate(Screen.Chat(openRoomId)) + } + } + MaterialExpressiveTheme( colorScheme = colorScheme, typography = Typography(), diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt index 1bde4f8..22fac53 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt @@ -23,8 +23,10 @@ class MainActivity : ComponentActivity() { startService(intent) } + val roomId = intent.getLongExtra("room_id", -1L) + setContent { - App() + App(openRoomId = if (roomId != -1L) roomId else null) } } } diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt index 4d53f16..fff1989 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt @@ -3,6 +3,7 @@ package su.reya.coop import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.app.Service import android.content.Intent import android.os.Build @@ -45,7 +46,7 @@ class NostrForegroundService : Service() { // Handle notifications nostr.handleLiteNotifications { event -> if (!isUserInApp()) { - showNewMessageNotification(event.content()) + showNewMessageNotification(event.roomId(), event.content()) } } } catch (e: Exception) { @@ -76,12 +77,26 @@ class NostrForegroundService : Service() { .build() } - private fun showNewMessageNotification(message: String) { + private fun showNewMessageNotification(roomId: Long, message: String) { + val intent = Intent(this, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + putExtra("room_id", roomId) + } + + val pendingIntent = PendingIntent.getActivity( + this, + roomId.toInt(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + val notification = NotificationCompat.Builder(this, "nostr_service") - .setContentTitle("New Message") + .setContentTitle("You received a new message") .setContentText(message) .setAutoCancel(true) + .setContentIntent(pendingIntent) .build() + val manager = getSystemService(NotificationManager::class.java) manager?.notify(System.currentTimeMillis().toInt(), notification) } diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt index c7f8f87..a3ab8a8 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt @@ -19,6 +19,8 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -37,6 +39,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -90,19 +93,16 @@ fun ChatScreen( var text by remember { mutableStateOf("") } var loading by remember { mutableStateOf(true) } + var newOtherMessages by remember { mutableIntStateOf(0) } val messages = remember { mutableStateListOf() } val groupedMessages = remember(messages.toList()) { messages.groupBy { it.createdAt().formatAsGroupHeader() } } - fun setLoading(value: Boolean) { - loading = value - } - LaunchedEffect(id) { // Start loading spinner - setLoading(true) + loading = true // Get messages val initialMessages = viewModel.getChatRoomMessages(id) @@ -122,7 +122,7 @@ fun ChatScreen( } // Stop loading spinner - setLoading(false) + loading = false // Handle new messages viewModel.newEvents.collect { event -> @@ -130,6 +130,9 @@ fun ChatScreen( if (event.id() !in messages.map { it.id() }) { messages.add(0, event) } + } else { + // If the event is not in the current room, it's a new message from another user + newOtherMessages++ } } } @@ -173,11 +176,21 @@ fun ChatScreen( } }, navigationIcon = { - IconButton(onClick = onBack) { - Icon( - painter = painterResource(Res.drawable.ic_arrow_back), - contentDescription = "Back" - ) + BadgedBox( + badge = { + if (newOtherMessages > 0) { + Badge { + Text(newOtherMessages.toString()) + } + } + } + ) { + IconButton(onClick = onBack) { + Icon( + painter = painterResource(Res.drawable.ic_arrow_back), + contentDescription = "Back" + ) + } } }, colors = TopAppBarDefaults.topAppBarColors( diff --git a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt index 241090d..049275c 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt @@ -109,13 +109,27 @@ class NostrViewModel( }, onSubscriptionClose = { getChatRooms() - if (!_isPartialProcessedGiftWrap.value) { _isPartialProcessedGiftWrap.value = true } }, onNewMessage = { event -> viewModelScope.launch { + val roomId = event.roomId() + val existingRoom = _chatRooms.value.firstOrNull { it.id == roomId } + + if (existingRoom == null) { + val currentUser = nostr.signer.currentUser + if (currentUser != null) { + val newRoom = Room.new(event, currentUser) + _chatRooms.update { currentRooms -> + currentRooms + newRoom + } + } + } else { + updateRoomList(roomId, event) + } + _newEvents.emit(event) } },