From fea0b9154a66623a4420790debad35708b1a2361 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Fri, 29 May 2026 08:28:20 +0700 Subject: [PATCH] add deep link --- .../src/androidMain/AndroidManifest.xml | 15 ++ .../androidMain/kotlin/su/reya/coop/App.kt | 174 +++++++++--------- .../kotlin/su/reya/coop/MainActivity.kt | 22 ++- .../su/reya/coop/NostrForegroundService.kt | 15 +- 4 files changed, 123 insertions(+), 103 deletions(-) diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml index 1b639de..d8bf7ea 100644 --- a/composeApp/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -18,15 +18,30 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@android:style/Theme.Material.Light.NoActionBar"> + + + + + + + + + + + + { @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable -fun App( - viewModel: NostrViewModel, - openRoomId: Long? = null -) { +fun App(viewModel: NostrViewModel) { val context = LocalContext.current val navController = rememberNavController() val scope = rememberCoroutineScope() @@ -114,18 +112,94 @@ fun App( LaunchedEffect(emptySecret) { // Navigate to the home screen if the secret is already set - if (emptySecret == false && openRoomId == null) { + if (emptySecret == false) { navController.navigate(Screen.Home) { popUpTo(Screen.Onboarding) { inclusive = true } } } } - LaunchedEffect(openRoomId) { - if (openRoomId != null) { - navController.navigate(Screen.Chat(openRoomId)) { - popUpTo(Screen.Home) { saveState = true } - } + NavHost( + navController = navController, + startDestination = if (emptySecret == false) Screen.Home else Screen.Onboarding + ) { + composable { backStackEntry -> + OnboardingScreen( + onOpenImport = { navController.navigate(Screen.Import) }, + onOpenNew = { navController.navigate(Screen.NewIdentity) } + ) + } + composable { backStackEntry -> + val isCreating by viewModel.isCreating.collectAsState() + + ImportScreen( + isLoading = isCreating, + onBack = { navController.popBackStack() }, + onSave = { secret -> + viewModel.importIdentity(secret) + } + ) + } + composable { backStackEntry -> + val isCreating by viewModel.isCreating.collectAsState() + + NewIdentityScreen( + isLoading = isCreating, + onBack = { navController.popBackStack() }, + onSave = { name, bio, uri -> + val contentType = uri?.let { context.contentResolver.getType(it) } + val picture = uri?.let { + context.contentResolver.openInputStream(it)?.use { input -> + input.readBytes() + } + } + viewModel.createIdentity(name, bio, picture, contentType) + } + ) + } + composable { backStackEntry -> + HomeScreen( + onOpenChat = { id -> navController.navigate(Screen.Chat(id)) }, + onNewChat = { navController.navigate(Screen.NewChat) } + ) + } + composable( + deepLinks = listOf( + navDeepLink(basePath = "coop://chat") + ) + ) { backStackEntry -> + val chat: Screen.Chat = backStackEntry.toRoute() + ChatScreen( + id = chat.id, + onBack = { navController.popBackStack() }, + ) + } + composable { backStackEntry -> + val profile: Screen.Profile = backStackEntry.toRoute() + ProfileScreen( + pubkey = profile.pubkey, + onBack = { navController.popBackStack() }, + ) + } + composable { backStackEntry -> + NewChatScreen( + onBack = { navController.popBackStack() }, + ) + } + composable { backStackEntry -> + ScanScreen( + onBack = { navController.popBackStack() }, + ) + } + composable { backStackEntry -> + MyQrScreen( + onBack = { navController.popBackStack() }, + ) + } + composable { backStackEntry -> + RelayScreen( + onBack = { navController.popBackStack() }, + ) } } @@ -183,86 +257,6 @@ fun App( } } } - - NavHost( - navController = navController, - startDestination = if (emptySecret == false) Screen.Home else Screen.Onboarding - ) { - composable { backStackEntry -> - OnboardingScreen( - onOpenImport = { navController.navigate(Screen.Import) }, - onOpenNew = { navController.navigate(Screen.NewIdentity) } - ) - } - composable { backStackEntry -> - val isCreating by viewModel.isCreating.collectAsState() - - ImportScreen( - isLoading = isCreating, - onBack = { navController.popBackStack() }, - onSave = { secret -> - viewModel.importIdentity(secret) - } - ) - } - composable { backStackEntry -> - val isCreating by viewModel.isCreating.collectAsState() - - NewIdentityScreen( - isLoading = isCreating, - onBack = { navController.popBackStack() }, - onSave = { name, bio, uri -> - val contentType = uri?.let { context.contentResolver.getType(it) } - val picture = uri?.let { - context.contentResolver.openInputStream(it)?.use { input -> - input.readBytes() - } - } - viewModel.createIdentity(name, bio, picture, contentType) - } - ) - } - composable { backStackEntry -> - HomeScreen( - onOpenChat = { id -> navController.navigate(Screen.Chat(id)) }, - onNewChat = { navController.navigate(Screen.NewChat) } - ) - } - composable { backStackEntry -> - val chat: Screen.Chat = backStackEntry.toRoute() - ChatScreen( - id = chat.id, - onBack = { navController.popBackStack() }, - ) - } - composable { backStackEntry -> - val profile: Screen.Profile = backStackEntry.toRoute() - ProfileScreen( - pubkey = profile.pubkey, - onBack = { navController.popBackStack() }, - ) - } - composable { backStackEntry -> - NewChatScreen( - onBack = { navController.popBackStack() }, - ) - } - composable { backStackEntry -> - ScanScreen( - onBack = { navController.popBackStack() }, - ) - } - composable { backStackEntry -> - MyQrScreen( - onBack = { navController.popBackStack() }, - ) - } - composable { backStackEntry -> - RelayScreen( - onBack = { navController.popBackStack() }, - ) - } - } } } } diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt index ff7d854..3daad42 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt @@ -6,10 +6,22 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import su.reya.coop.coop.storage.SecretStore class MainActivity : ComponentActivity() { + private val viewModel: NostrViewModel by viewModels { + object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + val secretStore = SecretStore(this@MainActivity) + return NostrViewModel(NostrManager.instance, secretStore) as T + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() enableEdgeToEdge() @@ -17,25 +29,19 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) val serviceIntent = Intent(this, NostrForegroundService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent) } else { startService(serviceIntent) } - val roomId = intent.getLongExtra("room_id", -1L) - val secretStore = SecretStore(this) - val viewModel = NostrViewModel(NostrManager.instance, secretStore) - splashScreen.setKeepOnScreenCondition { viewModel.emptySecret.value == null } setContent { - App( - viewModel = viewModel, - openRoomId = if (roomId != -1L) roomId else null - ) + App(viewModel = viewModel) } } diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt index 54a2349..48e9bc3 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt @@ -10,6 +10,7 @@ import android.os.Build import android.os.IBinder import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat +import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import androidx.lifecycle.ProcessLifecycleOwner import kotlinx.coroutines.CoroutineScope @@ -111,10 +112,14 @@ class NostrForegroundService : Service() { } 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 deepLinkUri = "coop://chat/$roomId".toUri() + + val intent = Intent( + Intent.ACTION_VIEW, + deepLinkUri, + this, + MainActivity::class.java + ) val pendingIntent = PendingIntent.getActivity( this, @@ -122,7 +127,7 @@ class NostrForegroundService : Service() { intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) - + val notification = NotificationCompat.Builder(this, "nostr_messages") .setSmallIcon(R.drawable.ic_notification) .setContentTitle("You received a new message")