package su.reya.coop import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.darkColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.expressiveLightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.toRoute 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.NewChatScreen import su.reya.coop.screens.NewIdentityScreen import su.reya.coop.screens.OnboardingScreen import su.reya.coop.screens.ScanScreen val LocalNostrViewModel = staticCompositionLocalOf { error("No NostrViewModel provided") } val LocalSnackbarHostState = staticCompositionLocalOf { error("No SnackbarHostState provided") } val LocalNavController = staticCompositionLocalOf { error("No NavController provided") } @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun App(dbPath: String) { val context = LocalContext.current val navController = rememberNavController() val darkMode = isSystemInDarkTheme() // Snackbar val snackbarHostState = remember { SnackbarHostState() } // Initialize Nostr and SecretStore val nostr = remember { Nostr() } val secretStore = remember { SecretStore(context) } val viewModel: NostrViewModel = viewModel { NostrViewModel(nostr, secretStore) } // Enabled the dynamic color scheme 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 -> expressiveLightColorScheme() } LaunchedEffect(Unit) { viewModel.initAndConnect(dbPath) viewModel.startNotificationHandler() viewModel.getChatRooms() // Collect error events from the ViewModel viewModel.errorEvents.collect { message -> snackbarHostState.showSnackbar(message) } } MaterialExpressiveTheme( colorScheme = colorScheme, ) { CompositionLocalProvider( LocalNostrViewModel provides viewModel, LocalSnackbarHostState provides snackbarHostState, LocalNavController provides navController, ) { val emptySecret by viewModel.emptySecret.collectAsState(initial = null) LaunchedEffect(emptySecret) { // Navigate to the home screen if the secret is already set if (emptySecret == false) { // Get chat rooms viewModel.getChatRooms() // Navigate to the home screen navController.navigate(Screen.Home) { popUpTo(Screen.Onboarding) { inclusive = true } } } } // Show loading screen while initializing if (emptySecret == null) return@CompositionLocalProvider 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 -> NewChatScreen( onBack = { navController.popBackStack() }, ) } composable { backStackEntry -> ScanScreen( onBack = { navController.popBackStack() }, ) } } } } }