diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b187d9af..1d9e149d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -62,134 +62,132 @@ kotlin { } android { - (this as ApplicationExtension).apply { - compileSdk = appCompileSdk - namespace = appNamespace + compileSdk = appCompileSdk + namespace = appNamespace - defaultConfig { - applicationId = appNamespace - minSdk = appMinSdk - targetSdk = appTargetSdk - versionCode = appVersionCode + defaultConfig { + applicationId = appNamespace + minSdk = appMinSdk + targetSdk = appTargetSdk + versionCode = appVersionCode - // WARN: Keep it consistent with the res value property in build variants below... - versionName = appVersionName + // WARN: Keep it consistent with the res value property in build variants below... + versionName = appVersionName - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - signingConfigs { - create("release") { - val keystorePropertiesFile = rootProject.file("keystore.properties") - if (keystorePropertiesFile.exists()) { - val keystoreProperties = Properties() - keystoreProperties.load(FileInputStream(keystorePropertiesFile)) - - keyAlias = keystoreProperties.getProperty("keyAlias") - keyPassword = keystoreProperties.getProperty("keyPassword") - storeFile = file(keystoreProperties.getProperty("storeFile")) - storePassword = keystoreProperties.getProperty("storePassword") - } else { - logger.warn("WARNING: keystore.properties not found for release signing config.") - // throw GradleException("keystore.properties not found!") - } - } + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } - create("staging") { - val keystorePropertiesFile = rootProject.file("staging-keystore.properties") - if (keystorePropertiesFile.exists()) { - val keystoreProperties = Properties() - keystoreProperties.load(FileInputStream(keystorePropertiesFile)) - - keyAlias = keystoreProperties.getProperty("stagingKeyAlias") - keyPassword = keystoreProperties.getProperty("stagingKeyPassword") - storeFile = file(keystoreProperties.getProperty("stagingStoreFile")) - storePassword = keystoreProperties.getProperty("stagingStorePassword") - } else { - logger.warn("WARNING: keystore.properties not found for release signing config.") - // throw GradleException("keystore.properties not found!") - } + signingConfigs { + create("release") { + val keystorePropertiesFile = rootProject.file("keystore.properties") + if (keystorePropertiesFile.exists()) { + val keystoreProperties = Properties() + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) + + keyAlias = keystoreProperties.getProperty("keyAlias") + keyPassword = keystoreProperties.getProperty("keyPassword") + storeFile = file(keystoreProperties.getProperty("storeFile")) + storePassword = keystoreProperties.getProperty("storePassword") + } else { + logger.warn("WARNING: keystore.properties not found for release signing config.") + // throw GradleException("keystore.properties not found!") } } - splits { - abi { - isEnable = true - reset() - include("armeabi-v7a", "arm64-v8a", "x86", "x86_64") - isUniversalApk = true + create("staging") { + val keystorePropertiesFile = rootProject.file("staging-keystore.properties") + if (keystorePropertiesFile.exists()) { + val keystoreProperties = Properties() + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) + + keyAlias = keystoreProperties.getProperty("stagingKeyAlias") + keyPassword = keystoreProperties.getProperty("stagingKeyPassword") + storeFile = file(keystoreProperties.getProperty("stagingStoreFile")) + storePassword = keystoreProperties.getProperty("stagingStorePassword") + } else { + logger.warn("WARNING: keystore.properties not found for release signing config.") + // throw GradleException("keystore.properties not found!") } } + } - lint { - // baseline = rootProject.file("lint-baseline.xml") // If you use a baseline - lintConfig = rootProject.file("lint.xml") + splits { + abi { + isEnable = true + reset() + include("armeabi-v7a", "arm64-v8a", "x86", "x86_64") + isUniversalApk = true } + } - buildTypes { - getByName("release") { - if (rootProject.file("keystore.properties").exists()) { - signingConfig = signingConfigs.getByName("release") - } else { - logger.warn("WARNING: Release build will not be signed as keystore.properties is missing.") - // throw GradleException("Can't Sign Release Build") - } - - isDebuggable = false - isShrinkResources = true - isMinifyEnabled = true - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - - resValue("string", "app_name", appBaseName) - resValue("string", "app_version", appLabel) - - manifestPlaceholders["appIcon"] = mainIcon - manifestPlaceholders["appRoundIcon"] = mainRoundIcon - } + lint { + // baseline = rootProject.file("lint-baseline.xml") // If you use a baseline + lintConfig = rootProject.file("lint.xml") + } - create("staging") { - if (rootProject.file("staging-keystore.properties").exists()) { - signingConfig = signingConfigs.getByName("staging") - } else { - logger.warn("WARNING: Staging build will not be signed as staging-keystore.properties is missing.") - // throw GradleException("Can't Sign Staging Build") - } - - applicationIdSuffix = ".staging" - versionNameSuffix = "-Staging" - - isDebuggable = false - isShrinkResources = true - isMinifyEnabled = true - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - - resValue("string", "app_name", "$appBaseName Staging") - resValue("string", "app_version", appDevLabel) - - manifestPlaceholders["appIcon"] = devIcon - manifestPlaceholders["appRoundIcon"] = devRoundIcon + buildTypes { + getByName("release") { + if (rootProject.file("keystore.properties").exists()) { + signingConfig = signingConfigs.getByName("release") + } else { + logger.warn("WARNING: Release build will not be signed as keystore.properties is missing.") + // throw GradleException("Can't Sign Release Build") } - getByName("debug") { - applicationIdSuffix = ".dev" - versionNameSuffix = "-Dev" + isDebuggable = false + isShrinkResources = true + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) - isDebuggable = true - isShrinkResources = false - isMinifyEnabled = false + resValue("string", "app_name", appBaseName) + resValue("string", "app_version", appLabel) - resValue("string", "app_name", "$appBaseName Debug") - resValue("string", "app_version", appDevLabel) + manifestPlaceholders["appIcon"] = mainIcon + manifestPlaceholders["appRoundIcon"] = mainRoundIcon + } - manifestPlaceholders["appIcon"] = devIcon - manifestPlaceholders["appRoundIcon"] = devRoundIcon + create("staging") { + if (rootProject.file("staging-keystore.properties").exists()) { + signingConfig = signingConfigs.getByName("staging") + } else { + logger.warn("WARNING: Staging build will not be signed as staging-keystore.properties is missing.") + // throw GradleException("Can't Sign Staging Build") } + + applicationIdSuffix = ".staging" + versionNameSuffix = "-Staging" + + isDebuggable = false + isShrinkResources = true + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + + resValue("string", "app_name", "$appBaseName Staging") + resValue("string", "app_version", appDevLabel) + + manifestPlaceholders["appIcon"] = devIcon + manifestPlaceholders["appRoundIcon"] = devRoundIcon + } + + getByName("debug") { + applicationIdSuffix = ".dev" + versionNameSuffix = "-Dev" + + isDebuggable = true + isShrinkResources = false + isMinifyEnabled = false + + resValue("string", "app_name", "$appBaseName Debug") + resValue("string", "app_version", appDevLabel) + + manifestPlaceholders["appIcon"] = devIcon + manifestPlaceholders["appRoundIcon"] = devRoundIcon } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/navigation/NavigationRoot.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/navigation/NavigationRoot.kt index 7bc32ae0..7407fc28 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/navigation/NavigationRoot.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/navigation/NavigationRoot.kt @@ -1,27 +1,69 @@ package com.jeeldobariya.passcodes.navigation import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.NavBackStack +import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay +import com.jeeldobariya.passcodes.core.feature_flags.FeatureFlagsSettings +import com.jeeldobariya.passcodes.core.feature_flags.featureFlagsDatastore import com.jeeldobariya.passcodes.core.navigation.Route import com.jeeldobariya.passcodes.password_manager.ui.PasswordManagerScreen import com.jeeldobariya.passcodes.password_manager.ui.SavePasswordScreen import com.jeeldobariya.passcodes.password_manager.ui.UpdatePasswordScreen import com.jeeldobariya.passcodes.ui.AboutScreen +import com.jeeldobariya.passcodes.ui.ClassicalMainScreen import com.jeeldobariya.passcodes.ui.MainScreen import com.jeeldobariya.passcodes.ui.SettingsScreen + @Composable -fun NavigationRoot() { - val backStack = rememberNavBackStack(Route.Home) +private fun ModernNavigationRoot(backStack: NavBackStack, navigateTo: (Route) -> Unit) { + NavDisplay( + backStack = backStack, + onBack = { + backStack.removeLastOrNull() + }, + entryDecorators = mutableListOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator() + ), + entryProvider = entryProvider { + entry { + MainScreen(navigateTo) + } - fun navigateTo(route: Route): Unit { - backStack.add(route) - } + entry { + SettingsScreen() + } + + entry { + AboutScreen() + } + + entry { + PasswordManagerScreen(navigateTo) + } + + entry { + SavePasswordScreen() + } + + entry { + UpdatePasswordScreen(it.id) + } + } + ) +} +@Composable +private fun ClassicNavigationRoot(backStack: NavBackStack, navigateTo: (Route) -> Unit) { NavDisplay( backStack = backStack, onBack = { @@ -33,7 +75,7 @@ fun NavigationRoot() { ), entryProvider = entryProvider { entry { - MainScreen(::navigateTo) + ClassicalMainScreen(navigateTo) } entry { @@ -45,7 +87,7 @@ fun NavigationRoot() { } entry { - PasswordManagerScreen(::navigateTo) + PasswordManagerScreen(navigateTo) } entry { @@ -58,3 +100,24 @@ fun NavigationRoot() { } ) } + +@Composable +fun NavigationRoot() { + val backStack = rememberNavBackStack(Route.Home) + + val flagDataStore = LocalContext.current.featureFlagsDatastore + val flagDatastoreState by flagDataStore.data.collectAsState( + FeatureFlagsSettings( + version = 0, + isPreviewFeaturesEnabled = false, + isPreviewLayoutEnabled = false + ) + ) + + fun navigateTo(route: Route): Unit { + backStack.add(route) + } + + if (flagDatastoreState.isPreviewLayoutEnabled) { ModernNavigationRoot(backStack, ::navigateTo) } + else { ClassicNavigationRoot(backStack, ::navigateTo) } +} diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainScreen.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainScreen.kt index 0b9933cf..5472fb96 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainScreen.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.material3.Card import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -111,6 +112,83 @@ fun MainScreen(navigateTo: (Route) -> Unit) { } } +@Composable +fun ClassicalMainScreen(navigateTo: (Route) -> Unit) { + Scaffold { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 24.dp, vertical = 64.dp), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Top section + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(R.drawable.ic_passcodes), + contentDescription = "Passcodes Icon" + ) + + Spacer(Modifier.height(32.dp)) + + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.displaySmall + ) + + Text( + text = stringResource(R.string.app_version), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + // Middle actions (primary content) + Card( + modifier = Modifier + .scale(1.25f) + .width(IntrinsicSize.Max), + shape = MaterialTheme.shapes.extraLarge + ) { + Column( + modifier = Modifier.padding(24.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Button( + onClick = { navigateTo(Route.PasswordManager) }, + modifier = Modifier.fillMaxWidth() + ) { + Icon(imageVector = Icons.Default.Lock, contentDescription = "lock") + Spacer(modifier = Modifier.padding(4.dp)) + Text(text = stringResource(R.string.password_manager_button_text), style = MaterialTheme.typography.bodyLarge) + } + + OutlinedButton( + onClick = { navigateTo(Route.Settings) }, + modifier = Modifier.fillMaxWidth() + ) { + Icon(imageVector = Icons.Default.Settings, contentDescription = "settings") + Spacer(modifier = Modifier.padding(4.dp)) + Text(stringResource(R.string.setting_button_text), style = MaterialTheme.typography.bodyLarge) + } + + OutlinedButton( + onClick = { navigateTo(Route.AboutUs) }, + modifier = Modifier.fillMaxWidth() + ) { + Icon(imageVector = Icons.Default.Info, contentDescription = "info") + Spacer(modifier = Modifier.padding(4.dp)) + Text(stringResource(R.string.about_us_button_text), style = MaterialTheme.typography.bodyLarge) + } + } + } + } + } +} + @Preview(showBackground = true) @PreviewLightDark diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3dc92b52..2554dea3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ material = "1.13.0" nav3Core = "1.0.0" lifecycleViewmodelNav3 = "2.10.0" okhttp = "5.3.2" -oss-license = "17.3.0" +oss-license = "17.4.0" appcompat = "1.7.1" room = "2.8.4" json = "20251224" @@ -15,16 +15,16 @@ androidx-test-ext-junit = "1.3.0" coroutines = "1.10.2" lifecycle = "2.10.0" koin = "4.1.1" -composeBom = "2026.01.00" -compose-activity = "1.12.2" +composeBom = "2026.01.01" +compose-activity = "1.12.3" compose-viewmodel = "2.10.0" datastorePreferences = "1.2.0" kotlinSerializationJson = "1.10.0" # Plugin versions agp = "9.0.0" -ksp = "2.3.4" -kotlin = "2.3.0" +ksp = "2.3.5" +kotlin = "2.3.10" oss-license-plugin = "0.10.10" # Also update in settings.gradle.kts diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 19a6bdeb..37f78a6a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME