Skip to content

Commit 053f638

Browse files
frozenjavaJosh
andauthored
Navigation3 Migration (#1)
* nav3 migration * more progress on nav3 migration * started working on nav3 split scene * Completed navigation-3 migration * fixed tests * a few last minute changes --------- Co-authored-by: Josh <josh@frozendevelopment.net>
1 parent 2fcd365 commit 053f638

55 files changed

Lines changed: 1982 additions & 1612 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
root = true
2+
3+
[*.{kt,kts}]
4+
max_line_length = 160
5+
ktlint_code_style = ktlint_official
6+
ktlint_standard_chain-method-continuation = disabled
7+
ktlint_standard_multiline-expression-wrapping = disabled
8+
ktlint_standard_no-wildcard-imports = disabled
9+
ktlint_standard_no-single-line-block-comment = disabled
10+
ktlint_standard_parameter-list-wrapping = enabled
11+
ktlint_standard_function-expression-body = disabled
12+
ktlint_function_naming_ignore_when_annotated_with = Composable,Preview,PreviewLightDark
13+
ktlint_function_signature_body_expression_wrapping = default

app/.editorconfig

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
root = true
22

33
[*.{kt,kts}]
4-
ktlint_code_style = ktlint_official
5-
ktlint_function_naming_ignore_when_annotated_with=Composable
64
max_line_length = 160
7-
8-
## Testing rules
9-
[**/{test,androidTest,commonTest}/**.kt]
10-
max_line_length = off
11-
spacing-between-declarations-with-comments = off
5+
ktlint_code_style = ktlint_official
6+
ktlint_standard_chain-method-continuation = disabled
7+
ktlint_standard_multiline-expression-wrapping = disabled
8+
ktlint_standard_no-wildcard-imports = disabled
9+
ktlint_standard_no-single-line-block-comment = disabled
10+
ktlint_standard_parameter-list-wrapping = enabled
11+
ktlint_standard_function-expression-body = disabled
12+
ktlint_function_naming_ignore_when_annotated_with = Composable,Preview,PreviewLightDark
13+
ktlint_function_signature_body_expression_wrapping = default

app/build.gradle.kts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ plugins {
1010
}
1111

1212
ksp {
13-
arg("KOIN_CONFIG_CHECK", "true")
13+
arg("KOIN_CONFIG_CHECK", "false")
1414
}
1515

1616
android {
1717
namespace = "net.frozendevelopment.openletters"
18-
compileSdk = 35
18+
compileSdk = 36
1919

2020
defaultConfig {
2121
applicationId = "net.frozendevelopment.openletters"
2222
minSdk = 26
23-
targetSdk = 35
23+
targetSdk = 36
2424
versionCode = Integer.parseInt(System.getenv("GITHUB_RUN_NUMBER") ?: "1")
2525
versionName = "0.1.0"
2626

@@ -79,9 +79,6 @@ android {
7979
sourceCompatibility = JavaVersion.VERSION_1_8
8080
targetCompatibility = JavaVersion.VERSION_1_8
8181
}
82-
kotlinOptions {
83-
jvmTarget = "1.8"
84-
}
8582
buildFeatures {
8683
compose = true
8784
}
@@ -92,6 +89,12 @@ android {
9289
}
9390
}
9491

92+
kotlin {
93+
compilerOptions {
94+
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8)
95+
}
96+
}
97+
9598
tasks.register("printVersionName") {
9699
doLast {
97100
println(android.defaultConfig.versionName)
@@ -118,24 +121,27 @@ dependencies {
118121
implementation(libs.androidx.ui.graphics)
119122
implementation(libs.androidx.ui.tooling.preview)
120123
implementation(libs.androidx.material3)
124+
implementation(libs.androidx.material3.adaptive.navigation)
121125
implementation(libs.androidx.material.icons)
122126
implementation(libs.androidx.material.icons.extended)
123-
implementation(libs.androidx.navigation)
124-
implementation(libs.androidx.navigation.common.ktx)
127+
implementation(libs.androidx.nav3.runtime)
128+
implementation(libs.androidx.nav3.ui)
125129
implementation(libs.androidx.lifecycle.runtime.compose.android)
130+
implementation(libs.androidx.lifecycle.viewmodel)
126131
implementation(libs.androidx.splashscreen)
127132

128133
implementation(platform(libs.koin.bom))
129134
implementation(libs.koin.android)
130135
implementation(libs.koin.compose)
131136
implementation(libs.koin.workmanager)
132-
implementation(libs.koin.compose.navigation)
137+
implementation(libs.koin.nav3)
138+
133139
implementation(libs.androidx.adaptive.android)
134140
implementation(libs.androidx.core.animation)
135141
implementation(libs.androidx.ui.text.google.fonts)
136142
implementation(libs.androidx.datastore.core.android)
137-
compileOnly(libs.koin.annotations)
138-
ksp(libs.koin.ksp)
143+
// compileOnly(libs.koin.annotations)
144+
// ksp(libs.koin.ksp)
139145

140146
implementation(libs.sqldelight)
141147
implementation(libs.sqldelight.coroutines)

app/src/main/java/net/frozendevelopment/openletters/MainActivity.kt

Lines changed: 125 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package net.frozendevelopment.openletters
22

3-
import android.content.pm.ActivityInfo
3+
import android.content.ActivityNotFoundException
4+
import android.content.Intent
45
import android.os.Bundle
6+
import android.widget.Toast
57
import androidx.activity.ComponentActivity
68
import androidx.activity.compose.setContent
79
import androidx.activity.enableEdgeToEdge
@@ -13,46 +15,53 @@ import androidx.compose.foundation.layout.only
1315
import androidx.compose.foundation.layout.safeDrawing
1416
import androidx.compose.foundation.layout.statusBarsPadding
1517
import androidx.compose.foundation.layout.windowInsetsPadding
16-
import androidx.compose.material3.DrawerState
1718
import androidx.compose.material3.DrawerValue
1819
import androidx.compose.material3.Scaffold
20+
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
1921
import 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
2025
import androidx.compose.material3.rememberDrawerState
26+
import androidx.compose.runtime.Composable
27+
import androidx.compose.runtime.CompositionLocalProvider
2128
import androidx.compose.runtime.getValue
29+
import androidx.compose.runtime.remember
2230
import androidx.compose.runtime.rememberCoroutineScope
2331
import androidx.compose.ui.Modifier
32+
import androidx.compose.ui.unit.dp
33+
import androidx.core.net.toUri
2434
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
2535
import 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
2938
import 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
3341
import net.frozendevelopment.openletters.feature.category.form.CategoryFormDestination
3442
import net.frozendevelopment.openletters.feature.category.manage.ManageCategoryDestination
35-
import net.frozendevelopment.openletters.feature.letter.letters
3643
import net.frozendevelopment.openletters.feature.letter.list.LetterListDestination
37-
import net.frozendevelopment.openletters.feature.letter.scan.ScanLetterDestination
3844
import net.frozendevelopment.openletters.feature.reminder.form.ReminderFormDestination
3945
import net.frozendevelopment.openletters.feature.reminder.list.ReminderListDestination
40-
import net.frozendevelopment.openletters.feature.reminder.reminders
4146
import 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
4856
import net.frozendevelopment.openletters.ui.theme.OpenLettersTheme
4957
import net.frozendevelopment.openletters.util.ThemeManagerType
5058
import org.koin.android.ext.android.inject
59+
import org.koin.core.annotation.KoinExperimentalAPI
5160

5261
class 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

Comments
 (0)