11package net.frozendevelopment.openletters
22
3- import android.content.pm.ActivityInfo
3+ import android.content.ActivityNotFoundException
4+ import android.content.Intent
45import android.os.Bundle
6+ import android.widget.Toast
57import androidx.activity.ComponentActivity
68import androidx.activity.compose.setContent
79import androidx.activity.enableEdgeToEdge
@@ -13,46 +15,53 @@ import androidx.compose.foundation.layout.only
1315import androidx.compose.foundation.layout.safeDrawing
1416import androidx.compose.foundation.layout.statusBarsPadding
1517import androidx.compose.foundation.layout.windowInsetsPadding
16- import androidx.compose.material3.DrawerState
1718import androidx.compose.material3.DrawerValue
1819import androidx.compose.material3.Scaffold
20+ import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
1921import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
22+ import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
23+ import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
24+ import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
2025import androidx.compose.material3.rememberDrawerState
26+ import androidx.compose.runtime.Composable
27+ import androidx.compose.runtime.CompositionLocalProvider
2128import androidx.compose.runtime.getValue
29+ import androidx.compose.runtime.remember
2230import androidx.compose.runtime.rememberCoroutineScope
2331import androidx.compose.ui.Modifier
32+ import androidx.compose.ui.unit.dp
33+ import androidx.core.net.toUri
2434import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
2535import androidx.lifecycle.compose.collectAsStateWithLifecycle
26- import androidx.navigation.compose.NavHost
27- import androidx.navigation.compose.rememberNavController
28- import androidx.window.core.layout.WindowWidthSizeClass
36+ import androidx.navigation3.runtime.NavKey
37+ import androidx.navigation3.ui.NavDisplay
2938import kotlinx.coroutines.launch
30- import net.frozendevelopment.openletters.data.sqldelight.LetterQueries
31- import net.frozendevelopment.openletters.extensions.newRoot
32- import net.frozendevelopment.openletters.feature.category.categories
39+ import net.frozendevelopment.openletters.extensions.EntryProvider
40+ import net.frozendevelopment.openletters.extensions.koinEntryProvider
3341import net.frozendevelopment.openletters.feature.category.form.CategoryFormDestination
3442import net.frozendevelopment.openletters.feature.category.manage.ManageCategoryDestination
35- import net.frozendevelopment.openletters.feature.letter.letters
3643import net.frozendevelopment.openletters.feature.letter.list.LetterListDestination
37- import net.frozendevelopment.openletters.feature.letter.scan.ScanLetterDestination
3844import net.frozendevelopment.openletters.feature.reminder.form.ReminderFormDestination
3945import net.frozendevelopment.openletters.feature.reminder.list.ReminderListDestination
40- import net.frozendevelopment.openletters.feature.reminder.reminders
4146import net.frozendevelopment.openletters.feature.settings.SettingsDestination
42- import net.frozendevelopment.openletters.feature.settings.settings
43- import net.frozendevelopment.openletters.ui.animation.navigationEnterTransition
44- import net.frozendevelopment.openletters.ui.animation.navigationExitTransition
45- import net.frozendevelopment.openletters.ui.animation.navigationPopEnterTransition
46- import net.frozendevelopment.openletters.ui.animation.navigationPopExitTransition
47- import net.frozendevelopment.openletters.ui.components.LettersNavDrawer
47+ import net.frozendevelopment.openletters.ui.animation.popTransitionSpec
48+ import net.frozendevelopment.openletters.ui.animation.pushTransitionSpec
49+ import net.frozendevelopment.openletters.ui.navigation.LettersNavDrawer
50+ import net.frozendevelopment.openletters.ui.navigation.LocalDrawerState
51+ import net.frozendevelopment.openletters.ui.navigation.LocalNavigationState
52+ import net.frozendevelopment.openletters.ui.navigation.LocalNavigator
53+ import net.frozendevelopment.openletters.ui.navigation.Navigator
54+ import net.frozendevelopment.openletters.ui.navigation.rememberNavigationState
55+ import net.frozendevelopment.openletters.ui.navigation.toEntries
4856import net.frozendevelopment.openletters.ui.theme.OpenLettersTheme
4957import net.frozendevelopment.openletters.util.ThemeManagerType
5058import org.koin.android.ext.android.inject
59+ import org.koin.core.annotation.KoinExperimentalAPI
5160
5261class MainActivity : ComponentActivity () {
5362 private val themeManager: ThemeManagerType by inject()
54- private val letterQueries: LetterQueries by inject()
5563
64+ @OptIn(KoinExperimentalAPI ::class , ExperimentalMaterial3AdaptiveApi ::class , ExperimentalMaterial3AdaptiveApi ::class )
5665 override fun onCreate (savedInstanceState : Bundle ? ) {
5766 installSplashScreen()
5867
@@ -61,77 +70,108 @@ class MainActivity : ComponentActivity() {
6170 enableEdgeToEdge()
6271
6372 setContent {
64- val currentTheme by themeManager.current.collectAsStateWithLifecycle()
73+ App ()
74+ }
75+ }
6576
66- OpenLettersTheme (
67- appTheme = currentTheme.first,
68- colorPalette = currentTheme.second,
69- ) {
70- val coroutineScope = rememberCoroutineScope()
71- val drawerState: DrawerState = rememberDrawerState(initialValue = DrawerValue .Closed )
72- val navHostController = rememberNavController()
77+ @OptIn(ExperimentalMaterial3AdaptiveApi ::class )
78+ @Composable
79+ private fun App () {
80+ val currentTheme by themeManager.current.collectAsStateWithLifecycle()
7381
74- // lock the app to portrait for phone users
75- if (currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass .COMPACT ) {
76- requestedOrientation = ActivityInfo .SCREEN_ORIENTATION_PORTRAIT
77- }
82+ val coroutineScope = rememberCoroutineScope()
83+ val drawerState = rememberDrawerState(DrawerValue .Closed )
84+ val navigationState = rememberNavigationState(
85+ LetterListDestination ,
86+ setOf (
87+ LetterListDestination ,
88+ ManageCategoryDestination ,
89+ ReminderListDestination ,
90+ ),
91+ )
92+ val navigator = remember {
93+ Navigator (
94+ state = navigationState,
95+ backPressedDispatcher = onBackPressedDispatcher,
96+ openInBrowser = {
97+ try {
98+ startActivity(Intent (Intent .ACTION_VIEW , it.toUri()))
99+ } catch (_: ActivityNotFoundException ) {
100+ Toast .makeText(this , " No browser found" , Toast .LENGTH_SHORT ).show()
101+ }
102+ },
103+ )
104+ }
105+ val entryProvider: EntryProvider = koinEntryProvider()
106+
107+ val windowAdaptiveInfo = currentWindowAdaptiveInfo()
108+ val directive = remember(windowAdaptiveInfo) {
109+ calculatePaneScaffoldDirective(windowAdaptiveInfo)
110+ .copy(horizontalPartitionSpacerSize = 0 .dp, verticalPartitionSpacerSize = 0 .dp)
111+ }
78112
79- LettersNavDrawer (
80- drawerState = drawerState,
81- goToMail = {
82- coroutineScope.launch { drawerState.close() }
83- navHostController.newRoot(LetterListDestination )
84- },
85- goToManageCategories = {
86- coroutineScope.launch { drawerState.close() }
87- navHostController.newRoot(ManageCategoryDestination )
88- },
89- goToCreateCategory = {
90- coroutineScope.launch { drawerState.close() }
91- navHostController.navigate(CategoryFormDestination ())
92- },
93- goToReminders = {
94- coroutineScope.launch { drawerState.close() }
95- navHostController.newRoot(ReminderListDestination )
96- },
97- goToCreateReminder = {
98- coroutineScope.launch { drawerState.close() }
99- navHostController.navigate(ReminderFormDestination ())
100- },
101- goToSettings = {
102- coroutineScope.launch { drawerState.close() }
103- navHostController.navigate(SettingsDestination )
104- },
105- ) {
106- Scaffold (modifier = Modifier .fillMaxSize()) { _ ->
107- Box (
108- modifier =
109- Modifier
110- .fillMaxSize()
111- .statusBarsPadding()
112- .windowInsetsPadding(
113- WindowInsets .safeDrawing.only(
114- WindowInsetsSides .Horizontal ,
115- ),
113+ val supportingPaneStrategy = rememberListDetailSceneStrategy<NavKey >(
114+ backNavigationBehavior = BackNavigationBehavior .PopUntilCurrentDestinationChange ,
115+ directive = directive,
116+ )
117+
118+ OpenLettersTheme (
119+ appTheme = currentTheme.first,
120+ colorPalette = currentTheme.second,
121+ ) {
122+ LettersNavDrawer (
123+ drawerState = drawerState,
124+ goToMail = {
125+ coroutineScope.launch { drawerState.close() }
126+ navigator.navigate(LetterListDestination )
127+ },
128+ goToManageCategories = {
129+ coroutineScope.launch { drawerState.close() }
130+ navigator.navigate(ManageCategoryDestination )
131+ },
132+ goToCreateCategory = {
133+ coroutineScope.launch { drawerState.close() }
134+ navigator.navigate(CategoryFormDestination ())
135+ },
136+ goToReminders = {
137+ coroutineScope.launch { drawerState.close() }
138+ navigator.navigate(ReminderListDestination )
139+ },
140+ goToCreateReminder = {
141+ coroutineScope.launch { drawerState.close() }
142+ navigator.navigate(ReminderFormDestination ())
143+ },
144+ goToSettings = {
145+ coroutineScope.launch { drawerState.close() }
146+ navigator.navigate(SettingsDestination )
147+ },
148+ ) {
149+ Scaffold (modifier = Modifier .fillMaxSize()) { _ ->
150+ Box (
151+ modifier =
152+ Modifier
153+ .fillMaxSize()
154+ .statusBarsPadding()
155+ .windowInsetsPadding(
156+ WindowInsets .safeDrawing.only(
157+ WindowInsetsSides .Horizontal ,
116158 ),
117- ) {
118- NavHost (
119- navController = navHostController,
120- startDestination =
121- if (letterQueries.hasLetters().executeAsOne() == 1L ) {
122- LetterListDestination
123- } else {
124- ScanLetterDestination (canNavigateBack = false )
125- },
126- enterTransition = { navigationEnterTransition() },
127- exitTransition = { navigationExitTransition() },
128- popEnterTransition = { navigationPopEnterTransition() },
129- popExitTransition = { navigationPopExitTransition() },
130- ) {
131- categories(navHostController, drawerState)
132- letters(navHostController, drawerState)
133- reminders(navHostController, drawerState)
134- settings(navHostController)
159+ ),
160+ ) {
161+ CompositionLocalProvider (LocalDrawerState provides drawerState) {
162+ CompositionLocalProvider (LocalNavigationState provides navigationState) {
163+ CompositionLocalProvider (
164+ LocalNavigator provides navigator,
165+ ) {
166+ NavDisplay (
167+ entries = navigationState.toEntries(entryProvider),
168+ sceneStrategy = supportingPaneStrategy,
169+ onBack = { navigator.pop() },
170+ transitionSpec = { pushTransitionSpec() },
171+ popTransitionSpec = { popTransitionSpec() },
172+ predictivePopTransitionSpec = { popTransitionSpec() },
173+ )
174+ }
135175 }
136176 }
137177 }
0 commit comments