Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
root = true

[*.{kt,kts}]
max_line_length = 160
ktlint_code_style = ktlint_official
ktlint_standard_chain-method-continuation = disabled
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_no-wildcard-imports = disabled
ktlint_standard_no-single-line-block-comment = disabled
ktlint_standard_parameter-list-wrapping = enabled
ktlint_standard_function-expression-body = disabled
ktlint_function_naming_ignore_when_annotated_with = Composable,Preview,PreviewLightDark
ktlint_function_signature_body_expression_wrapping = default
16 changes: 9 additions & 7 deletions app/.editorconfig
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
root = true

[*.{kt,kts}]
ktlint_code_style = ktlint_official
ktlint_function_naming_ignore_when_annotated_with=Composable
max_line_length = 160

## Testing rules
[**/{test,androidTest,commonTest}/**.kt]
max_line_length = off
spacing-between-declarations-with-comments = off
ktlint_code_style = ktlint_official
ktlint_standard_chain-method-continuation = disabled
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_no-wildcard-imports = disabled
ktlint_standard_no-single-line-block-comment = disabled
ktlint_standard_parameter-list-wrapping = enabled
ktlint_standard_function-expression-body = disabled
ktlint_function_naming_ignore_when_annotated_with = Composable,Preview,PreviewLightDark
ktlint_function_signature_body_expression_wrapping = default
28 changes: 17 additions & 11 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ plugins {
}

ksp {
arg("KOIN_CONFIG_CHECK", "true")
arg("KOIN_CONFIG_CHECK", "false")
}

android {
namespace = "net.frozendevelopment.openletters"
compileSdk = 35
compileSdk = 36

defaultConfig {
applicationId = "net.frozendevelopment.openletters"
minSdk = 26
targetSdk = 35
targetSdk = 36
versionCode = Integer.parseInt(System.getenv("GITHUB_RUN_NUMBER") ?: "1")
versionName = "0.1.0"

Expand Down Expand Up @@ -79,9 +79,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
Expand All @@ -92,6 +89,12 @@ android {
}
}

kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8)
}
}

tasks.register("printVersionName") {
doLast {
println(android.defaultConfig.versionName)
Expand All @@ -118,24 +121,27 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.material3.adaptive.navigation)
implementation(libs.androidx.material.icons)
implementation(libs.androidx.material.icons.extended)
implementation(libs.androidx.navigation)
implementation(libs.androidx.navigation.common.ktx)
implementation(libs.androidx.nav3.runtime)
implementation(libs.androidx.nav3.ui)
implementation(libs.androidx.lifecycle.runtime.compose.android)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.splashscreen)

implementation(platform(libs.koin.bom))
implementation(libs.koin.android)
implementation(libs.koin.compose)
implementation(libs.koin.workmanager)
implementation(libs.koin.compose.navigation)
implementation(libs.koin.nav3)

implementation(libs.androidx.adaptive.android)
implementation(libs.androidx.core.animation)
implementation(libs.androidx.ui.text.google.fonts)
implementation(libs.androidx.datastore.core.android)
compileOnly(libs.koin.annotations)
ksp(libs.koin.ksp)
// compileOnly(libs.koin.annotations)
// ksp(libs.koin.ksp)

implementation(libs.sqldelight)
implementation(libs.sqldelight.coroutines)
Expand Down
210 changes: 125 additions & 85 deletions app/src/main/java/net/frozendevelopment/openletters/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package net.frozendevelopment.openletters

import android.content.pm.ActivityInfo
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
Expand All @@ -13,46 +15,53 @@ import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.DrawerState
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.Scaffold
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import androidx.window.core.layout.WindowWidthSizeClass
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.ui.NavDisplay
import kotlinx.coroutines.launch
import net.frozendevelopment.openletters.data.sqldelight.LetterQueries
import net.frozendevelopment.openletters.extensions.newRoot
import net.frozendevelopment.openletters.feature.category.categories
import net.frozendevelopment.openletters.extensions.EntryProvider
import net.frozendevelopment.openletters.extensions.koinEntryProvider
import net.frozendevelopment.openletters.feature.category.form.CategoryFormDestination
import net.frozendevelopment.openletters.feature.category.manage.ManageCategoryDestination
import net.frozendevelopment.openletters.feature.letter.letters
import net.frozendevelopment.openletters.feature.letter.list.LetterListDestination
import net.frozendevelopment.openletters.feature.letter.scan.ScanLetterDestination
import net.frozendevelopment.openletters.feature.reminder.form.ReminderFormDestination
import net.frozendevelopment.openletters.feature.reminder.list.ReminderListDestination
import net.frozendevelopment.openletters.feature.reminder.reminders
import net.frozendevelopment.openletters.feature.settings.SettingsDestination
import net.frozendevelopment.openletters.feature.settings.settings
import net.frozendevelopment.openletters.ui.animation.navigationEnterTransition
import net.frozendevelopment.openletters.ui.animation.navigationExitTransition
import net.frozendevelopment.openletters.ui.animation.navigationPopEnterTransition
import net.frozendevelopment.openletters.ui.animation.navigationPopExitTransition
import net.frozendevelopment.openletters.ui.components.LettersNavDrawer
import net.frozendevelopment.openletters.ui.animation.popTransitionSpec
import net.frozendevelopment.openletters.ui.animation.pushTransitionSpec
import net.frozendevelopment.openletters.ui.navigation.LettersNavDrawer
import net.frozendevelopment.openletters.ui.navigation.LocalDrawerState
import net.frozendevelopment.openletters.ui.navigation.LocalNavigationState
import net.frozendevelopment.openletters.ui.navigation.LocalNavigator
import net.frozendevelopment.openletters.ui.navigation.Navigator
import net.frozendevelopment.openletters.ui.navigation.rememberNavigationState
import net.frozendevelopment.openletters.ui.navigation.toEntries
import net.frozendevelopment.openletters.ui.theme.OpenLettersTheme
import net.frozendevelopment.openletters.util.ThemeManagerType
import org.koin.android.ext.android.inject
import org.koin.core.annotation.KoinExperimentalAPI

class MainActivity : ComponentActivity() {
private val themeManager: ThemeManagerType by inject()
private val letterQueries: LetterQueries by inject()

@OptIn(KoinExperimentalAPI::class, ExperimentalMaterial3AdaptiveApi::class, ExperimentalMaterial3AdaptiveApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()

Expand All @@ -61,77 +70,108 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()

setContent {
val currentTheme by themeManager.current.collectAsStateWithLifecycle()
App()
}
}

OpenLettersTheme(
appTheme = currentTheme.first,
colorPalette = currentTheme.second,
) {
val coroutineScope = rememberCoroutineScope()
val drawerState: DrawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val navHostController = rememberNavController()
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
private fun App() {
val currentTheme by themeManager.current.collectAsStateWithLifecycle()

// lock the app to portrait for phone users
if (currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
val coroutineScope = rememberCoroutineScope()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val navigationState = rememberNavigationState(
LetterListDestination,
setOf(
LetterListDestination,
ManageCategoryDestination,
ReminderListDestination,
),
)
val navigator = remember {
Navigator(
state = navigationState,
backPressedDispatcher = onBackPressedDispatcher,
openInBrowser = {
try {
startActivity(Intent(Intent.ACTION_VIEW, it.toUri()))
} catch (_: ActivityNotFoundException) {
Toast.makeText(this, "No browser found", Toast.LENGTH_SHORT).show()
}
},
)
}
val entryProvider: EntryProvider = koinEntryProvider()

val windowAdaptiveInfo = currentWindowAdaptiveInfo()
val directive = remember(windowAdaptiveInfo) {
calculatePaneScaffoldDirective(windowAdaptiveInfo)
.copy(horizontalPartitionSpacerSize = 0.dp, verticalPartitionSpacerSize = 0.dp)
}

LettersNavDrawer(
drawerState = drawerState,
goToMail = {
coroutineScope.launch { drawerState.close() }
navHostController.newRoot(LetterListDestination)
},
goToManageCategories = {
coroutineScope.launch { drawerState.close() }
navHostController.newRoot(ManageCategoryDestination)
},
goToCreateCategory = {
coroutineScope.launch { drawerState.close() }
navHostController.navigate(CategoryFormDestination())
},
goToReminders = {
coroutineScope.launch { drawerState.close() }
navHostController.newRoot(ReminderListDestination)
},
goToCreateReminder = {
coroutineScope.launch { drawerState.close() }
navHostController.navigate(ReminderFormDestination())
},
goToSettings = {
coroutineScope.launch { drawerState.close() }
navHostController.navigate(SettingsDestination)
},
) {
Scaffold(modifier = Modifier.fillMaxSize()) { _ ->
Box(
modifier =
Modifier
.fillMaxSize()
.statusBarsPadding()
.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal,
),
val supportingPaneStrategy = rememberListDetailSceneStrategy<NavKey>(
backNavigationBehavior = BackNavigationBehavior.PopUntilCurrentDestinationChange,
directive = directive,
)

OpenLettersTheme(
appTheme = currentTheme.first,
colorPalette = currentTheme.second,
) {
LettersNavDrawer(
drawerState = drawerState,
goToMail = {
coroutineScope.launch { drawerState.close() }
navigator.navigate(LetterListDestination)
},
goToManageCategories = {
coroutineScope.launch { drawerState.close() }
navigator.navigate(ManageCategoryDestination)
},
goToCreateCategory = {
coroutineScope.launch { drawerState.close() }
navigator.navigate(CategoryFormDestination())
},
goToReminders = {
coroutineScope.launch { drawerState.close() }
navigator.navigate(ReminderListDestination)
},
goToCreateReminder = {
coroutineScope.launch { drawerState.close() }
navigator.navigate(ReminderFormDestination())
},
goToSettings = {
coroutineScope.launch { drawerState.close() }
navigator.navigate(SettingsDestination)
},
) {
Scaffold(modifier = Modifier.fillMaxSize()) { _ ->
Box(
modifier =
Modifier
.fillMaxSize()
.statusBarsPadding()
.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal,
),
) {
NavHost(
navController = navHostController,
startDestination =
if (letterQueries.hasLetters().executeAsOne() == 1L) {
LetterListDestination
} else {
ScanLetterDestination(canNavigateBack = false)
},
enterTransition = { navigationEnterTransition() },
exitTransition = { navigationExitTransition() },
popEnterTransition = { navigationPopEnterTransition() },
popExitTransition = { navigationPopExitTransition() },
) {
categories(navHostController, drawerState)
letters(navHostController, drawerState)
reminders(navHostController, drawerState)
settings(navHostController)
),
) {
CompositionLocalProvider(LocalDrawerState provides drawerState) {
CompositionLocalProvider(LocalNavigationState provides navigationState) {
CompositionLocalProvider(
LocalNavigator provides navigator,
) {
NavDisplay(
entries = navigationState.toEntries(entryProvider),
sceneStrategy = supportingPaneStrategy,
onBack = { navigator.pop() },
transitionSpec = { pushTransitionSpec() },
popTransitionSpec = { popTransitionSpec() },
predictivePopTransitionSpec = { popTransitionSpec() },
)
}
}
}
}
Expand Down
Loading
Loading