From f8ae98798b68b92448f3ffe24471eeaa1f1db965 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Fri, 19 Sep 2025 21:13:45 +0700 Subject: [PATCH 01/26] Chore chore/bump-dependencies: Update room & dagger version, ensure the app crud still functioning properly --- app/build.gradle | 8 +++----- build.gradle | 9 ++++++++- settings.gradle | 2 ++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a57ae88..89ac6a4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,7 +105,6 @@ dependencies { implementation 'androidx.test.ext:junit-ktx:1.3.0' // Room - def room_version = "2.7.0" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" androidTestImplementation "androidx.room:room-testing:$room_version" @@ -124,7 +123,6 @@ dependencies { androidTestImplementation "androidx.test.ext:junit:1.3.0" // Hilt - def dagger_version = "2.56.2" implementation "com.google.dagger:hilt-android:$dagger_version" kapt "com.google.dagger:hilt-android-compiler:$dagger_version" implementation "androidx.hilt:hilt-navigation-compose:1.2.0" @@ -150,9 +148,9 @@ dependencies { androidTestImplementation "androidx.test.espresso:espresso-intents:3.7.0" androidTestImplementation "androidx.test.espresso:espresso-core:3.7.0" - androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.9.0" - debugImplementation "androidx.compose.ui:ui-tooling:1.9.0" - debugImplementation "androidx.compose.ui:ui-test-manifest:1.9.0" + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_test_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_test_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_test_version" /// Hilt test (for handling service locator when test) androidTestImplementation "com.google.dagger:hilt-android-testing:$dagger_version" diff --git a/build.gradle b/build.gradle index eeb2e61..704c8d3 100644 --- a/build.gradle +++ b/build.gradle @@ -8,10 +8,17 @@ buildscript { // classpath 'com.google.firebase:perf-plugin:2.0.1' } } + // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id 'com.android.application' version '8.12.0' apply false id 'com.android.library' version '8.12.0' apply false id 'org.jetbrains.kotlin.android' version '2.2.10' apply false - id 'com.google.dagger.hilt.android' version '2.56.2' apply false + id 'com.google.dagger.hilt.android' version '2.57.1' apply false } + +ext { + compose_ui_test_version = "1.9.1" + dagger_version = "2.57.1" + room_version = "2.8.0" +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 0697020..74aa464 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,7 @@ pluginManagement { gradlePluginPortal() } } + dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { @@ -12,5 +13,6 @@ dependencyResolutionManagement { mavenCentral() } } + rootProject.name = "VentNote" include ':app' From f429c35feb8ba33d2877e18334b5eeb2a50c5796 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Fri, 19 Sep 2025 22:31:39 +0700 Subject: [PATCH 02/26] Chore chore/bump-dependencies: move all version to ext variables --- app/build.gradle | 78 ++++++++++++++++++++++-------------------------- build.gradle | 26 ++++++++++++++++ 2 files changed, 62 insertions(+), 42 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 89ac6a4..1a47911 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -85,24 +85,23 @@ android { dependencies { - implementation "androidx.core:core-ktx:1.17.0" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.9.2" + implementation "androidx.core:core-ktx:$core_ktx_version" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_ktx_version" - implementation "androidx.activity:activity-compose:1.10.1" + implementation "androidx.activity:activity-compose:$activity_compose_version" // Jetpack Compose - implementation "androidx.compose.runtime:runtime-livedata:1.9.0" - implementation "androidx.compose.ui:ui:1.9.0" - implementation "androidx.compose.ui:ui-tooling-preview:1.9.0" + implementation "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version" + implementation "androidx.compose.ui:ui:$jetpack_compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version" - implementation "androidx.compose.material3:material3:1.3.2" + implementation "androidx.compose.material3:material3:$material3_version" // Lifecycle Livedata - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.2" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.9.2" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_ktx_version" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_ktx_version" - implementation "androidx.compose.compiler:compiler:1.5.15" - implementation 'androidx.test.ext:junit-ktx:1.3.0' + implementation "androidx.compose.compiler:compiler:$compose_compiler_version" // Room implementation "androidx.room:room-ktx:$room_version" @@ -110,43 +109,42 @@ dependencies { androidTestImplementation "androidx.room:room-testing:$room_version" // Datastore - implementation("androidx.datastore:datastore-preferences:1.1.7") + implementation "androidx.datastore:datastore-preferences:$datastore_preference_version" // Compose Navigation - implementation "androidx.navigation:navigation-compose:2.9.3" + implementation "androidx.navigation:navigation-compose:$navigation_compose_version" // Coroutines - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2" - - // Unit test - testImplementation "junit:junit:4.13.2" - androidTestImplementation "androidx.test.ext:junit:1.3.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Hilt implementation "com.google.dagger:hilt-android:$dagger_version" kapt "com.google.dagger:hilt-android-compiler:$dagger_version" - implementation "androidx.hilt:hilt-navigation-compose:1.2.0" + implementation "androidx.hilt:hilt-navigation-compose:$hilt_navigation_compose_version" // Material Icon Extension - implementation "androidx.compose.material:material-icons-extended:1.7.8" + implementation "androidx.compose.material:material-icons-extended:$material_icons_version" // So, make sure you also include that repository in your project's build.gradle file. - implementation("com.google.android.play:app-update:2.1.0") + implementation "com.google.android.play:app-update:$play_app_update_version" // For Kotlin users also import the Kotlin extensions library for Play In-App Update: - implementation("com.google.android.play:app-update-ktx:2.1.0") + implementation "com.google.android.play:app-update-ktx:$play_app_update_version" // Google Play API - implementation "com.google.android.gms:play-services-auth:21.4.0" + implementation "com.google.android.gms:play-services-auth:$play_services_auth_version" // Accompanist - Status Bar - implementation "com.google.accompanist:accompanist-systemuicontroller:0.36.0" + implementation "com.google.accompanist:accompanist-systemuicontroller:$systemuicontroller_version" + + // Unit test + testImplementation "junit:junit:$junit_version" // Instrumented test /// Espresso (for ui interaction purpose) - androidTestImplementation "androidx.test:runner:1.7.0" - androidTestImplementation "androidx.test:rules:1.7.0" - androidTestImplementation "androidx.test.espresso:espresso-intents:3.7.0" - androidTestImplementation "androidx.test.espresso:espresso-core:3.7.0" + androidTestImplementation "androidx.test:runner:$test_version" + androidTestImplementation "androidx.test:rules:$test_version" + androidTestImplementation "androidx.test.espresso:espresso-intents:$test_espresso_version" + androidTestImplementation "androidx.test.espresso:espresso-core:$test_espresso_version" androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_test_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_test_version" @@ -157,31 +155,27 @@ dependencies { kaptAndroidTest "com.google.dagger:hilt-android-compiler:$dagger_version" // For mocking purposes & make it visible in instrumented test - testImplementation "org.mockito.kotlin:mockito-kotlin:6.0.0" - testImplementation "org.mockito:mockito-inline:5.2.0" + testImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_version" + testImplementation "org.mockito:mockito-inline:$mockito_inline_version" - androidTestImplementation "androidx.test.ext:junit-ktx:1.3.0" + androidTestImplementation "androidx.test.ext:junit-ktx:$junit_ktx_version" - testImplementation "androidx.arch.core:core-testing:2.2.0" + testImplementation "androidx.arch.core:core-testing:$core_testing_version" // For unit testing coroutines - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_test_version" // Import the Firebase BoM - implementation platform("com.google.firebase:firebase-bom:34.1.0") + implementation platform("com.google.firebase:firebase-bom:$firebase_bom_version") // When using the BoM, you don't specify versions in Firebase library dependencies // Add the dependency for the Firebase SDK for Google Analytics implementation "com.google.firebase:firebase-analytics" - implementation "com.google.firebase:firebase-crashlytics:20.0.0" - implementation "com.google.firebase:firebase-perf-ktx:21.0.5" - - //Google sign in - implementation "com.google.android.gms:play-services-auth:21.4.0" + implementation "com.google.firebase:firebase-crashlytics" //Google Drive API - implementation "com.google.http-client:google-http-client-gson:2.0.0" - implementation "com.google.apis:google-api-services-drive:v3-rev136-1.25.0" - implementation "com.google.api-client:google-api-client-android:2.8.1" + implementation "com.google.http-client:google-http-client-gson:$http_client_gson_version" + implementation "com.google.apis:google-api-services-drive:$google_drive_service_version" + implementation "com.google.api-client:google-api-client-android:$google_api_client_version" } kapt { diff --git a/build.gradle b/build.gradle index 704c8d3..e86ebff 100644 --- a/build.gradle +++ b/build.gradle @@ -21,4 +21,30 @@ ext { compose_ui_test_version = "1.9.1" dagger_version = "2.57.1" room_version = "2.8.0" + test_version = "1.7.0" + test_espresso_version = "3.7.0" + mockito_version = "6.0.0" + mockito_inline_version = "5.2.0" + junit_ktx_version = "1.3.0" + junit_version = "4.13.2" + core_testing_version = "2.2.0" + coroutines_test_version = "1.10.2" + systemuicontroller_version = "0.36.0" + play_services_auth_version = "21.4.0" + play_app_update_version = "2.1.0" + firebase_bom_version = "34.3.0" + material_icons_version = "1.7.8" + http_client_gson_version = "2.0.0" + google_drive_service_version = "v3-rev136-1.25.0" + google_api_client_version = "2.8.1" + kotlin_coroutines_version = "1.10.2" + datastore_preference_version = "1.1.7" + compose_compiler_version = "1.5.15" + material3_version = "1.3.2" + core_ktx_version = "1.17.0" + navigation_compose_version = "2.9.3" + hilt_navigation_compose_version = "1.2.0" + lifecycle_ktx_version = "2.9.2" + activity_compose_version = "1.10.1" + jetpack_compose_version = "1.9.0" } \ No newline at end of file From 78bf94c89541802de13912af4fbd1b717941fd05 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Fri, 19 Sep 2025 22:34:27 +0700 Subject: [PATCH 03/26] Chore chore/bump-dependencies: update hilt navigation compose & lifecycle viewmodel --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e86ebff..b32a430 100644 --- a/build.gradle +++ b/build.gradle @@ -43,8 +43,8 @@ ext { material3_version = "1.3.2" core_ktx_version = "1.17.0" navigation_compose_version = "2.9.3" - hilt_navigation_compose_version = "1.2.0" - lifecycle_ktx_version = "2.9.2" + hilt_navigation_compose_version = "1.3.0" + lifecycle_ktx_version = "2.9.4" activity_compose_version = "1.10.1" jetpack_compose_version = "1.9.0" } \ No newline at end of file From 43a09fb418aa399b1899969ecc7eeffff86c27b6 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Fri, 19 Sep 2025 22:39:44 +0700 Subject: [PATCH 04/26] Chore chore/bump-dependencies: update activity compose version --- app/build.gradle | 1 - build.gradle | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1a47911..ded4cf4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,7 +9,6 @@ plugins { // disabled for internal purpose (if you want to enable, you must create firebase project first) // id 'com.google.gms.google-services' // id 'com.google.firebase.crashlytics' -// id 'com.google.firebase.firebase-perf' } android { diff --git a/build.gradle b/build.gradle index b32a430..8d9d142 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,6 @@ buildscript { // disable for staging purpose // classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.6' // classpath 'com.google.gms:google-services:4.4.3' -// classpath 'com.google.firebase:perf-plugin:2.0.1' } } @@ -45,6 +44,6 @@ ext { navigation_compose_version = "2.9.3" hilt_navigation_compose_version = "1.3.0" lifecycle_ktx_version = "2.9.4" - activity_compose_version = "1.10.1" + activity_compose_version = "1.11.0" jetpack_compose_version = "1.9.0" } \ No newline at end of file From 22dbc693704a9177da6fd3b368cadad0eb9bdc70 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sat, 20 Sep 2025 14:13:33 +0700 Subject: [PATCH 05/26] Feat bugfix/theme-and-ui: Refactor scheme & color changer feature to viewmodel & component, modularize nav drawer --- .../com/digiventure/ventnote/MainActivity.kt | 8 +- .../ventnote/feature/drawer/NavDrawer.kt | 149 +++++++ .../drawer/components/NavDrawerColorPicker.kt | 97 +++++ .../drawer/components/NavDrawerItem.kt | 73 ++++ .../NavDrawerItemColorSchemeSwitch.kt | 106 +++++ .../feature/drawer/components/SectionTitle.kt | 46 ++ .../components/drawer/NavigationDrawer.kt | 411 ------------------ .../ventnote/module/ApplicationModule.kt | 9 + .../ventnote/ui/ColorSchemeChoice.kt | 152 +++---- .../digiventure/ventnote/ui/theme/Theme.kt | 87 ---- .../ui/theme/{ => components}/Color.kt | 2 +- .../ventnote/ui/theme/components/Theme.kt | 39 ++ .../ui/theme/{ => components}/Type.kt | 2 +- .../ui/theme/viewmodel/ThemeBaseVM.kt | 26 ++ .../ui/theme/viewmodel/ThemeMockVM.kt | 25 ++ .../ventnote/ui/theme/viewmodel/ThemeVM.kt | 80 ++++ 16 files changed, 731 insertions(+), 581 deletions(-) create mode 100644 app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt create mode 100644 app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerColorPicker.kt create mode 100644 app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItem.kt create mode 100644 app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItemColorSchemeSwitch.kt create mode 100644 app/src/main/java/com/digiventure/ventnote/feature/drawer/components/SectionTitle.kt delete mode 100644 app/src/main/java/com/digiventure/ventnote/feature/notes/components/drawer/NavigationDrawer.kt delete mode 100644 app/src/main/java/com/digiventure/ventnote/ui/theme/Theme.kt rename app/src/main/java/com/digiventure/ventnote/ui/theme/{ => components}/Color.kt (98%) create mode 100644 app/src/main/java/com/digiventure/ventnote/ui/theme/components/Theme.kt rename app/src/main/java/com/digiventure/ventnote/ui/theme/{ => components}/Type.kt (87%) create mode 100644 app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeBaseVM.kt create mode 100644 app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeMockVM.kt create mode 100644 app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeVM.kt diff --git a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt index f535e7a..1cc216f 100644 --- a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt +++ b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt @@ -18,10 +18,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.navigation.compose.rememberNavController import com.digiventure.ventnote.components.dialog.TextDialog -import com.digiventure.ventnote.feature.notes.components.drawer.NavDrawer +import com.digiventure.ventnote.feature.drawer.NavDrawer import com.digiventure.ventnote.navigation.NavGraph import com.digiventure.ventnote.navigation.PageNavigation -import com.digiventure.ventnote.ui.theme.VentNoteTheme +import com.digiventure.ventnote.ui.theme.components.VentNoteTheme import com.google.android.play.core.appupdate.AppUpdateInfo import com.google.android.play.core.appupdate.AppUpdateManager import com.google.android.play.core.appupdate.AppUpdateManagerFactory @@ -63,9 +63,7 @@ class MainActivity : ComponentActivity() { ) { NavDrawer( drawerState = drawerState, - onError = { - - }, + onError = {}, onBackupPressed = { navigationActions.navigateToBackupPage() }, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt b/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt new file mode 100644 index 0000000..e50dc97 --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt @@ -0,0 +1,149 @@ +package com.digiventure.ventnote.feature.drawer + +import android.annotation.SuppressLint +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Bedtime +import androidx.compose.material.icons.filled.CloudUpload +import androidx.compose.material.icons.filled.ColorLens +import androidx.compose.material.icons.filled.Shop +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.filled.Update +import androidx.compose.material3.DrawerDefaults +import androidx.compose.material3.DrawerState +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.rememberDrawerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.net.toUri +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.digiventure.ventnote.BuildConfig +import com.digiventure.ventnote.R +import com.digiventure.ventnote.commons.TestTags +import com.digiventure.ventnote.feature.drawer.components.NavDrawerColorPicker +import com.digiventure.ventnote.feature.drawer.components.NavDrawerItem +import com.digiventure.ventnote.feature.drawer.components.NavDrawerItemColorSchemeSwitch +import com.digiventure.ventnote.feature.drawer.components.SectionTitle +import com.digiventure.ventnote.ui.theme.viewmodel.ThemeBaseVM +import com.digiventure.ventnote.ui.theme.viewmodel.ThemeMockVM +import com.digiventure.ventnote.ui.theme.viewmodel.ThemeVM + +private const val appPath = "https://play.google.com/store/apps/details?id=com.digiventure.ventnote" +private const val devPagePath = "https://play.google.com/store/apps/developer?id=Mattrmost" + +private fun openPlayStore(context: Context, appURL: String, onError: (String) -> Unit) { + val playIntent: Intent = Intent().apply { + action = Intent.ACTION_VIEW + data = appURL.toUri() + } + try { + context.startActivity(playIntent) + } catch (e: ActivityNotFoundException) { + onError("Cannot open URL: Play Store not found or no app can handle this action.") + } catch (e: Exception) { + onError("Cannot open URL") + } +} + + +@Composable +fun NavDrawer( + drawerState: DrawerState, + content: @Composable () -> Unit, + onError: (String) -> Unit, + onBackupPressed: () -> Unit, + themeViewModel: ThemeBaseVM = hiltViewModel() +) { + val context = LocalContext.current + val configuration = LocalConfiguration.current + + val screenWidth = configuration.screenWidthDp.dp + + var currentSchemeName by themeViewModel.currentColorSchemeName + + ModalNavigationDrawer(drawerState = drawerState, drawerContent = { + ModalDrawerSheet( + drawerShape = DrawerDefaults.shape, + drawerContainerColor = MaterialTheme.colorScheme.background, + modifier = Modifier + .fillMaxHeight() + .padding(0.dp) + .width(screenWidth - 60.dp) + ) { + SectionTitle(title = stringResource(id = R.string.about_us)) + + NavDrawerItem(leftIcon = Icons.Filled.Star, + title = stringResource(id = R.string.rate_app), + subtitle = stringResource(id = R.string.rate_app_description), + testTagName = TestTags.RATE_APP_TILE, + onClick = { openPlayStore(context, appPath, onError) }) + + NavDrawerItem(leftIcon = Icons.Filled.Shop, + title = stringResource(id = R.string.more_apps), + subtitle = stringResource(id = R.string.more_apps_description), + onClick = { openPlayStore(context, devPagePath, onError) }) + + NavDrawerItem(leftIcon = Icons.Filled.Update, + title = stringResource(id = R.string.app_version), + subtitle = BuildConfig.VERSION_NAME, + onClick = { }) + + SectionTitle(title = stringResource(id = R.string.preferences)) + + NavDrawerColorPicker( + leftIcon = Icons.Filled.ColorLens, + title = stringResource(id = R.string.theme_color), + ) { + themeViewModel.updateColorPallet(it.second) + } + + NavDrawerItemColorSchemeSwitch( + leftIcon = Icons.Filled.Bedtime, + title = stringResource(id = R.string.theme_setting), + currentScheme = currentSchemeName, + ) { + themeViewModel.updateColorScheme(it) + } + + SectionTitle(title = stringResource(id = R.string.settings)) + + NavDrawerItem( + leftIcon = Icons.Filled.CloudUpload, + title = stringResource(id = R.string.backup), + subtitle = stringResource(id = R.string.backup_description), + onClick = { onBackupPressed() }) + } + }, content = { content() }, modifier = Modifier.semantics { testTag = TestTags.NAV_DRAWER }) +} + +@Preview +@Composable +@SuppressLint("ViewModelConstructorInComposable") +fun DrawerPreview() { + val drawerState = rememberDrawerState(DrawerValue.Open) + NavDrawer( + drawerState = drawerState, + content = { }, + onError = {}, + onBackupPressed = {}, + themeViewModel = ThemeMockVM() + ) +} + diff --git a/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerColorPicker.kt b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerColorPicker.kt new file mode 100644 index 0000000..61326a6 --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerColorPicker.kt @@ -0,0 +1,97 @@ +package com.digiventure.ventnote.feature.drawer.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.digiventure.ventnote.commons.ColorPalletName +import com.digiventure.ventnote.commons.Constants.EMPTY_STRING +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenLightPrimary +import com.digiventure.ventnote.ui.theme.components.CobaltBlueLightPrimary +import com.digiventure.ventnote.ui.theme.components.CrimsonLightPrimary +import com.digiventure.ventnote.ui.theme.components.PurpleLightPrimary + +@Composable +fun NavDrawerColorPicker( + leftIcon: ImageVector, + title: String, + testTagName: String = EMPTY_STRING, + onColorPicked: (color: Pair) -> Unit +) { + val colorList = listOf( + Pair(PurpleLightPrimary, ColorPalletName.PURPLE), + Pair(CrimsonLightPrimary, ColorPalletName.CRIMSON), + Pair(CadmiumGreenLightPrimary, ColorPalletName.CADMIUM_GREEN), + Pair(CobaltBlueLightPrimary, ColorPalletName.COBALT_BLUE) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 12.dp, top = 8.dp) + .semantics { testTag = testTagName }, verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(MaterialTheme.colorScheme.background) + ) { + Box(modifier = Modifier.padding(8.dp)) { + Icon( + leftIcon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(20.dp) + ) + } + } + + Column( + modifier = Modifier + .padding(start = 12.dp, bottom = 1.dp) + .weight(1f) + ) { + Text( + text = title, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 2.dp) + ) + Row { + colorList.forEach { + Box(modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .width(24.dp) + .height(24.dp) + .background(it.first) + .clickable { + onColorPicked(it) + }) + Box(modifier = Modifier.padding(start = 2.dp, end = 2.dp)) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItem.kt b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItem.kt new file mode 100644 index 0000000..55c02fd --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItem.kt @@ -0,0 +1,73 @@ +package com.digiventure.ventnote.feature.drawer.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.digiventure.ventnote.commons.Constants.EMPTY_STRING + +@Composable +fun NavDrawerItem( + leftIcon: ImageVector, title: String, subtitle: String, testTagName: String = EMPTY_STRING, onClick: () -> Unit +) { + Row(modifier = Modifier + .fillMaxWidth() + .clickable { onClick() } + .padding(start = 16.dp, end = 16.dp, bottom = 12.dp, top = 8.dp) + .semantics { testTag = testTagName }, verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(MaterialTheme.colorScheme.background) + ) { + Box(modifier = Modifier.padding(8.dp)) { + Icon( + leftIcon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(20.dp) + ) + } + } + + Column( + modifier = Modifier + .padding(start = 12.dp, bottom = 1.dp) + .weight(1f) + ) { + Text( + text = title, + modifier = Modifier.padding(bottom = 2.dp), + style = MaterialTheme.typography.titleSmall.copy( + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + ) + ) + Text( + text = subtitle, + style = MaterialTheme.typography.bodySmall.copy( + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.onSurface, + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItemColorSchemeSwitch.kt b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItemColorSchemeSwitch.kt new file mode 100644 index 0000000..c580a98 --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItemColorSchemeSwitch.kt @@ -0,0 +1,106 @@ +package com.digiventure.ventnote.feature.drawer.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.digiventure.ventnote.R +import com.digiventure.ventnote.commons.ColorSchemeName +import com.digiventure.ventnote.commons.Constants.EMPTY_STRING + +@Composable +fun NavDrawerItemColorSchemeSwitch( + leftIcon: ImageVector, + title: String, + testTagName: String = EMPTY_STRING, + currentScheme: String, + onColorSchemePicked: (scheme: String) -> Unit +) { + val lightModeString = stringResource(R.string.switch_to_light_mode) + val darkModeString = stringResource(R.string.switch_to_dark_mode) + val subtitle = if (currentScheme == ColorSchemeName.DARK_MODE) + lightModeString else darkModeString + + + fun onTileClicked() { + if (subtitle == darkModeString) { + onColorSchemePicked(ColorSchemeName.DARK_MODE) + } else { + onColorSchemePicked(ColorSchemeName.LIGHT_MODE) + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 12.dp, top = 8.dp) + .semantics { testTag = testTagName } + .clickable { + onTileClicked() + }, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(MaterialTheme.colorScheme.background) + ) { + Box(modifier = Modifier.padding(8.dp)) { + Icon( + leftIcon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(20.dp) + ) + } + } + + Column( + modifier = Modifier + .padding(start = 12.dp, bottom = 1.dp) + .weight(1f) + ) { + Text( + text = title, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 2.dp) + ) + Text( + text = subtitle, + fontSize = 12.sp, + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.onSurface + ) + } + + Icon( + Icons.Filled.ChevronRight, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(20.dp) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/SectionTitle.kt b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/SectionTitle.kt new file mode 100644 index 0000000..171d51e --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/SectionTitle.kt @@ -0,0 +1,46 @@ +package com.digiventure.ventnote.feature.drawer.components + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import java.util.Locale + +@Composable +fun SectionTitle(title: String) { + val firstLetterColor = MaterialTheme.colorScheme.primary + val restLetterColor = MaterialTheme.colorScheme.onSurface + + val modifiedTitle = title.replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() + } + + Text( + buildAnnotatedString { + withStyle( + style = SpanStyle( + color = firstLetterColor, fontWeight = FontWeight.ExtraBold, fontSize = 24.sp + ) + ) { + append(modifiedTitle.first()) + } + withStyle( + style = SpanStyle( + color = restLetterColor, fontWeight = FontWeight.Bold, fontSize = 22.sp + ) + ) { + append(modifiedTitle.substring(1)) + } + }, + modifier = Modifier.padding( + start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp + ), + ) +} diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/drawer/NavigationDrawer.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/drawer/NavigationDrawer.kt deleted file mode 100644 index 96f5e32..0000000 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/drawer/NavigationDrawer.kt +++ /dev/null @@ -1,411 +0,0 @@ -package com.digiventure.ventnote.feature.notes.components.drawer - -import android.content.Intent -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Bedtime -import androidx.compose.material.icons.filled.ChevronRight -import androidx.compose.material.icons.filled.CloudUpload -import androidx.compose.material.icons.filled.ColorLens -import androidx.compose.material.icons.filled.Shop -import androidx.compose.material.icons.filled.Star -import androidx.compose.material.icons.filled.Update -import androidx.compose.material3.DrawerDefaults -import androidx.compose.material3.DrawerState -import androidx.compose.material3.DrawerValue -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalDrawerSheet -import androidx.compose.material3.ModalNavigationDrawer -import androidx.compose.material3.Text -import androidx.compose.material3.rememberDrawerState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.net.toUri -import com.digiventure.ventnote.BuildConfig -import com.digiventure.ventnote.R -import com.digiventure.ventnote.commons.ColorPalletName -import com.digiventure.ventnote.commons.ColorSchemeName -import com.digiventure.ventnote.commons.Constants -import com.digiventure.ventnote.commons.TestTags -import com.digiventure.ventnote.data.local.NoteDataStore -import com.digiventure.ventnote.ui.theme.CadmiumGreenLightPrimary -import com.digiventure.ventnote.ui.theme.CobaltBlueLightPrimary -import com.digiventure.ventnote.ui.theme.CrimsonLightPrimary -import com.digiventure.ventnote.ui.theme.PurpleLightPrimary -import kotlinx.coroutines.launch -import java.util.Locale - -@Composable -fun NavDrawer( - drawerState: DrawerState, - content: @Composable () -> Unit, - onError: (String) -> Unit, - onBackupPressed: () -> Unit -) { - val context = LocalContext.current - val configuration = LocalConfiguration.current - - val screenWidth = configuration.screenWidthDp.dp - - fun openPlayStore(appURL: String) { - val playIntent: Intent = Intent().apply { - action = Intent.ACTION_VIEW - data = appURL.toUri() - } - try { - context.startActivity(playIntent) - } catch (e: Exception) { - onError("Cannot Open URL") - } - } - - val appPath = "https://play.google.com/store/apps/details?id=com.digiventure.ventnote" - val devPagePath = "https://play.google.com/store/apps/developer?id=Mattrmost" - - val dataStore = NoteDataStore(LocalContext.current) - - var currentScheme by remember { mutableStateOf("") } - - LaunchedEffect(key1 = Unit) { - dataStore.getStringData(Constants.COLOR_SCHEME).collect { - currentScheme = it - } - } - - val scope = rememberCoroutineScope() - - fun setColorPallet(colorPallet: String) { - scope.launch { - dataStore.setStringData(Constants.COLOR_PALLET, colorPallet) - } - } - - fun setColorScheme(colorScheme: String) { - scope.launch { - dataStore.setStringData(Constants.COLOR_SCHEME, colorScheme) - } - } - - ModalNavigationDrawer(drawerState = drawerState, drawerContent = { - ModalDrawerSheet( - drawerShape = DrawerDefaults.shape, - drawerContainerColor = MaterialTheme.colorScheme.background, - modifier = Modifier - .fillMaxHeight() - .padding(0.dp) - .width(screenWidth - 60.dp) - ) { - SectionTitle(title = stringResource(id = R.string.about_us)) - - NavDrawerItem(leftIcon = Icons.Filled.Star, - title = stringResource(id = R.string.rate_app), - subtitle = stringResource(id = R.string.rate_app_description), - testTagName = TestTags.RATE_APP_TILE, - onClick = { openPlayStore(appPath) }) - - NavDrawerItem(leftIcon = Icons.Filled.Shop, - title = stringResource(id = R.string.more_apps), - subtitle = stringResource(id = R.string.more_apps_description), - testTagName = "", - onClick = { openPlayStore(devPagePath) }) - - NavDrawerItem(leftIcon = Icons.Filled.Update, - title = stringResource(id = R.string.app_version), - subtitle = BuildConfig.VERSION_NAME, - testTagName = "", - onClick = { }) - - SectionTitle(title = stringResource(id = R.string.preferences)) - - NavDrawerColorPicker( - leftIcon = Icons.Filled.ColorLens, - title = stringResource(id = R.string.theme_color), - testTagName = "" - ) { - setColorPallet(it.second) - } - - NavDrawerItemColorSchemeSwitch( - leftIcon = Icons.Filled.Bedtime, - title = stringResource(id = R.string.theme_setting), - currentScheme = currentScheme, - testTagName = "", - ) { - setColorScheme(it) - } - - SectionTitle(title = stringResource(id = R.string.settings)) - - NavDrawerItem( - leftIcon = Icons.Filled.CloudUpload, - title = stringResource(id = R.string.backup), - subtitle = stringResource(id = R.string.backup_description), - testTagName = "", - onClick = { onBackupPressed() }) - } - }, content = { content() }, modifier = Modifier.semantics { testTag = TestTags.NAV_DRAWER }) -} - - -@Composable -fun SectionTitle(title: String) { - val firstLetterColor = MaterialTheme.colorScheme.primary - val restLetterColor = MaterialTheme.colorScheme.onSurface - - val modifiedTitle = title.replaceFirstChar { - if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() - } - - Text( - buildAnnotatedString { - withStyle( - style = SpanStyle( - color = firstLetterColor, fontWeight = FontWeight.ExtraBold, fontSize = 24.sp - ) - ) { - append(modifiedTitle.first()) - } - withStyle( - style = SpanStyle( - color = restLetterColor, fontWeight = FontWeight.Bold, fontSize = 22.sp - ) - ) { - append(modifiedTitle.substring(1)) - } - }, - modifier = Modifier.padding( - start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp - ), - ) -} - -@Composable -fun NavDrawerItem( - leftIcon: ImageVector, title: String, subtitle: String, testTagName: String, onClick: () -> Unit -) { - Row(modifier = Modifier - .fillMaxWidth() - .clickable { onClick() } - .padding(start = 16.dp, end = 16.dp, bottom = 12.dp, top = 8.dp) - .semantics { testTag = testTagName }, verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .background(MaterialTheme.colorScheme.background) - ) { - Box(modifier = Modifier.padding(8.dp)) { - Icon( - leftIcon, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(20.dp) - ) - } - } - - Column( - modifier = Modifier - .padding(start = 12.dp, bottom = 1.dp) - .weight(1f) - ) { - Text( - text = title, - modifier = Modifier.padding(bottom = 2.dp), - style = MaterialTheme.typography.titleSmall.copy( - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - ) - ) - Text( - text = subtitle, - style = MaterialTheme.typography.bodySmall.copy( - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.onSurface, - ) - ) - } - } -} - -@Composable -fun NavDrawerColorPicker( - leftIcon: ImageVector, - title: String, - testTagName: String, - onColorPicked: (color: Pair) -> Unit -) { - val colorList = listOf( - Pair(PurpleLightPrimary, ColorPalletName.PURPLE), - Pair(CrimsonLightPrimary, ColorPalletName.CRIMSON), - Pair(CadmiumGreenLightPrimary, ColorPalletName.CADMIUM_GREEN), - Pair(CobaltBlueLightPrimary, ColorPalletName.COBALT_BLUE) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 12.dp, top = 8.dp) - .semantics { testTag = testTagName }, verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .background(MaterialTheme.colorScheme.background) - ) { - Box(modifier = Modifier.padding(8.dp)) { - Icon( - leftIcon, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(20.dp) - ) - } - } - - Column( - modifier = Modifier - .padding(start = 12.dp, bottom = 1.dp) - .weight(1f) - ) { - Text( - text = title, - fontSize = 14.sp, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(bottom = 2.dp) - ) - Row { - colorList.forEach { - Box(modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .width(24.dp) - .height(24.dp) - .background(it.first) - .clickable { - onColorPicked(it) - }) - Box(modifier = Modifier.padding(start = 2.dp, end = 2.dp)) - } - } - } - } -} - -@Composable -fun NavDrawerItemColorSchemeSwitch( - leftIcon: ImageVector, - title: String, - testTagName: String, - currentScheme: String, - onColorSchemePicked: (scheme: String) -> Unit -) { - val lightModeString = stringResource(R.string.switch_to_light_mode) - val darkModeString = stringResource(R.string.switch_to_dark_mode) - val subtitle = if (currentScheme == ColorSchemeName.DARK_MODE) - lightModeString else darkModeString - - - fun onTileClicked() { - if (subtitle == darkModeString) { - onColorSchemePicked(ColorSchemeName.DARK_MODE) - } else { - onColorSchemePicked(ColorSchemeName.LIGHT_MODE) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 12.dp, top = 8.dp) - .semantics { testTag = testTagName } - .clickable { - onTileClicked() - }, - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .background(MaterialTheme.colorScheme.background) - ) { - Box(modifier = Modifier.padding(8.dp)) { - Icon( - leftIcon, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(20.dp) - ) - } - } - - Column( - modifier = Modifier - .padding(start = 12.dp, bottom = 1.dp) - .weight(1f) - ) { - Text( - text = title, - fontSize = 14.sp, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(bottom = 2.dp) - ) - Text( - text = subtitle, - fontSize = 12.sp, - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.onSurface - ) - } - - Icon( - Icons.Filled.ChevronRight, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(20.dp) - ) - } -} - -@Preview -@Composable -fun DrawerPreview() { - val drawerState = rememberDrawerState(DrawerValue.Open) - NavDrawer(drawerState = drawerState, content = { }, onError = {}, onBackupPressed = {}) -} - diff --git a/app/src/main/java/com/digiventure/ventnote/module/ApplicationModule.kt b/app/src/main/java/com/digiventure/ventnote/module/ApplicationModule.kt index 49386b8..01a26b2 100644 --- a/app/src/main/java/com/digiventure/ventnote/module/ApplicationModule.kt +++ b/app/src/main/java/com/digiventure/ventnote/module/ApplicationModule.kt @@ -1,8 +1,11 @@ package com.digiventure.ventnote.module +import android.content.Context +import com.digiventure.ventnote.data.local.NoteDataStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExecutorCoroutineDispatcher @@ -21,4 +24,10 @@ class ApplicationModule { @Provides @Singleton fun provideExecutorCoroutineDispatcher(): ExecutorCoroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + + @Provides + @Singleton + fun provideNoteDataStore(@ApplicationContext context: Context): NoteDataStore { + return NoteDataStore(context) + } } \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/ui/ColorSchemeChoice.kt b/app/src/main/java/com/digiventure/ventnote/ui/ColorSchemeChoice.kt index 4f5001a..efe6375 100644 --- a/app/src/main/java/com/digiventure/ventnote/ui/ColorSchemeChoice.kt +++ b/app/src/main/java/com/digiventure/ventnote/ui/ColorSchemeChoice.kt @@ -5,82 +5,82 @@ import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import com.digiventure.ventnote.commons.ColorPalletName import com.digiventure.ventnote.commons.ColorSchemeName -import com.digiventure.ventnote.ui.theme.CadmiumGreenDarkPrimary -import com.digiventure.ventnote.ui.theme.CadmiumGreenDarkPrimaryContainer -import com.digiventure.ventnote.ui.theme.CadmiumGreenDarkSecondary -import com.digiventure.ventnote.ui.theme.CadmiumGreenDarkSecondaryContainer -import com.digiventure.ventnote.ui.theme.CadmiumGreenDarkTertiary -import com.digiventure.ventnote.ui.theme.CadmiumGreenDarkTertiaryContainer -import com.digiventure.ventnote.ui.theme.CadmiumGreenLightPrimary -import com.digiventure.ventnote.ui.theme.CadmiumGreenLightPrimaryContainer -import com.digiventure.ventnote.ui.theme.CadmiumGreenLightSecondary -import com.digiventure.ventnote.ui.theme.CadmiumGreenLightSecondaryContainer -import com.digiventure.ventnote.ui.theme.CadmiumGreenLightTertiary -import com.digiventure.ventnote.ui.theme.CadmiumGreenLightTertiaryContainer -import com.digiventure.ventnote.ui.theme.CobaltBlueDarkPrimary -import com.digiventure.ventnote.ui.theme.CobaltBlueDarkPrimaryContainer -import com.digiventure.ventnote.ui.theme.CobaltBlueDarkSecondary -import com.digiventure.ventnote.ui.theme.CobaltBlueDarkSecondaryContainer -import com.digiventure.ventnote.ui.theme.CobaltBlueDarkTertiary -import com.digiventure.ventnote.ui.theme.CobaltBlueDarkTertiaryContainer -import com.digiventure.ventnote.ui.theme.CobaltBlueLightPrimary -import com.digiventure.ventnote.ui.theme.CobaltBlueLightPrimaryContainer -import com.digiventure.ventnote.ui.theme.CobaltBlueLightSecondary -import com.digiventure.ventnote.ui.theme.CobaltBlueLightSecondaryContainer -import com.digiventure.ventnote.ui.theme.CobaltBlueLightTertiary -import com.digiventure.ventnote.ui.theme.CobaltBlueLightTertiaryContainer -import com.digiventure.ventnote.ui.theme.CrimsonDarkPrimary -import com.digiventure.ventnote.ui.theme.CrimsonDarkPrimaryContainer -import com.digiventure.ventnote.ui.theme.CrimsonDarkSecondary -import com.digiventure.ventnote.ui.theme.CrimsonDarkSecondaryContainer -import com.digiventure.ventnote.ui.theme.CrimsonDarkTertiary -import com.digiventure.ventnote.ui.theme.CrimsonDarkTertiaryContainer -import com.digiventure.ventnote.ui.theme.CrimsonLightPrimary -import com.digiventure.ventnote.ui.theme.CrimsonLightPrimaryContainer -import com.digiventure.ventnote.ui.theme.CrimsonLightSecondary -import com.digiventure.ventnote.ui.theme.CrimsonLightSecondaryContainer -import com.digiventure.ventnote.ui.theme.CrimsonLightTertiary -import com.digiventure.ventnote.ui.theme.CrimsonLightTertiaryContainer -import com.digiventure.ventnote.ui.theme.DarkBackground -import com.digiventure.ventnote.ui.theme.DarkOnBackground -import com.digiventure.ventnote.ui.theme.DarkOnPrimary -import com.digiventure.ventnote.ui.theme.DarkOnPrimaryContainer -import com.digiventure.ventnote.ui.theme.DarkOnSecondary -import com.digiventure.ventnote.ui.theme.DarkOnSecondaryContainer -import com.digiventure.ventnote.ui.theme.DarkOnSurface -import com.digiventure.ventnote.ui.theme.DarkOnSurfaceVariant -import com.digiventure.ventnote.ui.theme.DarkOnTertiary -import com.digiventure.ventnote.ui.theme.DarkOnTertiaryContainer -import com.digiventure.ventnote.ui.theme.DarkOutline -import com.digiventure.ventnote.ui.theme.DarkOutlineVariant -import com.digiventure.ventnote.ui.theme.DarkSurface -import com.digiventure.ventnote.ui.theme.DarkSurfaceVariant -import com.digiventure.ventnote.ui.theme.LightBackground -import com.digiventure.ventnote.ui.theme.LightOnBackground -import com.digiventure.ventnote.ui.theme.LightOnPrimary -import com.digiventure.ventnote.ui.theme.LightOnPrimaryContainer -import com.digiventure.ventnote.ui.theme.LightOnSecondary -import com.digiventure.ventnote.ui.theme.LightOnSecondaryContainer -import com.digiventure.ventnote.ui.theme.LightOnSurface -import com.digiventure.ventnote.ui.theme.LightOnSurfaceVariant -import com.digiventure.ventnote.ui.theme.LightOnTertiary -import com.digiventure.ventnote.ui.theme.LightOnTertiaryContainer -import com.digiventure.ventnote.ui.theme.LightOutline -import com.digiventure.ventnote.ui.theme.LightOutlineVariant -import com.digiventure.ventnote.ui.theme.LightSurface -import com.digiventure.ventnote.ui.theme.LightSurfaceVariant -import com.digiventure.ventnote.ui.theme.PurpleDarkPrimary -import com.digiventure.ventnote.ui.theme.PurpleDarkPrimaryContainer -import com.digiventure.ventnote.ui.theme.PurpleDarkSecondary -import com.digiventure.ventnote.ui.theme.PurpleDarkSecondaryContainer -import com.digiventure.ventnote.ui.theme.PurpleDarkTertiary -import com.digiventure.ventnote.ui.theme.PurpleDarkTertiaryContainer -import com.digiventure.ventnote.ui.theme.PurpleLightPrimary -import com.digiventure.ventnote.ui.theme.PurpleLightPrimaryContainer -import com.digiventure.ventnote.ui.theme.PurpleLightSecondary -import com.digiventure.ventnote.ui.theme.PurpleLightSecondaryContainer -import com.digiventure.ventnote.ui.theme.PurpleLightTertiary -import com.digiventure.ventnote.ui.theme.PurpleLightTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenDarkPrimary +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenDarkPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenDarkSecondary +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenDarkSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenDarkTertiary +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenDarkTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenLightPrimary +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenLightPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenLightSecondary +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenLightSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenLightTertiary +import com.digiventure.ventnote.ui.theme.components.CadmiumGreenLightTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.CobaltBlueDarkPrimary +import com.digiventure.ventnote.ui.theme.components.CobaltBlueDarkPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.CobaltBlueDarkSecondary +import com.digiventure.ventnote.ui.theme.components.CobaltBlueDarkSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.CobaltBlueDarkTertiary +import com.digiventure.ventnote.ui.theme.components.CobaltBlueDarkTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.CobaltBlueLightPrimary +import com.digiventure.ventnote.ui.theme.components.CobaltBlueLightPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.CobaltBlueLightSecondary +import com.digiventure.ventnote.ui.theme.components.CobaltBlueLightSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.CobaltBlueLightTertiary +import com.digiventure.ventnote.ui.theme.components.CobaltBlueLightTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.CrimsonDarkPrimary +import com.digiventure.ventnote.ui.theme.components.CrimsonDarkPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.CrimsonDarkSecondary +import com.digiventure.ventnote.ui.theme.components.CrimsonDarkSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.CrimsonDarkTertiary +import com.digiventure.ventnote.ui.theme.components.CrimsonDarkTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.CrimsonLightPrimary +import com.digiventure.ventnote.ui.theme.components.CrimsonLightPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.CrimsonLightSecondary +import com.digiventure.ventnote.ui.theme.components.CrimsonLightSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.CrimsonLightTertiary +import com.digiventure.ventnote.ui.theme.components.CrimsonLightTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.DarkBackground +import com.digiventure.ventnote.ui.theme.components.DarkOnBackground +import com.digiventure.ventnote.ui.theme.components.DarkOnPrimary +import com.digiventure.ventnote.ui.theme.components.DarkOnPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.DarkOnSecondary +import com.digiventure.ventnote.ui.theme.components.DarkOnSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.DarkOnSurface +import com.digiventure.ventnote.ui.theme.components.DarkOnSurfaceVariant +import com.digiventure.ventnote.ui.theme.components.DarkOnTertiary +import com.digiventure.ventnote.ui.theme.components.DarkOnTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.DarkOutline +import com.digiventure.ventnote.ui.theme.components.DarkOutlineVariant +import com.digiventure.ventnote.ui.theme.components.DarkSurface +import com.digiventure.ventnote.ui.theme.components.DarkSurfaceVariant +import com.digiventure.ventnote.ui.theme.components.LightBackground +import com.digiventure.ventnote.ui.theme.components.LightOnBackground +import com.digiventure.ventnote.ui.theme.components.LightOnPrimary +import com.digiventure.ventnote.ui.theme.components.LightOnPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.LightOnSecondary +import com.digiventure.ventnote.ui.theme.components.LightOnSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.LightOnSurface +import com.digiventure.ventnote.ui.theme.components.LightOnSurfaceVariant +import com.digiventure.ventnote.ui.theme.components.LightOnTertiary +import com.digiventure.ventnote.ui.theme.components.LightOnTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.LightOutline +import com.digiventure.ventnote.ui.theme.components.LightOutlineVariant +import com.digiventure.ventnote.ui.theme.components.LightSurface +import com.digiventure.ventnote.ui.theme.components.LightSurfaceVariant +import com.digiventure.ventnote.ui.theme.components.PurpleDarkPrimary +import com.digiventure.ventnote.ui.theme.components.PurpleDarkPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.PurpleDarkSecondary +import com.digiventure.ventnote.ui.theme.components.PurpleDarkSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.PurpleDarkTertiary +import com.digiventure.ventnote.ui.theme.components.PurpleDarkTertiaryContainer +import com.digiventure.ventnote.ui.theme.components.PurpleLightPrimary +import com.digiventure.ventnote.ui.theme.components.PurpleLightPrimaryContainer +import com.digiventure.ventnote.ui.theme.components.PurpleLightSecondary +import com.digiventure.ventnote.ui.theme.components.PurpleLightSecondaryContainer +import com.digiventure.ventnote.ui.theme.components.PurpleLightTertiary +import com.digiventure.ventnote.ui.theme.components.PurpleLightTertiaryContainer object ColorSchemeChoice { fun getColorScheme(colorScheme: String, colorPallet: String): ColorScheme { diff --git a/app/src/main/java/com/digiventure/ventnote/ui/theme/Theme.kt b/app/src/main/java/com/digiventure/ventnote/ui/theme/Theme.kt deleted file mode 100644 index cae562e..0000000 --- a/app/src/main/java/com/digiventure/ventnote/ui/theme/Theme.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.digiventure.ventnote.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.platform.LocalContext -import com.digiventure.ventnote.commons.ColorPalletName -import com.digiventure.ventnote.commons.ColorSchemeName -import com.digiventure.ventnote.commons.Constants -import com.digiventure.ventnote.data.local.NoteDataStore -import com.digiventure.ventnote.ui.ColorSchemeChoice -import com.google.accompanist.systemuicontroller.rememberSystemUiController -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch - -@Composable -fun VentNoteTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - val dataStore = NoteDataStore(LocalContext.current) - - val scope = rememberCoroutineScope() - - fun setColorPallet(colorPallet: String) { - scope.launch { - dataStore.setStringData(Constants.COLOR_PALLET, colorPallet) - } - } - - fun setColorScheme(colorScheme: String) { - scope.launch { - dataStore.setStringData(Constants.COLOR_SCHEME, colorScheme) - } - } - - val colorScheme = remember { - val scheme = if (darkTheme) ColorSchemeName.DARK_MODE else - ColorSchemeName.LIGHT_MODE - mutableStateOf( - ColorSchemeChoice.getColorScheme( - scheme, - ColorPalletName.PURPLE - ) - ) - } - - LaunchedEffect(Unit) { - val colorSchemeFlow = dataStore.getStringData(Constants.COLOR_SCHEME) - val colorPalletFlow = dataStore.getStringData(Constants.COLOR_PALLET) - - val combinedFlow = combine( - colorSchemeFlow, colorPalletFlow - ) { scheme, pallet -> - val defaultScheme = - scheme.ifEmpty { - if (darkTheme) ColorSchemeName.DARK_MODE - else ColorSchemeName.LIGHT_MODE - } - val defaultPallet = pallet.ifEmpty { ColorPalletName.PURPLE } - setColorScheme(defaultScheme) - setColorPallet(defaultPallet) - - Pair(defaultScheme, defaultPallet) - } - - combinedFlow.collect { - colorScheme.value = ColorSchemeChoice.getColorScheme( - it.first, - it.second - ) - } - } - - val systemUiController = rememberSystemUiController() - systemUiController.setStatusBarColor( - darkIcons = !darkTheme, color = colorScheme.value.primary - ) - - MaterialTheme( - colorScheme = colorScheme.value, typography = Typography, content = content - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/ui/theme/Color.kt b/app/src/main/java/com/digiventure/ventnote/ui/theme/components/Color.kt similarity index 98% rename from app/src/main/java/com/digiventure/ventnote/ui/theme/Color.kt rename to app/src/main/java/com/digiventure/ventnote/ui/theme/components/Color.kt index caf9abe..4651104 100644 --- a/app/src/main/java/com/digiventure/ventnote/ui/theme/Color.kt +++ b/app/src/main/java/com/digiventure/ventnote/ui/theme/components/Color.kt @@ -1,4 +1,4 @@ -package com.digiventure.ventnote.ui.theme +package com.digiventure.ventnote.ui.theme.components import androidx.compose.ui.graphics.Color diff --git a/app/src/main/java/com/digiventure/ventnote/ui/theme/components/Theme.kt b/app/src/main/java/com/digiventure/ventnote/ui/theme/components/Theme.kt new file mode 100644 index 0000000..f331875 --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/ui/theme/components/Theme.kt @@ -0,0 +1,39 @@ +package com.digiventure.ventnote.ui.theme.components + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.hilt.navigation.compose.hiltViewModel +import com.digiventure.ventnote.ui.theme.viewmodel.ThemeBaseVM +import com.digiventure.ventnote.ui.theme.viewmodel.ThemeVM +import com.google.accompanist.systemuicontroller.rememberSystemUiController + +@Composable +fun VentNoteTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + themeVM: ThemeBaseVM = hiltViewModel(), + content: @Composable () -> Unit +) { + val colorScheme by themeVM.currentColorScheme + + // Update system UI based on the theme from ViewModel + val systemUiController = rememberSystemUiController() + LaunchedEffect(colorScheme, darkTheme) { + systemUiController.setStatusBarColor( + darkIcons = !darkTheme, + color = colorScheme.primary + ) + systemUiController.setSystemBarsColor( + darkIcons = !darkTheme, + color = colorScheme.primary + ) + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/app/src/main/java/com/digiventure/ventnote/ui/theme/Type.kt b/app/src/main/java/com/digiventure/ventnote/ui/theme/components/Type.kt similarity index 87% rename from app/src/main/java/com/digiventure/ventnote/ui/theme/Type.kt rename to app/src/main/java/com/digiventure/ventnote/ui/theme/components/Type.kt index e014ce5..91ff11b 100644 --- a/app/src/main/java/com/digiventure/ventnote/ui/theme/Type.kt +++ b/app/src/main/java/com/digiventure/ventnote/ui/theme/components/Type.kt @@ -1,4 +1,4 @@ -package com.digiventure.ventnote.ui.theme +package com.digiventure.ventnote.ui.theme.components import androidx.compose.material3.Typography diff --git a/app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeBaseVM.kt b/app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeBaseVM.kt new file mode 100644 index 0000000..8aa067a --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeBaseVM.kt @@ -0,0 +1,26 @@ +package com.digiventure.ventnote.ui.theme.viewmodel + +import androidx.compose.material3.ColorScheme +import androidx.compose.runtime.MutableState + +interface ThemeBaseVM { + /** + * + * */ + val currentColorScheme: MutableState + + /** + * + * */ + val currentColorSchemeName: MutableState + + /** + * + * */ + fun updateColorPallet(colorPallet: String) + + /** + * + * */ + fun updateColorScheme(colorSchemeName: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeMockVM.kt b/app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeMockVM.kt new file mode 100644 index 0000000..64be0c1 --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeMockVM.kt @@ -0,0 +1,25 @@ +package com.digiventure.ventnote.ui.theme.viewmodel + +import androidx.compose.material3.ColorScheme +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import com.digiventure.ventnote.commons.ColorPalletName +import com.digiventure.ventnote.commons.ColorSchemeName +import com.digiventure.ventnote.commons.Constants.EMPTY_STRING +import com.digiventure.ventnote.ui.ColorSchemeChoice + +class ThemeMockVM: ViewModel(), ThemeBaseVM { + override val currentColorScheme: MutableState = mutableStateOf(ColorSchemeChoice.getColorScheme( + ColorSchemeName.DARK_MODE, ColorPalletName.PURPLE + )) + override val currentColorSchemeName: MutableState = mutableStateOf(EMPTY_STRING) + + override fun updateColorPallet(colorPallet: String) { + + } + + override fun updateColorScheme(colorSchemeName: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeVM.kt b/app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeVM.kt new file mode 100644 index 0000000..7411c43 --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/ui/theme/viewmodel/ThemeVM.kt @@ -0,0 +1,80 @@ +package com.digiventure.ventnote.ui.theme.viewmodel + +import androidx.compose.material3.ColorScheme +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.digiventure.ventnote.commons.ColorPalletName +import com.digiventure.ventnote.commons.ColorSchemeName +import com.digiventure.ventnote.commons.Constants +import com.digiventure.ventnote.data.local.NoteDataStore +import com.digiventure.ventnote.ui.ColorSchemeChoice +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class ThemeVM @Inject constructor( + private val dataStore: NoteDataStore, +) : ViewModel(), ThemeBaseVM { + private val _currentColorScheme = mutableStateOf(getDefaultColorScheme()) + private val _currentColorSchemeName = mutableStateOf(getDefaultColorSchemeName()) + override val currentColorScheme: MutableState = _currentColorScheme + override val currentColorSchemeName: MutableState = _currentColorSchemeName + + // Flows for observing datastore changes + private val colorSchemePreferenceFlow = dataStore.getStringData(Constants.COLOR_SCHEME) + private val colorPalletPreferenceFlow = dataStore.getStringData(Constants.COLOR_PALLET) + + init { + viewModelScope.launch { + combine( + colorSchemePreferenceFlow, + colorPalletPreferenceFlow + ) { schemePref, palletPref -> + val currentSystemIsDark = false + val effectiveSchemeName = schemePref.ifEmpty { + if (currentSystemIsDark) ColorSchemeName.DARK_MODE + else ColorSchemeName.LIGHT_MODE + } + val effectivePalletName = palletPref.ifEmpty { ColorPalletName.PURPLE } + + if (schemePref.isEmpty()) { + dataStore.setStringData(Constants.COLOR_SCHEME, effectiveSchemeName) + } + if (palletPref.isEmpty()) { + dataStore.setStringData(Constants.COLOR_PALLET, effectivePalletName) + } + + ColorSchemeChoice.getColorScheme(effectiveSchemeName, effectivePalletName) + }.collect { newScheme -> + _currentColorScheme.value = newScheme + } + } + } + + override fun updateColorPallet(colorPallet: String) { + viewModelScope.launch { + dataStore.setStringData(Constants.COLOR_PALLET, colorPallet) + } + } + + override fun updateColorScheme(colorSchemeName: String) { + viewModelScope.launch { + dataStore.setStringData(Constants.COLOR_SCHEME, colorSchemeName) + _currentColorSchemeName.value = colorSchemeName + } + } + + private fun getDefaultColorScheme(isSystemDark: Boolean = false): ColorScheme { + val schemeName = this.getDefaultColorSchemeName(isSystemDark); + return ColorSchemeChoice.getColorScheme(schemeName, ColorPalletName.PURPLE) + } + + private fun getDefaultColorSchemeName(isSystemDark: Boolean = false): String { + val schemeName = if (isSystemDark) ColorSchemeName.DARK_MODE else ColorSchemeName.LIGHT_MODE + return schemeName + } +} \ No newline at end of file From 9796b341536be5d51d87c909e2fc6d5ddf1526b1 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sat, 20 Sep 2025 14:27:18 +0700 Subject: [PATCH 06/26] Feat bugfix/theme-and-ui: Change selected text color, remove animating search bar to dismiss when scrolled --- .../java/com/digiventure/ventnote/feature/notes/NotesPage.kt | 3 --- .../ventnote/feature/notes/components/navbar/AppBar.kt | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt index 06a4f88..10b6215 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt @@ -264,9 +264,6 @@ fun NotesPage( searchBarHeightPx = coords.size.height.toFloat() scrollBehavior.state.heightOffsetLimit = -searchBarHeightPx } - .graphicsLayer { - translationY = scrollBehavior.state.heightOffset - } .fillMaxWidth() .padding(16.dp, 24.dp, 16.dp, 8.dp) ) { diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/navbar/AppBar.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/navbar/AppBar.kt index 24e49b9..90b0d5e 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/navbar/AppBar.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/navbar/AppBar.kt @@ -154,7 +154,7 @@ private fun SelectionTitle( } withStyle( style = SpanStyle( - color = MaterialTheme.colorScheme.onPrimary + color = MaterialTheme.colorScheme.primary ) ) { append(" of $totalNotesCount selected") From b5180071dc52d73f85309ccb45e424c78a8dd708 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sat, 20 Sep 2025 14:35:52 +0700 Subject: [PATCH 07/26] Feat bugfix/theme-and-ui: Make notes page could change orientation --- .../ventnote/feature/drawer/NavDrawer.kt | 92 ++++++++++--------- .../ventnote/feature/notes/NotesPage.kt | 11 +-- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt b/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt index e50dc97..780d7d9 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt @@ -4,9 +4,13 @@ import android.annotation.SuppressLint import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bedtime import androidx.compose.material.icons.filled.CloudUpload @@ -75,6 +79,7 @@ fun NavDrawer( val configuration = LocalConfiguration.current val screenWidth = configuration.screenWidthDp.dp + val maxDrawerWidth = 320.dp var currentSchemeName by themeViewModel.currentColorSchemeName @@ -85,50 +90,55 @@ fun NavDrawer( modifier = Modifier .fillMaxHeight() .padding(0.dp) - .width(screenWidth - 60.dp) + .widthIn(max = maxDrawerWidth) + .width(screenWidth * 0.8f) ) { - SectionTitle(title = stringResource(id = R.string.about_us)) - - NavDrawerItem(leftIcon = Icons.Filled.Star, - title = stringResource(id = R.string.rate_app), - subtitle = stringResource(id = R.string.rate_app_description), - testTagName = TestTags.RATE_APP_TILE, - onClick = { openPlayStore(context, appPath, onError) }) - - NavDrawerItem(leftIcon = Icons.Filled.Shop, - title = stringResource(id = R.string.more_apps), - subtitle = stringResource(id = R.string.more_apps_description), - onClick = { openPlayStore(context, devPagePath, onError) }) - - NavDrawerItem(leftIcon = Icons.Filled.Update, - title = stringResource(id = R.string.app_version), - subtitle = BuildConfig.VERSION_NAME, - onClick = { }) - - SectionTitle(title = stringResource(id = R.string.preferences)) - - NavDrawerColorPicker( - leftIcon = Icons.Filled.ColorLens, - title = stringResource(id = R.string.theme_color), + Column( + modifier = Modifier.fillMaxHeight().verticalScroll(rememberScrollState()) ) { - themeViewModel.updateColorPallet(it.second) + SectionTitle(title = stringResource(id = R.string.about_us)) + + NavDrawerItem(leftIcon = Icons.Filled.Star, + title = stringResource(id = R.string.rate_app), + subtitle = stringResource(id = R.string.rate_app_description), + testTagName = TestTags.RATE_APP_TILE, + onClick = { openPlayStore(context, appPath, onError) }) + + NavDrawerItem(leftIcon = Icons.Filled.Shop, + title = stringResource(id = R.string.more_apps), + subtitle = stringResource(id = R.string.more_apps_description), + onClick = { openPlayStore(context, devPagePath, onError) }) + + NavDrawerItem(leftIcon = Icons.Filled.Update, + title = stringResource(id = R.string.app_version), + subtitle = BuildConfig.VERSION_NAME, + onClick = { }) + + SectionTitle(title = stringResource(id = R.string.preferences)) + + NavDrawerColorPicker( + leftIcon = Icons.Filled.ColorLens, + title = stringResource(id = R.string.theme_color), + ) { + themeViewModel.updateColorPallet(it.second) + } + + NavDrawerItemColorSchemeSwitch( + leftIcon = Icons.Filled.Bedtime, + title = stringResource(id = R.string.theme_setting), + currentScheme = currentSchemeName, + ) { + themeViewModel.updateColorScheme(it) + } + + SectionTitle(title = stringResource(id = R.string.settings)) + + NavDrawerItem( + leftIcon = Icons.Filled.CloudUpload, + title = stringResource(id = R.string.backup), + subtitle = stringResource(id = R.string.backup_description), + onClick = { onBackupPressed() }) } - - NavDrawerItemColorSchemeSwitch( - leftIcon = Icons.Filled.Bedtime, - title = stringResource(id = R.string.theme_setting), - currentScheme = currentSchemeName, - ) { - themeViewModel.updateColorScheme(it) - } - - SectionTitle(title = stringResource(id = R.string.settings)) - - NavDrawerItem( - leftIcon = Icons.Filled.CloudUpload, - title = stringResource(id = R.string.backup), - subtitle = stringResource(id = R.string.backup_description), - onClick = { onBackupPressed() }) } }, content = { content() }, modifier = Modifier.semantics { testTag = TestTags.NAV_DRAWER }) } diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt index 10b6215..681e73a 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt @@ -1,6 +1,6 @@ package com.digiventure.ventnote.feature.notes -import android.content.pm.ActivityInfo +import android.annotation.SuppressLint import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -35,7 +35,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalFocusManager @@ -50,7 +49,6 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import com.digiventure.ventnote.R import com.digiventure.ventnote.commons.TestTags -import com.digiventure.ventnote.components.LockScreenOrientation import com.digiventure.ventnote.components.dialog.LoadingDialog import com.digiventure.ventnote.components.dialog.TextDialog import com.digiventure.ventnote.data.persistence.NoteModel @@ -75,8 +73,6 @@ fun NotesPage( val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() var searchBarHeightPx by remember { mutableFloatStateOf(0f) } - LockScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - val navigationActions = remember(navHostController) { PageNavigation(navHostController) } @@ -333,11 +329,10 @@ fun NotesPage( @Preview @Composable +@SuppressLint("ViewModelConstructorInComposable") fun NotesPagePreview() { NotesPage( navHostController = rememberNavController(), viewModel = NotesPageMockVM() - ) { - - } + ) { } } \ No newline at end of file From 52922171d63121b9608a9a037e4133fd2c09b828 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sat, 20 Sep 2025 15:15:18 +0700 Subject: [PATCH 08/26] Feat bugfix/theme-and-ui: Remove all lock orientation, make notes edit text could be requested focus in all area, optimize imports --- .../components/LockScreenOrientation.kt | 28 ------------------- .../feature/note_creation/NoteCreationPage.kt | 6 ++-- .../components/section/NoteSection.kt | 20 +++++++++++-- .../viewmodel/NoteCreationPageMockVM.kt | 2 +- .../feature/note_detail/NoteDetailPage.kt | 4 --- .../components/section/NoteSection.kt | 20 +++++++++++-- .../feature/share_preview/SharePreviewPage.kt | 4 --- 7 files changed, 39 insertions(+), 45 deletions(-) delete mode 100644 app/src/main/java/com/digiventure/ventnote/components/LockScreenOrientation.kt diff --git a/app/src/main/java/com/digiventure/ventnote/components/LockScreenOrientation.kt b/app/src/main/java/com/digiventure/ventnote/components/LockScreenOrientation.kt deleted file mode 100644 index ac1a849..0000000 --- a/app/src/main/java/com/digiventure/ventnote/components/LockScreenOrientation.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.digiventure.ventnote.components - -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.platform.LocalContext - -@Composable -fun LockScreenOrientation(orientation: Int) { - val context = LocalContext.current - DisposableEffect(Unit) { - val activity = context.findActivity() ?: return@DisposableEffect onDispose {} - val originalOrientation = activity.requestedOrientation - activity.requestedOrientation = orientation - onDispose { - // restore original orientation when view disappears - activity.requestedOrientation = originalOrientation - } - } -} - -fun Context.findActivity(): Activity? = when (this) { - is Activity -> this - is ContextWrapper -> baseContext.findActivity() - else -> null -} \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/NoteCreationPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/NoteCreationPage.kt index 7e3f54c..218da39 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/NoteCreationPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/NoteCreationPage.kt @@ -1,6 +1,6 @@ package com.digiventure.ventnote.feature.note_creation -import android.content.pm.ActivityInfo +import android.annotation.SuppressLint import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -33,7 +33,6 @@ import androidx.navigation.compose.rememberNavController import com.digiventure.ventnote.R import com.digiventure.ventnote.commons.Constants.EMPTY_STRING import com.digiventure.ventnote.commons.TestTags -import com.digiventure.ventnote.components.LockScreenOrientation import com.digiventure.ventnote.components.dialog.TextDialog import com.digiventure.ventnote.data.persistence.NoteModel import com.digiventure.ventnote.feature.note_creation.components.navbar.EnhancedBottomAppBar @@ -51,8 +50,6 @@ fun NoteCreationPage( navHostController: NavHostController, viewModel: NoteCreationPageBaseVM = hiltViewModel() ) { - LockScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - val titleTextFieldText = stringResource(R.string.title_textField) val bodyTextFieldText = stringResource(R.string.body_textField) @@ -193,6 +190,7 @@ fun NoteCreationPage( @Preview @Composable +@SuppressLint("ViewModelConstructorInComposable") fun NoteCreationPagePreview() { NoteCreationPage( navHostController = rememberNavController(), diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt index 8c6f25b..6a5d06c 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt @@ -4,6 +4,8 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -25,8 +27,11 @@ import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription @@ -83,11 +88,21 @@ fun ImprovedDescriptionTextField( label = label ) + val focusRequester = remember { FocusRequester() } + val interactionSource = remember { MutableInteractionSource() } + Card( modifier = Modifier .fillMaxWidth() .animateContentSize() - .heightIn(min = 200.dp), + .heightIn(min = 200.dp) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { + focusRequester.requestFocus() + } + ), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface ), @@ -119,7 +134,8 @@ fun ImprovedDescriptionTextField( modifier = Modifier .fillMaxWidth() .fillMaxHeight() - .semantics { contentDescription = bodyTextField }, + .semantics { contentDescription = bodyTextField } + .focusRequester(focusRequester), placeholder = { Text( text = bodyInput, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/viewmodel/NoteCreationPageMockVM.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/viewmodel/NoteCreationPageMockVM.kt index 3fa4521..e0ba8a4 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/viewmodel/NoteCreationPageMockVM.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/viewmodel/NoteCreationPageMockVM.kt @@ -12,6 +12,6 @@ class NoteCreationPageMockVM: ViewModel(), NoteCreationPageBaseVM { override val descriptionText: MutableState = mutableStateOf("") override suspend fun addNote(note: NoteModel): Result { - TODO("Not yet implemented") + return Result.success(true) } } \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/NoteDetailPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/NoteDetailPage.kt index 0d8bea3..14723da 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/NoteDetailPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/NoteDetailPage.kt @@ -1,6 +1,5 @@ package com.digiventure.ventnote.feature.note_detail -import android.content.pm.ActivityInfo import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.foundation.clickable @@ -40,7 +39,6 @@ import androidx.navigation.compose.rememberNavController import com.digiventure.ventnote.R import com.digiventure.ventnote.commons.Constants.EMPTY_STRING import com.digiventure.ventnote.commons.TestTags -import com.digiventure.ventnote.components.LockScreenOrientation import com.digiventure.ventnote.components.dialog.LoadingDialog import com.digiventure.ventnote.components.dialog.TextDialog import com.digiventure.ventnote.feature.note_detail.components.navbar.EnhancedBottomAppBar @@ -61,8 +59,6 @@ fun NoteDetailPage( viewModel: NoteDetailPageBaseVM = hiltViewModel(), id: String ) { - LockScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - val navigationActions = remember(navHostController) { PageNavigation(navHostController) } diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt index 02a6b47..e72947d 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt @@ -4,6 +4,8 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -25,8 +27,11 @@ import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription @@ -86,11 +91,21 @@ fun ImprovedDescriptionTextField( label = label ) + val focusRequester = remember { FocusRequester() } + val interactionSource = remember { MutableInteractionSource() } + Card( modifier = Modifier .fillMaxWidth() .animateContentSize() - .heightIn(min = 200.dp), + .heightIn(min = 200.dp) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { + focusRequester.requestFocus() + } + ), colors = CardDefaults.cardColors( containerColor = if (isEditingState) { MaterialTheme.colorScheme.surface @@ -127,7 +142,8 @@ fun ImprovedDescriptionTextField( modifier = Modifier .fillMaxWidth() .fillMaxHeight() - .semantics { contentDescription = bodyTextField }, + .semantics { contentDescription = bodyTextField } + .focusRequester(focusRequester), placeholder = { if (isEditingState) { Text( diff --git a/app/src/main/java/com/digiventure/ventnote/feature/share_preview/SharePreviewPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/share_preview/SharePreviewPage.kt index 03d39e0..017379e 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/share_preview/SharePreviewPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/share_preview/SharePreviewPage.kt @@ -2,7 +2,6 @@ package com.digiventure.ventnote.feature.share_preview import android.content.Context import android.content.Intent -import android.content.pm.ActivityInfo import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -52,7 +51,6 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import com.digiventure.ventnote.R import com.digiventure.ventnote.commons.DateUtil -import com.digiventure.ventnote.components.LockScreenOrientation import com.digiventure.ventnote.components.dialog.TextDialog import com.digiventure.ventnote.data.persistence.NoteModel import com.digiventure.ventnote.feature.share_preview.components.navbar.EnhancedBottomAppBar @@ -67,8 +65,6 @@ fun SharePreviewPage( navHostController: NavHostController, note: NoteModel? ) { - LockScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - val appBarState = rememberTopAppBarState() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(appBarState) val rememberedScrollBehavior = remember { scrollBehavior } From 57d6a3e49e264e5de1134dc1c650d0a8df18c8ab Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sat, 4 Oct 2025 18:07:34 +0700 Subject: [PATCH 09/26] Chore staging: Upgrade version to 1.1.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ded4cf4..6cee0b2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,8 +19,8 @@ android { applicationId "com.digiventure.ventnote" minSdk 23 targetSdk 36 - versionCode 42 - versionName "1.1.0" + versionCode 43 + versionName "1.1.1" testInstrumentationRunner "com.digiventure.utils.CustomTestRunner" vectorDrawables { From e4894a99d5031013b830432764ef5d28bcadcb7e Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sun, 1 Feb 2026 15:10:00 +0700 Subject: [PATCH 10/26] Feat bugfix-1.2.0: Remove request autofocus when editing notes --- .../components/section/NoteSection.kt | 16 ++-------------- .../components/section/TitleSection.kt | 7 +------ .../feature/note_detail/NoteDetailPage.kt | 6 ------ .../components/section/NoteSection.kt | 16 ++-------------- .../components/section/TitleSection.kt | 16 +--------------- 5 files changed, 6 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt index 6a5d06c..fbf67e2 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt @@ -4,7 +4,6 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -30,8 +29,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription @@ -88,21 +85,13 @@ fun ImprovedDescriptionTextField( label = label ) - val focusRequester = remember { FocusRequester() } val interactionSource = remember { MutableInteractionSource() } Card( modifier = Modifier .fillMaxWidth() .animateContentSize() - .heightIn(min = 200.dp) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = { - focusRequester.requestFocus() - } - ), + .heightIn(min = 200.dp), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface ), @@ -134,8 +123,7 @@ fun ImprovedDescriptionTextField( modifier = Modifier .fillMaxWidth() .fillMaxHeight() - .semantics { contentDescription = bodyTextField } - .focusRequester(focusRequester), + .semantics { contentDescription = bodyTextField }, placeholder = { Text( text = bodyInput, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/TitleSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/TitleSection.kt index 3ae2195..3cb2457 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/TitleSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/TitleSection.kt @@ -23,11 +23,8 @@ import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription @@ -78,7 +75,6 @@ fun ImprovedTitleTextField( titleInput: String ) { val label = "border_color" - val focusRequester = remember { FocusRequester() } val borderColor by animateColorAsState( targetValue = MaterialTheme.colorScheme.primary, animationSpec = tween(300), @@ -119,8 +115,7 @@ fun ImprovedTitleTextField( ), modifier = Modifier .fillMaxWidth() - .semantics { contentDescription = titleTextField } - .focusRequester(focusRequester), + .semantics { contentDescription = titleTextField }, placeholder = { Text( text = titleInput, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/NoteDetailPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/NoteDetailPage.kt index 14723da..1fdd167 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/NoteDetailPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/NoteDetailPage.kt @@ -176,12 +176,6 @@ fun NoteDetailPage( initData() } - LaunchedEffect(isEditingState) { - if (!isEditingState) { - focusManager.clearFocus() - } - } - LaunchedEffect(loadingState) { openLoadingDialog.value = loadingState == true } diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt index e72947d..9c6a1b4 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt @@ -4,7 +4,6 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -30,8 +29,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription @@ -91,21 +88,13 @@ fun ImprovedDescriptionTextField( label = label ) - val focusRequester = remember { FocusRequester() } val interactionSource = remember { MutableInteractionSource() } Card( modifier = Modifier .fillMaxWidth() .animateContentSize() - .heightIn(min = 200.dp) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = { - focusRequester.requestFocus() - } - ), + .heightIn(min = 200.dp), colors = CardDefaults.cardColors( containerColor = if (isEditingState) { MaterialTheme.colorScheme.surface @@ -142,8 +131,7 @@ fun ImprovedDescriptionTextField( modifier = Modifier .fillMaxWidth() .fillMaxHeight() - .semantics { contentDescription = bodyTextField } - .focusRequester(focusRequester), + .semantics { contentDescription = bodyTextField }, placeholder = { if (isEditingState) { Text( diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/TitleSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/TitleSection.kt index 0f379cc..80cd650 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/TitleSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/TitleSection.kt @@ -22,13 +22,9 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription @@ -37,7 +33,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.digiventure.ventnote.R import com.digiventure.ventnote.feature.note_detail.viewmodel.NoteDetailPageBaseVM -import kotlinx.coroutines.delay @Composable fun TitleSection( @@ -83,20 +78,12 @@ fun ImprovedTitleTextField( titleInput: String ) { val label = "border_color" - val focusRequester = remember { FocusRequester() } val borderColor by animateColorAsState( targetValue = if (isEditingState) MaterialTheme.colorScheme.primary else Color.Transparent, animationSpec = tween(300), label = label ) - LaunchedEffect(isEditingState) { - if (isEditingState) { - delay(200) - focusRequester.requestFocus() - } - } - Card( modifier = Modifier .fillMaxWidth() @@ -136,8 +123,7 @@ fun ImprovedTitleTextField( ), modifier = Modifier .fillMaxWidth() - .semantics { contentDescription = titleTextField } - .focusRequester(focusRequester), + .semantics { contentDescription = titleTextField }, placeholder = { if (isEditingState) { Text( From 62e0b39d2e591f909d84ceccf90d5827d5d4dafd Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sun, 1 Feb 2026 15:10:49 +0700 Subject: [PATCH 11/26] Feat bugfix-1.2.0: Remove unused variables --- .../feature/note_creation/components/section/NoteSection.kt | 2 -- .../feature/note_detail/components/section/NoteSection.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt index fbf67e2..6f1ae0f 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt @@ -85,8 +85,6 @@ fun ImprovedDescriptionTextField( label = label ) - val interactionSource = remember { MutableInteractionSource() } - Card( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt index 9c6a1b4..c67cf7d 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt @@ -88,8 +88,6 @@ fun ImprovedDescriptionTextField( label = label ) - val interactionSource = remember { MutableInteractionSource() } - Card( modifier = Modifier .fillMaxWidth() From dc64296ddc4b6b6de8e4f34ea76d5d3716b97a45 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sun, 1 Feb 2026 19:30:41 +0700 Subject: [PATCH 12/26] Feat bugfix-1.2.0: Add key in the item list --- .../feature/backup/components/list/BackupFileList.kt | 5 ++++- .../ventnote/feature/notes/components/sheets/FilterSheet.kt | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt index 16d2d11..1c29f0a 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt @@ -297,7 +297,10 @@ fun BackupListContainer( } } - items(items = backupFiles) { file -> + items( + items = backupFiles, + key = { it.id } + ) { file -> Card( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/sheets/FilterSheet.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/sheets/FilterSheet.kt index af760f3..6cc3b96 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/sheets/FilterSheet.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/sheets/FilterSheet.kt @@ -100,7 +100,10 @@ fun FilterSheet( horizontalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(horizontal = 4.dp) ) { - items(sortOptions) { option -> + items( + items = sortOptions, + key = { it.value } + ) { option -> CompactFilterChip( label = stringResource(option.labelRes), icon = option.icon, From acf44f54163c87869f75a382277845dca40a0b52 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sun, 1 Feb 2026 19:30:58 +0700 Subject: [PATCH 13/26] Feat bugfix-1.2.0: Add debounce when searching notes --- .../ventnote/feature/notes/NotesPage.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt index 681e73a..76cfd25 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt @@ -84,19 +84,28 @@ fun NotesPage( val isMarking by viewModel.isMarking val markedNoteList = viewModel.markedNoteList + // Debounced search query + var debouncedSearchQuery by remember { mutableStateOf("") } + + // Debounce search input + LaunchedEffect(searchQuery) { + kotlinx.coroutines.delay(300) // 300ms debounce delay + debouncedSearchQuery = searchQuery + } + val scope = rememberCoroutineScope() val snackBarHostState = remember { SnackbarHostState() } - // Memoized filtered notes with proper dependencies + // Memoized filtered notes with proper dependencies - using debounced query val filteredNotes by remember { derivedStateOf { val notes = noteListState?.getOrNull() ?: emptyList() - if (searchQuery.isBlank()) { + if (debouncedSearchQuery.isBlank()) { notes } else { notes.filter { note -> - note.title.contains(searchQuery, ignoreCase = true) || - note.note.contains(searchQuery, ignoreCase = true) + note.title.contains(debouncedSearchQuery, ignoreCase = true) || + note.note.contains(debouncedSearchQuery, ignoreCase = true) } } } From 3e262a5917c0bd3ff61f9560fcc184aaaf52cdc8 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sun, 1 Feb 2026 21:17:04 +0700 Subject: [PATCH 14/26] Feat bugfix-1.2.0: Add clean up old backups so it only keeps 10 newest data --- .../ventnote/feature/backup/BackupPage.kt | 2 - .../feature/backup/viewmodel/BackupPageVM.kt | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt index aea0f91..dd70cea 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt @@ -167,7 +167,6 @@ fun BackupPage( deleteConfirmationDialogState.value = true }, successfullyRestoredRequest = { - Log.e("hehe event", "restored") scope.launch { snackBarHostState.showSnackbar( message = restoredMessage, @@ -176,7 +175,6 @@ fun BackupPage( } }, successfullyDeletedRequest = { - Log.e("hehe event", "deleted") scope.launch { snackBarHostState.showSnackbar( message = deletedMessage, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/viewmodel/BackupPageVM.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/viewmodel/BackupPageVM.kt index e035196..e366ce8 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/viewmodel/BackupPageVM.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/viewmodel/BackupPageVM.kt @@ -1,6 +1,7 @@ package com.digiventure.ventnote.feature.backup.viewmodel import android.app.Application +import android.util.Log import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.LiveData @@ -49,6 +50,10 @@ class BackupPageVM @Inject constructor( getDatabaseNameWithTimestamps(), drive ).onEach { + // Clean up old backups first (seamlessly) + cleanupOldBackups(drive) + + // Then update UI state and fetch final list _uiState.value = currentState.copy(fileBackupState = FileBackupState.SyncFinished) getBackupFileList() }.last() @@ -182,4 +187,38 @@ class BackupPageVM @Inject constructor( val jsonFormat = ".json" return Constants.BACKUP_FILE_NAME + "_" + timestamp + jsonFormat } + + /** + * Cleans up old backup files, keeping only the last 5 most recent backups. + * This prevents unlimited growth of backup files in Google Drive. + */ + private suspend fun cleanupOldBackups(drive: Drive?) { + try { + // Fetch latest list directly to determine what to delete + // This runs in the background within the existing scope + repository.getBackupFileList(drive).collect { result -> + if (result.isSuccess) { + val backupFiles = result.getOrNull() ?: emptyList() + + // Keep only last 5 backups + val maxBackups = 10 + + if (backupFiles.size > maxBackups) { + // Sort by creation time (newest first) + val sortedFiles = backupFiles.sortedByDescending { it.createdTime?.value ?: 0L } + + // Get files to delete (all except the first 5) + val filesToDelete = sortedFiles.drop(maxBackups) + + // Delete old backup files + filesToDelete.forEach { file -> + repository.deleteFile(file.id, drive).last() + } + } + } + } + } catch (e: Exception) { + Log.e("BackupPageVM", "Cleanup failed: ${e.message}") + } + } } \ No newline at end of file From 726ea86920de71981ee1a0facf8d8c8c0ba1430e Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sun, 1 Feb 2026 21:50:29 +0700 Subject: [PATCH 15/26] Feat bugfix-1.2.0: Change backup file name in the list --- .../java/com/digiventure/ventnote/feature/backup/BackupPage.kt | 1 - .../ventnote/feature/backup/components/list/BackupFileList.kt | 3 ++- .../feature/note_creation/components/section/NoteSection.kt | 2 -- .../feature/note_detail/components/section/NoteSection.kt | 2 -- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt index dd70cea..1845606 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt @@ -1,6 +1,5 @@ package com.digiventure.ventnote.feature.backup -import android.util.Log import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt index 1c29f0a..7aa7f1e 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt @@ -341,7 +341,8 @@ fun BackupListContainer( Spacer(modifier = Modifier.width(16.dp)) Text( - text = file.name.substringBefore("."), + text = file.name.substringBefore(".") + .substringAfter("backup_"), maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt index 6f1ae0f..8c6f25b 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt @@ -4,7 +4,6 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -26,7 +25,6 @@ import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt index c67cf7d..02a6b47 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt @@ -4,7 +4,6 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -26,7 +25,6 @@ import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color From ea92b30e4da9bf256d171e3123ff89a8afafe103 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sun, 1 Feb 2026 22:00:27 +0700 Subject: [PATCH 16/26] Feat bugfix-1.2.0: Remove redundant cloud icon --- .../backup/components/list/BackupFileList.kt | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt index 7aa7f1e..ba2fb4e 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CloudDownload import androidx.compose.material.icons.filled.CloudOff -import androidx.compose.material.icons.filled.CloudQueue import androidx.compose.material.icons.filled.CloudUpload import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.ErrorOutline @@ -320,26 +319,6 @@ fun BackupListContainer( .padding(16.dp), verticalAlignment = Alignment.CenterVertically, ) { - // File icon - Surface( - modifier = Modifier.size(48.dp), - shape = CircleShape, - color = MaterialTheme.colorScheme.primaryContainer - ) { - Box( - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Filled.CloudQueue, - contentDescription = null, - tint = MaterialTheme.colorScheme.onPrimaryContainer, - modifier = Modifier.size(24.dp) - ) - } - } - - Spacer(modifier = Modifier.width(16.dp)) - Text( text = file.name.substringBefore(".") .substringAfter("backup_"), From f0db1de10a7db5ef7bb0bb91031637aff45647ca Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Mon, 2 Feb 2026 22:36:29 +0700 Subject: [PATCH 17/26] Feat app-size-enhancement-1.2.: Remove extended material icon & use minimal version, configure minimize & shrink resource in the gradle --- app/build.gradle | 26 +++++++++++------ .../com/digiventure/ventnote/MainActivity.kt | 6 ++++ .../ventnote/feature/backup/BackupPage.kt | 4 +-- .../backup/components/button/SignInButton.kt | 4 +-- .../backup/components/list/BackupFileList.kt | 18 ++++++------ .../backup/components/navbar/AppBar.kt | 4 +-- .../ventnote/feature/drawer/NavDrawer.kt | 25 +++++++++-------- .../NavDrawerItemColorSchemeSwitch.kt | 4 +-- .../components/navbar/EnhancedBottomAppBar.kt | 4 +-- .../components/section/NoteSection.kt | 4 +-- .../components/section/TitleSection.kt | 4 +-- .../components/navbar/EnhancedBottomAppBar.kt | 18 ++++++------ .../components/section/NoteSection.kt | 4 +-- .../components/section/TitleSection.kt | 4 +-- .../feature/notes/components/item/NoteItem.kt | 5 ++-- .../feature/notes/components/navbar/AppBar.kt | 14 ++++------ .../notes/components/sheets/FilterSheet.kt | 28 +++++++++---------- .../feature/share_preview/SharePreviewPage.kt | 4 +-- .../share_preview/components/navbar/AppBar.kt | 4 +-- .../components/navbar/EnhancedBottomAppBar.kt | 5 ++-- build.gradle | 5 ++-- gradle.properties | 3 +- 22 files changed, 106 insertions(+), 91 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6cee0b2..154dd74 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,9 +6,9 @@ plugins { id 'kotlin-parcelize' id 'org.jetbrains.kotlin.plugin.compose' version '2.2.10' - // disabled for internal purpose (if you want to enable, you must create firebase project first) -// id 'com.google.gms.google-services' -// id 'com.google.firebase.crashlytics' + // Google Services & Firebase + id 'com.google.gms.google-services' + id 'com.google.firebase.crashlytics' } android { @@ -37,13 +37,17 @@ android { buildTypes { release { minifyEnabled true + shrinkResources true debuggable false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + buildConfigField "boolean", "ENABLE_CRASHLYTICS", "true" } debug { - minifyEnabled false + minifyEnabled true + shrinkResources true signingConfig signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + buildConfigField "boolean", "ENABLE_CRASHLYTICS", "false" } } @@ -73,7 +77,15 @@ android { packaging { resources { excludes += '/META-INF/{AL2.0,LGPL2.1}' - excludes += 'META-INF/*' + excludes += 'META-INF/DEPENDENCIES' + excludes += 'META-INF/LICENSE' + excludes += 'META-INF/LICENSE.txt' + excludes += 'META-INF/license.txt' + excludes += 'META-INF/NOTICE' + excludes += 'META-INF/NOTICE.txt' + excludes += 'META-INF/notice.txt' + excludes += 'META-INF/ASL2.0' + excludes += 'META-INF/*.kotlin_module' } } @@ -121,9 +133,6 @@ dependencies { kapt "com.google.dagger:hilt-android-compiler:$dagger_version" implementation "androidx.hilt:hilt-navigation-compose:$hilt_navigation_compose_version" - // Material Icon Extension - implementation "androidx.compose.material:material-icons-extended:$material_icons_version" - // So, make sure you also include that repository in your project's build.gradle file. implementation "com.google.android.play:app-update:$play_app_update_version" // For Kotlin users also import the Kotlin extensions library for Play In-App Update: @@ -168,7 +177,6 @@ dependencies { implementation platform("com.google.firebase:firebase-bom:$firebase_bom_version") // When using the BoM, you don't specify versions in Firebase library dependencies // Add the dependency for the Firebase SDK for Google Analytics - implementation "com.google.firebase:firebase-analytics" implementation "com.google.firebase:firebase-crashlytics" //Google Drive API diff --git a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt index 1cc216f..b70215a 100644 --- a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt +++ b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt @@ -31,6 +31,7 @@ import com.google.android.play.core.install.model.AppUpdateType.FLEXIBLE import com.google.android.play.core.install.model.AppUpdateType.IMMEDIATE import com.google.android.play.core.install.model.InstallStatus import com.google.android.play.core.install.model.UpdateAvailability +import com.google.firebase.crashlytics.FirebaseCrashlytics import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @@ -45,6 +46,11 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) startInAppUpdateCheck() enableEdgeToEdge() + + if (BuildConfig.ENABLE_CRASHLYTICS) { + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) + } + setContent { VentNoteTheme { val navController = rememberNavController() diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt index 1845606..04d1851 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/BackupPage.kt @@ -11,7 +11,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.CloudOff +import androidx.compose.material.icons.filled.Lock import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator @@ -281,7 +281,7 @@ private fun SignedOutStateContent( contentAlignment = Alignment.Center ) { Icon( - imageVector = Icons.Filled.CloudOff, + imageVector = Icons.Filled.Lock, contentDescription = null, modifier = Modifier.size(48.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/button/SignInButton.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/button/SignInButton.kt index bba714c..6ee544f 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/button/SignInButton.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/button/SignInButton.kt @@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Login +import androidx.compose.material.icons.filled.Person import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator @@ -105,7 +105,7 @@ fun SignInButton( verticalAlignment = Alignment.CenterVertically ) { Icon( - imageVector = Icons.AutoMirrored.Filled.Login, + imageVector = Icons.Filled.Person, contentDescription = null, modifier = Modifier.size(20.dp) ) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt index ba2fb4e..15ce55e 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt @@ -19,12 +19,10 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.CloudDownload -import androidx.compose.material.icons.filled.CloudOff -import androidx.compose.material.icons.filled.CloudUpload import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.ErrorOutline import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card @@ -188,7 +186,7 @@ fun EmptyBackupListContainer( contentAlignment = Alignment.Center ) { Icon( - imageVector = Icons.Filled.CloudOff, + imageVector = Icons.Filled.Warning, contentDescription = null, modifier = Modifier.size(48.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer @@ -236,7 +234,7 @@ fun EmptyBackupListContainer( verticalAlignment = Alignment.CenterVertically ) { Icon( - imageVector = Icons.Filled.CloudUpload, + imageVector = Icons.Filled.Share, contentDescription = null, modifier = Modifier.size(20.dp) ) @@ -283,7 +281,7 @@ fun BackupListContainer( verticalAlignment = Alignment.CenterVertically ) { Icon( - imageVector = Icons.Filled.CloudUpload, + imageVector = Icons.Filled.Share, contentDescription = null, modifier = Modifier.size(20.dp) ) @@ -345,7 +343,7 @@ fun BackupListContainer( contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp) ) { Icon( - imageVector = Icons.Filled.CloudDownload, + imageVector = Icons.Filled.Refresh, contentDescription = stringResource(R.string.restore_icon), modifier = Modifier.size(18.dp) ) @@ -398,10 +396,10 @@ fun BackupFailedContainer( contentAlignment = Alignment.Center ) { Icon( - imageVector = Icons.Filled.ErrorOutline, + imageVector = Icons.Filled.Warning, contentDescription = null, modifier = Modifier.size(48.dp), - tint = MaterialTheme.colorScheme.onPrimaryContainer + tint = MaterialTheme.colorScheme.error ) } } diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/navbar/AppBar.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/navbar/AppBar.kt index 5aafdc2..6384265 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/navbar/AppBar.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/navbar/AppBar.kt @@ -3,7 +3,7 @@ package com.digiventure.ventnote.feature.backup.components.navbar import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.automirrored.filled.Logout +import androidx.compose.material.icons.filled.Lock import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -70,7 +70,7 @@ fun TrailingMenuIcons( onLogoutRequest: () -> Unit, ) { TopNavBarIcon( - Icons.AutoMirrored.Filled.Logout, + Icons.Filled.Lock, stringResource(R.string.logout_nav_icon), modifier = Modifier.semantics { }) { onLogoutRequest() diff --git a/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt b/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt index 780d7d9..8d44028 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt @@ -12,12 +12,13 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Bedtime -import androidx.compose.material.icons.filled.CloudUpload -import androidx.compose.material.icons.filled.ColorLens -import androidx.compose.material.icons.filled.Shop -import androidx.compose.material.icons.filled.Star -import androidx.compose.material.icons.filled.Update +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.filled.ThumbUp + import androidx.compose.material3.DrawerDefaults import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue @@ -98,18 +99,18 @@ fun NavDrawer( ) { SectionTitle(title = stringResource(id = R.string.about_us)) - NavDrawerItem(leftIcon = Icons.Filled.Star, + NavDrawerItem(leftIcon = Icons.Filled.ThumbUp, title = stringResource(id = R.string.rate_app), subtitle = stringResource(id = R.string.rate_app_description), testTagName = TestTags.RATE_APP_TILE, onClick = { openPlayStore(context, appPath, onError) }) - NavDrawerItem(leftIcon = Icons.Filled.Shop, + NavDrawerItem(leftIcon = Icons.Filled.Search, title = stringResource(id = R.string.more_apps), subtitle = stringResource(id = R.string.more_apps_description), onClick = { openPlayStore(context, devPagePath, onError) }) - NavDrawerItem(leftIcon = Icons.Filled.Update, + NavDrawerItem(leftIcon = Icons.Filled.Info, title = stringResource(id = R.string.app_version), subtitle = BuildConfig.VERSION_NAME, onClick = { }) @@ -117,14 +118,14 @@ fun NavDrawer( SectionTitle(title = stringResource(id = R.string.preferences)) NavDrawerColorPicker( - leftIcon = Icons.Filled.ColorLens, + leftIcon = Icons.Filled.Settings, title = stringResource(id = R.string.theme_color), ) { themeViewModel.updateColorPallet(it.second) } NavDrawerItemColorSchemeSwitch( - leftIcon = Icons.Filled.Bedtime, + leftIcon = Icons.Filled.Person, title = stringResource(id = R.string.theme_setting), currentScheme = currentSchemeName, ) { @@ -134,7 +135,7 @@ fun NavDrawer( SectionTitle(title = stringResource(id = R.string.settings)) NavDrawerItem( - leftIcon = Icons.Filled.CloudUpload, + leftIcon = Icons.Filled.Share, title = stringResource(id = R.string.backup), subtitle = stringResource(id = R.string.backup_description), onClick = { onBackupPressed() }) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItemColorSchemeSwitch.kt b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItemColorSchemeSwitch.kt index c580a98..12788ce 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItemColorSchemeSwitch.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/drawer/components/NavDrawerItemColorSchemeSwitch.kt @@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -97,7 +97,7 @@ fun NavDrawerItemColorSchemeSwitch( } Icon( - Icons.Filled.ChevronRight, + Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null, tint = MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/navbar/EnhancedBottomAppBar.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/navbar/EnhancedBottomAppBar.kt index a18e20c..75a815b 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/navbar/EnhancedBottomAppBar.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/navbar/EnhancedBottomAppBar.kt @@ -14,7 +14,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.filled.Check import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -51,7 +51,7 @@ fun EnhancedBottomAppBar( verticalAlignment = Alignment.CenterVertically ) { EnhancedBottomBarButton( - icon = Icons.Rounded.Check, + icon = Icons.Filled.Check, label = stringResource(R.string.save), onClick = onSaveClick, containerColor = MaterialTheme.colorScheme.primaryContainer, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt index 8c6f25b..2fea88e 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/NoteSection.kt @@ -15,7 +15,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Description +import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -48,7 +48,7 @@ fun NoteSection( modifier = Modifier.padding(bottom = 12.dp) ) { Icon( - imageVector = Icons.Rounded.Description, + imageVector = Icons.Filled.Edit, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/TitleSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/TitleSection.kt index 3cb2457..1f60d79 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/TitleSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_creation/components/section/TitleSection.kt @@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Title +import androidx.compose.material.icons.filled.Info import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -46,7 +46,7 @@ fun TitleSection( modifier = Modifier.padding(bottom = 12.dp) ) { Icon( - imageVector = Icons.Rounded.Title, + imageVector = Icons.Filled.Info, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(24.dp) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/navbar/EnhancedBottomAppBar.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/navbar/EnhancedBottomAppBar.kt index e475134..d0f65df 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/navbar/EnhancedBottomAppBar.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/navbar/EnhancedBottomAppBar.kt @@ -14,10 +14,12 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Check -import androidx.compose.material.icons.rounded.Close -import androidx.compose.material.icons.rounded.DeleteOutline -import androidx.compose.material.icons.rounded.Edit +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit + + import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -60,7 +62,7 @@ fun EnhancedBottomAppBar( if (isEditing) { // Cancel button in editing mode EnhancedBottomBarButton( - icon = Icons.Rounded.Close, + icon = Icons.Filled.Close, label = stringResource(R.string.cancel), onClick = onCancelClick, containerColor = MaterialTheme.colorScheme.primaryContainer, @@ -69,7 +71,7 @@ fun EnhancedBottomAppBar( // Save button in editing mode EnhancedBottomBarButton( - icon = Icons.Rounded.Check, + icon = Icons.Filled.Check, label = stringResource(R.string.save), onClick = onSaveClick, containerColor = MaterialTheme.colorScheme.primaryContainer, @@ -79,7 +81,7 @@ fun EnhancedBottomAppBar( } else { // Edit button in view mode EnhancedBottomBarButton( - icon = Icons.Rounded.Edit, + icon = Icons.Filled.Edit, label = stringResource(R.string.edit), onClick = onEditClick, containerColor = MaterialTheme.colorScheme.secondary, @@ -88,7 +90,7 @@ fun EnhancedBottomAppBar( // Delete button in view mode EnhancedBottomBarButton( - icon = Icons.Rounded.DeleteOutline, + icon = Icons.Filled.Delete, label = stringResource(R.string.delete), onClick = onDeleteClick, containerColor = MaterialTheme.colorScheme.secondaryContainer, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt index 02a6b47..6b1199f 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/NoteSection.kt @@ -15,7 +15,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Description +import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -49,7 +49,7 @@ fun NoteSection( modifier = Modifier.padding(bottom = 12.dp) ) { Icon( - imageVector = Icons.Rounded.Description, + imageVector = Icons.Filled.Edit, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/TitleSection.kt b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/TitleSection.kt index 80cd650..0b11653 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/TitleSection.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/note_detail/components/section/TitleSection.kt @@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Title +import androidx.compose.material.icons.filled.Info import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -47,7 +47,7 @@ fun TitleSection( modifier = Modifier.padding(bottom = 12.dp) ) { Icon( - imageVector = Icons.Rounded.Title, + imageVector = Icons.Filled.Info, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/item/NoteItem.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/item/NoteItem.kt index 34d72f5..b34aab3 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/item/NoteItem.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/item/NoteItem.kt @@ -13,7 +13,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.Check + import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -64,7 +65,7 @@ fun NotesItem( Row(verticalAlignment = Alignment.CenterVertically) { if (isMarked) { TopNavBarIcon( - image = Icons.Filled.CheckCircle, + image = Icons.Filled.Check, "", modifier = Modifier .padding(start = 12.dp) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/navbar/AppBar.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/navbar/AppBar.kt index 90b0d5e..9d03b05 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/navbar/AppBar.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/navbar/AppBar.kt @@ -14,15 +14,13 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Sort -import androidx.compose.material.icons.filled.CheckCircle -import androidx.compose.material.icons.filled.CheckCircleOutline +import androidx.compose.material.icons.automirrored.filled.List +import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.Menu -import androidx.compose.material.icons.filled.RadioButtonUnchecked import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -134,7 +132,7 @@ private fun SelectionTitle( } ) { Icon( - imageVector = Icons.Default.CheckCircle, + imageVector = Icons.Filled.Check, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp) @@ -217,7 +215,7 @@ private fun EnhancedDropdownMenu( modifier = Modifier.fillMaxWidth() ) { Icon( - imageVector = if (allSelected) Icons.Default.CheckCircle else Icons.Default.CheckCircleOutline, + imageVector = if (allSelected) Icons.Filled.Check else Icons.Filled.Check, contentDescription = null, tint = if (allSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), @@ -270,7 +268,7 @@ private fun EnhancedDropdownMenu( modifier = Modifier.fillMaxWidth() ) { Icon( - imageVector = Icons.Default.RadioButtonUnchecked, + imageVector = Icons.Filled.Close, contentDescription = null, tint = if (noneSelected) MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), @@ -385,7 +383,7 @@ fun TrailingMenuIcons( modifier = Modifier.semantics { testTag = TestTags.SORT_ICON_BUTTON } ) { Icon( - imageVector = Icons.AutoMirrored.Outlined.Sort, + imageVector = Icons.AutoMirrored.Filled.List, contentDescription = stringResource(R.string.sort_nav_icon), tint = MaterialTheme.colorScheme.onSurface ) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/sheets/FilterSheet.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/sheets/FilterSheet.kt index 6cc3b96..ee76101 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/sheets/FilterSheet.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/sheets/FilterSheet.kt @@ -11,13 +11,13 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Sort -import androidx.compose.material.icons.outlined.ArrowDownward -import androidx.compose.material.icons.outlined.ArrowUpward -import androidx.compose.material.icons.outlined.DateRange -import androidx.compose.material.icons.outlined.SwapVert -import androidx.compose.material.icons.outlined.Title -import androidx.compose.material.icons.outlined.Update +import androidx.compose.material.icons.automirrored.filled.List +import androidx.compose.material.icons.filled.DateRange +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.filled.Refresh + import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -62,16 +62,16 @@ fun FilterSheet( // Memoized sort options with constants mapping val sortOptions = remember { listOf( - SortOption(R.string.sort_title, Constants.TITLE, Icons.Outlined.Title), - SortOption(R.string.sort_created_date, Constants.CREATED_AT, Icons.Outlined.DateRange), - SortOption(R.string.sort_modified_date, Constants.UPDATED_AT, Icons.Outlined.Update) + SortOption(R.string.sort_title, Constants.TITLE, Icons.Filled.Info), + SortOption(R.string.sort_created_date, Constants.CREATED_AT, Icons.Filled.DateRange), + SortOption(R.string.sort_modified_date, Constants.UPDATED_AT, Icons.Filled.Refresh) ) } val orderOptions = remember { listOf( - OrderOption(R.string.order_ascending, Constants.ASCENDING, Icons.Outlined.ArrowUpward), - OrderOption(R.string.order_descending, Constants.DESCENDING, Icons.Outlined.ArrowDownward) + OrderOption(R.string.order_ascending, Constants.ASCENDING, Icons.Filled.KeyboardArrowUp), + OrderOption(R.string.order_descending, Constants.DESCENDING, Icons.Filled.KeyboardArrowDown) ) } @@ -94,7 +94,7 @@ fun FilterSheet( ) { FilterSection( title = stringResource(R.string.sort_by), - icon = Icons.AutoMirrored.Outlined.Sort + icon = Icons.AutoMirrored.Filled.List ) { LazyRow ( horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -117,7 +117,7 @@ fun FilterSheet( // Order By Section FilterSection( title = stringResource(R.string.order_by), - icon = Icons.Outlined.SwapVert + icon = Icons.AutoMirrored.Filled.List ) { LazyRow( horizontalArrangement = Arrangement.spacedBy(8.dp), diff --git a/app/src/main/java/com/digiventure/ventnote/feature/share_preview/SharePreviewPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/share_preview/SharePreviewPage.kt index 017379e..b293fd0 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/share_preview/SharePreviewPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/share_preview/SharePreviewPage.kt @@ -17,7 +17,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Schedule +import androidx.compose.material.icons.filled.DateRange import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -165,7 +165,7 @@ fun SharePreviewPage( verticalAlignment = Alignment.CenterVertically ) { Icon( - imageVector = Icons.Default.Schedule, + imageVector = Icons.Filled.DateRange, contentDescription = null, modifier = Modifier.size(16.dp), tint = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.7f) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/share_preview/components/navbar/AppBar.kt b/app/src/main/java/com/digiventure/ventnote/feature/share_preview/components/navbar/AppBar.kt index 2759fd1..f3d7163 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/share_preview/components/navbar/AppBar.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/share_preview/components/navbar/AppBar.kt @@ -2,7 +2,7 @@ package com.digiventure.ventnote.feature.share_preview.components.navbar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.automirrored.filled.Help +import androidx.compose.material.icons.filled.Info import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -43,7 +43,7 @@ fun SharePreviewAppBar( } }, actions = { - TopNavBarIcon(Icons.AutoMirrored.Filled.Help, stringResource(R.string.menu_nav_icon), Modifier.semantics { }) { + TopNavBarIcon(Icons.Filled.Info, stringResource(R.string.menu_nav_icon), Modifier.semantics { }) { onHelpPressed() } }, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/share_preview/components/navbar/EnhancedBottomAppBar.kt b/app/src/main/java/com/digiventure/ventnote/feature/share_preview/components/navbar/EnhancedBottomAppBar.kt index cf3dc8d..9324b49 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/share_preview/components/navbar/EnhancedBottomAppBar.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/share_preview/components/navbar/EnhancedBottomAppBar.kt @@ -14,7 +14,8 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Share +import androidx.compose.material.icons.filled.Share + import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -51,7 +52,7 @@ fun EnhancedBottomAppBar( verticalAlignment = Alignment.CenterVertically ) { EnhancedBottomBarButton( - icon = Icons.Rounded.Share, + icon = Icons.Filled.Share, label = stringResource(R.string.share_note), onClick = onCancelClick, containerColor = MaterialTheme.colorScheme.primaryContainer, diff --git a/build.gradle b/build.gradle index 8d9d142..479b277 100644 --- a/build.gradle +++ b/build.gradle @@ -3,8 +3,8 @@ buildscript { classpath 'com.android.tools.build:gradle:8.12.0' // disable for staging purpose -// classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.6' -// classpath 'com.google.gms:google-services:4.4.3' + classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.6' + classpath 'com.google.gms:google-services:4.4.4' } } @@ -32,7 +32,6 @@ ext { play_services_auth_version = "21.4.0" play_app_update_version = "2.1.0" firebase_bom_version = "34.3.0" - material_icons_version = "1.7.8" http_client_gson_version = "2.0.0" google_drive_service_version = "v3-rev136-1.25.0" google_api_client_version = "2.8.1" diff --git a/gradle.properties b/gradle.properties index 3a2a720..baf895d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,4 +22,5 @@ kotlin.code.style=official # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true android.nonFinalResIds=true -#org.gradle.unsafe.configuration-cache=true \ No newline at end of file +android.enableR8.fullMode=true +org.gradle.configuration-cache=true \ No newline at end of file From 969398f20c9b17ff3ee27cacf538b34fe9ae61c3 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Mon, 2 Feb 2026 23:39:51 +0700 Subject: [PATCH 18/26] Feat app-size-enhancement-1.2.: Adjusting pro-guard rule to keep necessary classes or functions --- app/proguard-rules.pro | 41 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 09349d8..d996086 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -54,30 +54,13 @@ void traceEventEnd(); } -# Google Play Services Auth --keep class com.google.android.gms.auth.** { *; } --keep class com.google.android.gms.common.** { *; } --keep class com.google.api.client.** { *; } --keep class com.google.oauth.client.** { *; } --keep class com.google.http.client.** { *; } --keep class com.google.api.client.json.** { *; } --keep class com.google.api.client.http.** { *; } --keep class com.google.api.client.googleapis.** { *; } - -# Google Drive API --keep class com.google.api.services.drive.** { *; } --keep class com.google.api.client.googleapis.services.** { *; } - -# Google API Client for Android --keep class com.google.api.client.googleapis.extensions.android.** { *; } --keep class com.google.api.client.extensions.android.** { *; } - -# Keep all classes related to Google Auth API --keep class com.google.api.services.** { *; } --keep class com.google.auth.** { *; } --keep class com.google.api.client.auth.** { *; } --keep class com.google.http.client.auth.** { *; } --keep class com.google.oauth.client.auth.** { *; } +# Google API Client & Auth (Targeted) +-keep class * extends com.google.api.client.json.GenericJson { *; } +-keep class com.google.api.client.util.** { *; } + +# Google Drive API Models +-keep class com.google.api.services.drive.model.** { *; } +-keep class com.google.api.services.drive.Drive$Files$** { *; } -keep class com.google.android.gms.auth.api.signin.GoogleSignInClient { *; @@ -98,8 +81,8 @@ @com.google.api.client.util.Value *; } -# Room database --keep class androidx.room.** { *; } +# Room database (Targeted) +# -keep class androidx.room.** { *; } # Broad rule removed -keep class * extends androidx.room.RoomDatabase { (...); } @@ -125,13 +108,7 @@ # If NoteModel has inner classes, keep them too -keep class com.digiventure.ventnote.data.persistence.NoteModel$* { *; } -# Keep the GoogleDriveService class and all its methods --keep class com.digiventure.ventnote.data.google_drive.GoogleDriveService { *; } - -# Prevent obfuscation of the DatabaseProxy class as it is used within GoogleDriveService --keep class com.digiventure.ventnote.module.proxy.DatabaseProxy { *; } --keep class com.digiventure.ventnote.feature.backup.viewmodel.AuthVM # Keep any classes that are used in Gson serialization -keep class * implements com.google.gson.TypeAdapterFactory From 9d7d1c8fd1d58d335fa29a14bc82540137f7c6f2 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Thu, 5 Feb 2026 11:55:59 +0700 Subject: [PATCH 19/26] Feat app-size-enhancement-1.2.: Change version code & name --- app/build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 154dd74..25c849e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,8 +19,8 @@ android { applicationId "com.digiventure.ventnote" minSdk 23 targetSdk 36 - versionCode 43 - versionName "1.1.1" + versionCode 44 + versionName "1.2.0" testInstrumentationRunner "com.digiventure.utils.CustomTestRunner" vectorDrawables { @@ -86,6 +86,7 @@ android { excludes += 'META-INF/notice.txt' excludes += 'META-INF/ASL2.0' excludes += 'META-INF/*.kotlin_module' + excludes += 'META-INF/services/org.jetbrains.kotlin.*' } } From 41eca02357808364e27114a28b960861b9978ea0 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Fri, 6 Feb 2026 14:28:42 +0700 Subject: [PATCH 20/26] Feat app-size-enhancement-1.2.: Add manual update check when automatic update is not prompted --- .../com/digiventure/ventnote/MainActivity.kt | 44 +++++++++++-------- .../ventnote/feature/drawer/NavDrawer.kt | 4 +- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt index b70215a..e4892ce 100644 --- a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt +++ b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt @@ -1,6 +1,7 @@ package com.digiventure.ventnote import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -44,7 +45,20 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - startInAppUpdateCheck() + + // Initialize AppUpdate components early (must register launcher before STARTED) + appUpdateManager = AppUpdateManagerFactory.create(this) + updateLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> + if (result.resultCode != RESULT_OK) { + // If update fails or is cancelled, we don't force a re-check here + // unless it was an IMMEDIATE update, but usually, we just let it be. + } + } + addUpdateStatusListener() + + // Check for updates automatically on startup + checkUpdate(isManual = false) + enableEdgeToEdge() if (BuildConfig.ENABLE_CRASHLYTICS) { @@ -73,6 +87,9 @@ class MainActivity : ComponentActivity() { onBackupPressed = { navigationActions.navigateToBackupPage() }, + onUpdateCheckPressed = { + checkUpdate(isManual = true) + }, content = { NavGraph(navHostController = navController, openDrawer = { coroutineScope.launch { drawerState.open() } @@ -97,22 +114,6 @@ class MainActivity : ComponentActivity() { } } - /** - * Initializes the in-app update process by creating an AppUpdateManager, - * registering an activity result launcher for handling update confirmations, - * setting up an update status listener, and initiating the update check. - */ - private fun startInAppUpdateCheck() { - appUpdateManager = AppUpdateManagerFactory.create(this) - updateLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> - if (result.resultCode != RESULT_OK) { - checkUpdate() - } - } - addUpdateStatusListener() - checkUpdate() - } - /** * Adds a listener to handle app update installation status changes. * 1. After the update is downloaded, show a dialog and request user confirmation to restart the app. @@ -131,7 +132,7 @@ class MainActivity : ComponentActivity() { * Checks for available app updates and initiates the update flow if an update is available and allowed. * 1. Before starting an update, register a listener for updates. */ - private fun checkUpdate() { + private fun checkUpdate(isManual: Boolean = false) { appUpdateManager.registerListener(installStateUpdatedListener) val appUpdateInfoTask = appUpdateManager.appUpdateInfo appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> @@ -146,6 +147,13 @@ class MainActivity : ComponentActivity() { return@addOnSuccessListener } } + } else if (isManual) { + android.widget.Toast.makeText(this, "No update available", android.widget.Toast.LENGTH_SHORT).show() + } + } + if (isManual) { + appUpdateInfoTask.addOnFailureListener { + android.widget.Toast.makeText(this, "Failed to check for update", android.widget.Toast.LENGTH_SHORT).show() } } } diff --git a/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt b/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt index 8d44028..82f5b11 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/drawer/NavDrawer.kt @@ -74,6 +74,7 @@ fun NavDrawer( content: @Composable () -> Unit, onError: (String) -> Unit, onBackupPressed: () -> Unit, + onUpdateCheckPressed: () -> Unit, themeViewModel: ThemeBaseVM = hiltViewModel() ) { val context = LocalContext.current @@ -113,7 +114,7 @@ fun NavDrawer( NavDrawerItem(leftIcon = Icons.Filled.Info, title = stringResource(id = R.string.app_version), subtitle = BuildConfig.VERSION_NAME, - onClick = { }) + onClick = { onUpdateCheckPressed() }) SectionTitle(title = stringResource(id = R.string.preferences)) @@ -154,6 +155,7 @@ fun DrawerPreview() { content = { }, onError = {}, onBackupPressed = {}, + onUpdateCheckPressed = {}, themeViewModel = ThemeMockVM() ) } From 1705543015c5c66dbd26be99cb945a50eaf4fe03 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Fri, 6 Feb 2026 16:58:52 +0700 Subject: [PATCH 21/26] Feat app-size-enhancement-1.2.: Remove unused imports --- app/build.gradle | 7 ++++--- app/src/main/java/com/digiventure/ventnote/MainActivity.kt | 1 - .../feature/backup/components/list/BackupFileList.kt | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 25c849e..4845792 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ android { applicationId "com.digiventure.ventnote" minSdk 23 targetSdk 36 - versionCode 44 + versionCode 45 versionName "1.2.0" testInstrumentationRunner "com.digiventure.utils.CustomTestRunner" @@ -43,8 +43,9 @@ android { buildConfigField "boolean", "ENABLE_CRASHLYTICS", "true" } debug { - minifyEnabled true - shrinkResources true + minifyEnabled false + shrinkResources false + debuggable true signingConfig signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' buildConfigField "boolean", "ENABLE_CRASHLYTICS", "false" diff --git a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt index e4892ce..e037eaf 100644 --- a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt +++ b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt @@ -1,7 +1,6 @@ package com.digiventure.ventnote import android.os.Bundle -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge diff --git a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt index 15ce55e..75de8c2 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/backup/components/list/BackupFileList.kt @@ -32,7 +32,6 @@ import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect From f0976ed3198a02421a5961f1b13ce0312695f7e7 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Fri, 6 Feb 2026 17:03:09 +0700 Subject: [PATCH 22/26] Feat app-size-enhancement-1.2.: Remove empty conditionals & change several line of code in the gradle to modern DSL --- app/build.gradle | 38 +++++++++---------- .../com/digiventure/ventnote/MainActivity.kt | 7 +--- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4845792..f902f6b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' @@ -13,12 +15,12 @@ plugins { android { namespace 'com.digiventure.ventnote' - compileSdk 36 + compileSdk = 36 defaultConfig { applicationId "com.digiventure.ventnote" minSdk 23 - targetSdk 36 + targetSdk = 36 versionCode 45 versionName "1.2.0" @@ -57,8 +59,10 @@ android { targetCompatibility JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = '17' + kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } } buildFeatures { @@ -70,24 +74,20 @@ android { kotlinCompilerExtensionVersion '1.5.15' } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } packaging { resources { - excludes += '/META-INF/{AL2.0,LGPL2.1}' - excludes += 'META-INF/DEPENDENCIES' - excludes += 'META-INF/LICENSE' - excludes += 'META-INF/LICENSE.txt' - excludes += 'META-INF/license.txt' - excludes += 'META-INF/NOTICE' - excludes += 'META-INF/NOTICE.txt' - excludes += 'META-INF/notice.txt' - excludes += 'META-INF/ASL2.0' - excludes += 'META-INF/*.kotlin_module' - excludes += 'META-INF/services/org.jetbrains.kotlin.*' + excludes.add('/META-INF/{AL2.0,LGPL2.1}') + excludes.add('META-INF/DEPENDENCIES') + excludes.add('META-INF/LICENSE') + excludes.add('META-INF/LICENSE.txt') + excludes.add('META-INF/license.txt') + excludes.add('META-INF/NOTICE') + excludes.add('META-INF/NOTICE.txt') + excludes.add('META-INF/notice.txt') + excludes.add('META-INF/ASL2.0') + excludes.add('META-INF/*.kotlin_module') + excludes.add('META-INF/services/org.jetbrains.kotlin.*') } } diff --git a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt index e037eaf..1434ae6 100644 --- a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt +++ b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt @@ -47,12 +47,7 @@ class MainActivity : ComponentActivity() { // Initialize AppUpdate components early (must register launcher before STARTED) appUpdateManager = AppUpdateManagerFactory.create(this) - updateLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> - if (result.resultCode != RESULT_OK) { - // If update fails or is cancelled, we don't force a re-check here - // unless it was an IMMEDIATE update, but usually, we just let it be. - } - } + updateLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {} addUpdateStatusListener() // Check for updates automatically on startup From 64b726d196397570b362d26b00ef3e39987867b6 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Fri, 6 Feb 2026 17:40:47 +0700 Subject: [PATCH 23/26] Feat bugfix-1.2.0: Fix restore note backup always replace & create new notes (the sequence is messed up) --- .../ventnote/data/google_drive/GoogleDriveService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt b/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt index f47e83d..2326dee 100644 --- a/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt +++ b/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt @@ -64,7 +64,7 @@ class GoogleDriveService @Inject constructor( Gson().fromJson(it, Array::class.java).toList() } ?: emptyList() - proxy.dao().upsertNotesWithTimestamp(notes) + proxy.dao().upsertNotes(notes) Result.success(Unit) } catch (e: Exception) { Result.failure(e) From 4c2bd0448b8fed47a097a5538a856a9049613f16 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sat, 7 Feb 2026 12:08:14 +0700 Subject: [PATCH 24/26] Feat note-widget-1.2.0: Adding fully functional widgets that show list of notes, enhance startup times when navigated from widget --- app/build.gradle | 12 +++ .../3.json | 84 +++++++++++++++++++ .../data/persistence/MigrationTest.kt | 53 ++++++++++++ app/src/main/AndroidManifest.xml | 16 ++++ .../com/digiventure/ventnote/MainActivity.kt | 68 +++++++++++++-- .../digiventure/ventnote/commons/DateUtil.kt | 42 ++++++---- .../ventnote/config/NoteDatabase.kt | 28 +++++-- .../data/google_drive/GoogleDriveService.kt | 7 ++ .../ventnote/data/persistence/NoteDAO.kt | 3 + .../data/persistence/NoteLocalService.kt | 10 +++ .../ventnote/data/persistence/NoteModel.kt | 10 ++- .../ventnote/feature/notes/NotesPage.kt | 6 +- .../feature/notes/components/item/NoteItem.kt | 5 +- .../feature/widget/NoteWidgetFactory.kt | 52 ++++++++++++ .../feature/widget/NoteWidgetProvider.kt | 70 ++++++++++++++++ .../feature/widget/NoteWidgetService.kt | 10 +++ .../res/drawable/rounded_item_background.xml | 4 + .../drawable/rounded_widget_background.xml | 5 ++ app/src/main/res/layout/note_widget_item.xml | 32 +++++++ .../main/res/layout/note_widget_layout.xml | 60 +++++++++++++ app/src/main/res/values-night/colors.xml | 9 ++ app/src/main/res/values/colors.xml | 6 ++ app/src/main/res/xml/note_widget_info.xml | 9 ++ 23 files changed, 566 insertions(+), 35 deletions(-) create mode 100644 app/schemas/com.digiventure.ventnote.config.NoteDatabase/3.json create mode 100644 app/src/androidTest/java/com/digiventure/ventnote/data/persistence/MigrationTest.kt create mode 100644 app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactory.kt create mode 100644 app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetProvider.kt create mode 100644 app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetService.kt create mode 100644 app/src/main/res/drawable/rounded_item_background.xml create mode 100644 app/src/main/res/drawable/rounded_widget_background.xml create mode 100644 app/src/main/res/layout/note_widget_item.xml create mode 100644 app/src/main/res/layout/note_widget_layout.xml create mode 100644 app/src/main/res/values-night/colors.xml create mode 100644 app/src/main/res/xml/note_widget_info.xml diff --git a/app/build.gradle b/app/build.gradle index f902f6b..45e0f08 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,6 +6,7 @@ plugins { id 'com.google.dagger.hilt.android' id 'kotlin-kapt' id 'kotlin-parcelize' + id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.10' id 'org.jetbrains.kotlin.plugin.compose' version '2.2.10' // Google Services & Firebase @@ -57,6 +58,7 @@ android { compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 + coreLibraryDesugaringEnabled true } kotlin { @@ -94,6 +96,10 @@ android { lint { abortOnError false } + + sourceSets { + androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) + } } @@ -126,6 +132,8 @@ dependencies { // Compose Navigation implementation "androidx.navigation:navigation-compose:$navigation_compose_version" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.8.0" // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" @@ -169,6 +177,8 @@ dependencies { testImplementation "org.mockito:mockito-inline:$mockito_inline_version" androidTestImplementation "androidx.test.ext:junit-ktx:$junit_ktx_version" + androidTestImplementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0" + androidTestImplementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.8.0" testImplementation "androidx.arch.core:core-testing:$core_testing_version" @@ -185,6 +195,8 @@ dependencies { implementation "com.google.http-client:google-http-client-gson:$http_client_gson_version" implementation "com.google.apis:google-api-services-drive:$google_drive_service_version" implementation "com.google.api-client:google-api-client-android:$google_api_client_version" + + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' } kapt { diff --git a/app/schemas/com.digiventure.ventnote.config.NoteDatabase/3.json b/app/schemas/com.digiventure.ventnote.config.NoteDatabase/3.json new file mode 100644 index 0000000..fa5bdcd --- /dev/null +++ b/app/schemas/com.digiventure.ventnote.config.NoteDatabase/3.json @@ -0,0 +1,84 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "c10dc54920a5fcd5ed352c147c816ee5", + "entities": [ + { + "tableName": "note_table", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `note` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `updated_at` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_note_table_title", + "unique": false, + "columnNames": [ + "title" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_note_table_title` ON `${TABLE_NAME}` (`title`)" + }, + { + "name": "index_note_table_created_at", + "unique": false, + "columnNames": [ + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_note_table_created_at` ON `${TABLE_NAME}` (`created_at`)" + }, + { + "name": "index_note_table_updated_at", + "unique": false, + "columnNames": [ + "updated_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_note_table_updated_at` ON `${TABLE_NAME}` (`updated_at`)" + } + ] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c10dc54920a5fcd5ed352c147c816ee5')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/digiventure/ventnote/data/persistence/MigrationTest.kt b/app/src/androidTest/java/com/digiventure/ventnote/data/persistence/MigrationTest.kt new file mode 100644 index 0000000..fed451f --- /dev/null +++ b/app/src/androidTest/java/com/digiventure/ventnote/data/persistence/MigrationTest.kt @@ -0,0 +1,53 @@ +package com.digiventure.ventnote.data.persistence + +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.digiventure.ventnote.config.NoteDatabase +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException + +@RunWith(AndroidJUnit4::class) +class MigrationTest { + private val TEST_DB = "migration-test" + + @get:Rule + val helper: MigrationTestHelper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + NoteDatabase::class.java.canonicalName, + FrameworkSQLiteOpenHelperFactory() + ) + + @Test + @Throws(IOException::class) + fun migrate1To2() { + var db = helper.createDatabase(TEST_DB, 1) + + // db has schema version 1. insert some data using SQL queries. + // You cannot use DAO classes because they expect the latest schema. + db.execSQL("INSERT INTO note_table (title, note, created_at, updated_at) VALUES ('Title 1', 'Note 1', 123456789, 123456789)") + + // Prepare for the next version. + db.close() + + // Re-open the database with version 2 and provide the manual migration. + db = helper.runMigrationsAndValidate(TEST_DB, 2, true, NoteDatabase.MIGRATION_1_2) + } + + @Test + @Throws(IOException::class) + fun migrate2To3() { + var db = helper.createDatabase(TEST_DB, 2) + + // db has schema version 2. insert some data. + db.execSQL("INSERT INTO note_table (title, note, created_at, updated_at) VALUES ('Title 2', 'Note 2', 987654321, 987654321)") + + db.close() + + // Re-open with version 3 and provide the manual migration. + db = helper.runMigrationsAndValidate(TEST_DB, 3, true, NoteDatabase.MIGRATION_2_3) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 37e2056..5b14871 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ @@ -30,6 +31,21 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt index 1434ae6..5ace375 100644 --- a/app/src/main/java/com/digiventure/ventnote/MainActivity.kt +++ b/app/src/main/java/com/digiventure/ventnote/MainActivity.kt @@ -1,5 +1,6 @@ package com.digiventure.ventnote +import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -12,13 +13,19 @@ import androidx.compose.material3.DrawerValue import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.rememberDrawerState +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.lifecycle.lifecycleScope import androidx.navigation.compose.rememberNavController import com.digiventure.ventnote.components.dialog.TextDialog import com.digiventure.ventnote.feature.drawer.NavDrawer +import com.digiventure.ventnote.module.proxy.DatabaseProxy import com.digiventure.ventnote.navigation.NavGraph import com.digiventure.ventnote.navigation.PageNavigation import com.digiventure.ventnote.ui.theme.components.VentNoteTheme @@ -33,7 +40,9 @@ import com.google.android.play.core.install.model.InstallStatus import com.google.android.play.core.install.model.UpdateAvailability import com.google.firebase.crashlytics.FirebaseCrashlytics import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -41,24 +50,34 @@ class MainActivity : ComponentActivity() { private lateinit var appUpdateManager: AppUpdateManager private var isDialogShowed = false private lateinit var updateLauncher: ActivityResultLauncher + private var currentIntent by mutableStateOf(null) + + @Inject + lateinit var databaseProxy: DatabaseProxy override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (BuildConfig.ENABLE_CRASHLYTICS) { + lifecycleScope.launch(Dispatchers.IO) { + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) + } + } + + // Pre-warm the database in the background to avoid main-thread blockage on first access + lifecycleScope.launch(Dispatchers.IO) { + databaseProxy.getObject() + } + + currentIntent = intent + // Initialize AppUpdate components early (must register launcher before STARTED) appUpdateManager = AppUpdateManagerFactory.create(this) updateLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {} addUpdateStatusListener() - // Check for updates automatically on startup - checkUpdate(isManual = false) - enableEdgeToEdge() - if (BuildConfig.ENABLE_CRASHLYTICS) { - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) - } - setContent { VentNoteTheme { val navController = rememberNavController() @@ -67,9 +86,31 @@ class MainActivity : ComponentActivity() { } val drawerState = rememberDrawerState(DrawerValue.Closed) - val coroutineScope = rememberCoroutineScope() + // Handle intent from Widget + androidx.compose.runtime.LaunchedEffect(currentIntent) { + currentIntent?.let { intent -> + val noteId = intent.getStringExtra("noteId") + val actionCreate = intent.getBooleanExtra("action_create", false) + + if (noteId != null) { + navigationActions.navigateToDetailPage(noteId.toInt()) + clearIntent() + currentIntent = null + } else if (actionCreate) { + navigationActions.navigateToCreatePage() + clearIntent() + currentIntent = null + } + } + } + + // Check for updates automatically on startup without blocking initial frame + LaunchedEffect(Unit) { + checkUpdate(isManual = false) + } + Surface( modifier = Modifier.safeDrawingPadding(), color = MaterialTheme.colorScheme.primary, @@ -188,4 +229,15 @@ class MainActivity : ComponentActivity() { private fun showDialogForCompleteUpdate() { isDialogShowed = true } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + currentIntent = intent + } + + private fun clearIntent() { + intent?.removeExtra("noteId") + intent?.removeExtra("action_create") + } } \ No newline at end of file diff --git a/app/src/main/java/com/digiventure/ventnote/commons/DateUtil.kt b/app/src/main/java/com/digiventure/ventnote/commons/DateUtil.kt index 839ae00..c9b9e1c 100644 --- a/app/src/main/java/com/digiventure/ventnote/commons/DateUtil.kt +++ b/app/src/main/java/com/digiventure/ventnote/commons/DateUtil.kt @@ -1,26 +1,40 @@ package com.digiventure.ventnote.commons -import java.text.ParseException -import java.text.SimpleDateFormat +import java.time.ZoneId +import java.time.format.DateTimeFormatter import java.util.Date import java.util.Locale object DateUtil { + private val defaultFormatter = DateTimeFormatter.ofPattern("EEEE, MMMM d h:mm a", Locale.getDefault()) + private val zoneId = ZoneId.systemDefault() + /** - * Return formatted date string - * @param format pattern can be see here https://developer.android.com/reference/kotlin/android/icu/text/SimpleDateFormat - * @param dateString is raw date in string - * */ - fun convertDateString(format: String, dateString: String): String { + * Return formatted date string using modern java.time API + * @param date the Date object to format + */ + fun formatNoteDate(date: Date): String { return try { - val inputDateFormat = SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.getDefault()) - val inputDate = inputDateFormat.parse(dateString) - - val outputDateFormat = SimpleDateFormat(format, Locale.getDefault()) - val outputDateString = inputDate?.let { outputDateFormat.format(it) } + val instant = date.toInstant() + val localDateTime = instant.atZone(zoneId).toLocalDateTime() + defaultFormatter.format(localDateTime) + } catch (e: Exception) { + "" + } + } - (outputDateString?.format(dateString) ?: Date()).toString() - } catch (e: ParseException) { + /** + * Legacy support for string-based conversion, optimized with java.time + */ + fun convertDateString(format: String, dateString: String): String { + return try { + // "EEE MMM dd HH:mm:ss zzz yyyy" is the default Date.toString() format + val formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US) + val dateTime = java.time.ZonedDateTime.parse(dateString, formatter) + + val outputFormatter = DateTimeFormatter.ofPattern(format, Locale.getDefault()) + outputFormatter.format(dateTime) + } catch (e: Exception) { "" } } diff --git a/app/src/main/java/com/digiventure/ventnote/config/NoteDatabase.kt b/app/src/main/java/com/digiventure/ventnote/config/NoteDatabase.kt index 68ec959..120aac9 100644 --- a/app/src/main/java/com/digiventure/ventnote/config/NoteDatabase.kt +++ b/app/src/main/java/com/digiventure/ventnote/config/NoteDatabase.kt @@ -1,12 +1,13 @@ package com.digiventure.ventnote.config import android.content.Context -import androidx.room.AutoMigration import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverter import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import com.digiventure.ventnote.commons.Constants import com.digiventure.ventnote.data.persistence.NoteDAO import com.digiventure.ventnote.data.persistence.NoteModel @@ -26,11 +27,8 @@ object DateConverters { @Database( entities = [NoteModel::class], - version = 2, - exportSchema = true, - autoMigrations = [ - AutoMigration (from = 1, to = 2) - ] + version = 3, + exportSchema = true ) @TypeConverters(DateConverters::class) abstract class NoteDatabase: RoomDatabase() { @@ -40,6 +38,20 @@ abstract class NoteDatabase: RoomDatabase() { @Volatile private var instance: NoteDatabase? = null + val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(db: SupportSQLiteDatabase) { + // No changes between version 1 and 2 + } + } + + val MIGRATION_2_3 = object : Migration(2, 3) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE INDEX IF NOT EXISTS `index_note_table_title` ON `note_table` (`title`)") + db.execSQL("CREATE INDEX IF NOT EXISTS `index_note_table_created_at` ON `note_table` (`created_at`)") + db.execSQL("CREATE INDEX IF NOT EXISTS `index_note_table_updated_at` ON `note_table` (`updated_at`)") + } + } + fun getInstance(context : Context): NoteDatabase { if (instance == null) { synchronized(this) { @@ -47,7 +59,9 @@ abstract class NoteDatabase: RoomDatabase() { context, NoteDatabase::class.java, Constants.BACKUP_FILE_NAME - ).build() + ) + .addMigrations(MIGRATION_1_2, MIGRATION_2_3) + .build() } } diff --git a/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt b/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt index 2326dee..198b11e 100644 --- a/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt +++ b/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt @@ -1,6 +1,8 @@ package com.digiventure.ventnote.data.google_drive +import android.app.Application import com.digiventure.ventnote.data.persistence.NoteModel +import com.digiventure.ventnote.feature.widget.NoteWidgetProvider import com.digiventure.ventnote.module.proxy.DatabaseProxy import com.google.api.client.http.ByteArrayContent import com.google.api.services.drive.Drive @@ -12,6 +14,7 @@ import kotlinx.coroutines.withContext import javax.inject.Inject class GoogleDriveService @Inject constructor( + private val app: Application, private val proxy: DatabaseProxy, ) { companion object { @@ -65,6 +68,10 @@ class GoogleDriveService @Inject constructor( } ?: emptyList() proxy.dao().upsertNotes(notes) + + // Refresh widget after restore + NoteWidgetProvider.refreshWidgets(app) + Result.success(Unit) } catch (e: Exception) { Result.failure(e) diff --git a/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteDAO.kt b/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteDAO.kt index acd3493..ce8cee1 100644 --- a/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteDAO.kt +++ b/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteDAO.kt @@ -21,6 +21,9 @@ interface NoteDAO { " CASE WHEN :sortBy = 'updated_at' AND :orderBy = 'DESC' THEN updated_at END DESC") fun getNotes(sortBy: String, orderBy: String): Flow> + @Query("SELECT * FROM note_table ORDER BY created_at DESC") + fun getSyncNotes(): List + @Query("SELECT * FROM note_table WHERE id = :id") fun getNoteDetail(id: Int): Flow diff --git a/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteLocalService.kt b/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteLocalService.kt index 2ebeac4..b33ecc5 100644 --- a/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteLocalService.kt +++ b/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteLocalService.kt @@ -1,14 +1,18 @@ package com.digiventure.ventnote.data.persistence +import android.app.Application import com.digiventure.ventnote.commons.ErrorMessage +import com.digiventure.ventnote.feature.widget.NoteWidgetProvider import com.digiventure.ventnote.module.proxy.DatabaseProxy import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import javax.inject.Inject class NoteLocalService @Inject constructor( + private val app: Application, private val proxy: DatabaseProxy ) { fun getNoteList(sortBy: String, order: String): Flow>> { @@ -23,6 +27,8 @@ class NoteLocalService @Inject constructor( flow { val result = (proxy.dao().deleteNotes(*notes) == notes.size) emit(Result.success(result)) + }.onEach { + if (it.isSuccess) NoteWidgetProvider.refreshWidgets(app) }.catch { emit(Result.failure(RuntimeException(ErrorMessage.FAILED_DELETE_ROOM))) } @@ -39,6 +45,8 @@ class NoteLocalService @Inject constructor( flow { val result = proxy.dao().updateWithTimestamp(note) >= 1 emit(Result.success(result)) + }.onEach { + if (it.isSuccess) NoteWidgetProvider.refreshWidgets(app) }.catch { emit(Result.failure(RuntimeException(ErrorMessage.FAILED_UPDATE_NOTE_ROOM))) } @@ -47,6 +55,8 @@ class NoteLocalService @Inject constructor( flow { val result = proxy.dao().insertWithTimestamp(note) != -1L emit(Result.success(result)) + }.onEach { + if (it.isSuccess) NoteWidgetProvider.refreshWidgets(app) }.catch { emit(Result.failure(RuntimeException(ErrorMessage.FAILED_INSERT_NOTE_ROOM))) } diff --git a/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteModel.kt b/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteModel.kt index 573b42d..9cacb95 100644 --- a/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteModel.kt +++ b/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteModel.kt @@ -3,12 +3,20 @@ package com.digiventure.ventnote.data.persistence import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Index import androidx.room.PrimaryKey import kotlinx.parcelize.Parcelize import java.util.Date @Parcelize -@Entity(tableName = "note_table") +@Entity( + tableName = "note_table", + indices = [ + Index(value = ["title"]), + Index(value = ["created_at"]), + Index(value = ["updated_at"]) + ] +) data class NoteModel( @PrimaryKey(autoGenerate = true) val id: Int, @ColumnInfo(name = "title") val title: String, diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt index 76cfd25..8785e08 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/NotesPage.kt @@ -137,7 +137,11 @@ fun NotesPage( } LaunchedEffect(loadingState) { - showLoadingDialog = loadingState == true + // Only show loading dialog if there's already some data or if it's a long operation + // For initial load, we prefer a non-blocking experience + if (filteredNotes.isNotEmpty()) { + showLoadingDialog = loadingState == true + } } val noteIsDeletedText = stringResource(R.string.note_is_successfully_deleted) diff --git a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/item/NoteItem.kt b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/item/NoteItem.kt index b34aab3..cefd627 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/notes/components/item/NoteItem.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/notes/components/item/NoteItem.kt @@ -109,10 +109,7 @@ fun NotesItem( Spacer(modifier = Modifier.height(8.dp)) Text( - text = DateUtil.convertDateString( - "EEEE, MMMM d h:mm a", - data.updatedAt.toString() - ), + text = DateUtil.formatNoteDate(data.updatedAt), maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodySmall.copy( diff --git a/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactory.kt b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactory.kt new file mode 100644 index 0000000..03a6687 --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactory.kt @@ -0,0 +1,52 @@ +package com.digiventure.ventnote.feature.widget + +import android.content.Context +import android.content.Intent +import android.widget.RemoteViews +import android.widget.RemoteViewsService +import com.digiventure.ventnote.R +import com.digiventure.ventnote.config.NoteDatabase +import com.digiventure.ventnote.data.persistence.NoteModel + +class NoteWidgetFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory { + private var notes: List = emptyList() + + override fun onCreate() {} + + override fun onDataSetChanged() { + // Fetch notes from database + notes = NoteDatabase.getInstance(context).dao().getSyncNotes() + } + + override fun onDestroy() { + notes = emptyList() + } + + override fun getCount(): Int = notes.size + + override fun getViewAt(position: Int): RemoteViews { + if (position >= notes.size) return RemoteViews(context.packageName, R.layout.note_widget_item) + + val note = notes[position] + val views = RemoteViews(context.packageName, R.layout.note_widget_item) + + views.setTextViewText(R.id.widget_item_title, note.title) + views.setTextViewText(R.id.widget_item_content, note.note) + + // Fill in specific data for the click template + val fillInIntent = Intent().apply { + putExtra("noteId", note.id.toString()) + } + views.setOnClickFillInIntent(R.id.widget_item_root, fillInIntent) + + return views + } + + override fun getLoadingView(): RemoteViews? = null + + override fun getViewTypeCount(): Int = 1 + + override fun getItemId(position: Int): Long = notes[position].id.toLong() + + override fun hasStableIds(): Boolean = true +} diff --git a/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetProvider.kt b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetProvider.kt new file mode 100644 index 0000000..c906143 --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetProvider.kt @@ -0,0 +1,70 @@ +package com.digiventure.ventnote.feature.widget + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.widget.RemoteViews +import com.digiventure.ventnote.MainActivity +import com.digiventure.ventnote.R + +class NoteWidgetProvider : AppWidgetProvider() { + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + for (appWidgetId in appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId) + } + super.onUpdate(context, appWidgetManager, appWidgetIds) + } + + companion object { + fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { + val views = RemoteViews(context.packageName, R.layout.note_widget_layout) + + // Set up the intent for the RemoteViewsService + val intent = Intent(context, NoteWidgetService::class.java).apply { + putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME)) + } + views.setRemoteAdapter(R.id.widget_list, intent) + views.setEmptyView(R.id.widget_list, R.id.widget_empty_view) + + // Set up click template for list items + val clickIntent = Intent(context, MainActivity::class.java) + val clickPendingIntent = PendingIntent.getActivity( + context, 0, clickIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + ) + views.setPendingIntentTemplate(R.id.widget_list, clickPendingIntent) + + // Set up click intent for Add button + val addIntent = Intent(context, MainActivity::class.java).apply { + putExtra("action_create", true) + } + val addPendingIntent = PendingIntent.getActivity( + context, 1, addIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + views.setOnClickPendingIntent(R.id.widget_add_button, addPendingIntent) + + appWidgetManager.updateAppWidget(appWidgetId, views) + } + + /** + * Refreshes all active widgets by notifying the AppWidgetManager that the data has changed. + * This should be called whenever the note database is modified. + */ + fun refreshWidgets(context: Context) { + val appWidgetManager = AppWidgetManager.getInstance(context) + val componentName = android.content.ComponentName(context, NoteWidgetProvider::class.java) + val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName) + + if (appWidgetIds.isNotEmpty()) { + // Notifies the RemoteViewsService (NoteWidgetService) that data has changed + // This triggers onDataSetChanged() in NoteWidgetFactory + appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_list) + } + } + } +} diff --git a/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetService.kt b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetService.kt new file mode 100644 index 0000000..5e0aa4a --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetService.kt @@ -0,0 +1,10 @@ +package com.digiventure.ventnote.feature.widget + +import android.content.Intent +import android.widget.RemoteViewsService + +class NoteWidgetService : RemoteViewsService() { + override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { + return NoteWidgetFactory(this.applicationContext) + } +} diff --git a/app/src/main/res/drawable/rounded_item_background.xml b/app/src/main/res/drawable/rounded_item_background.xml new file mode 100644 index 0000000..79fbe1f --- /dev/null +++ b/app/src/main/res/drawable/rounded_item_background.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/rounded_widget_background.xml b/app/src/main/res/drawable/rounded_widget_background.xml new file mode 100644 index 0000000..18c0ec6 --- /dev/null +++ b/app/src/main/res/drawable/rounded_widget_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/note_widget_item.xml b/app/src/main/res/layout/note_widget_item.xml new file mode 100644 index 0000000..83b4d6b --- /dev/null +++ b/app/src/main/res/layout/note_widget_item.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/app/src/main/res/layout/note_widget_layout.xml b/app/src/main/res/layout/note_widget_layout.xml new file mode 100644 index 0000000..ad4d8bf --- /dev/null +++ b/app/src/main/res/layout/note_widget_layout.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..2bd72ad --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,9 @@ + + + #131416 + #202125 + #b4b8ba + #888888 + #b1c5ff + #131416 + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 55344e5..d4f2e0e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,3 +1,9 @@ + #f2f5fa + #FFFFFF + #484b51 + #777777 + #2559bd + #FFFFFF \ No newline at end of file diff --git a/app/src/main/res/xml/note_widget_info.xml b/app/src/main/res/xml/note_widget_info.xml new file mode 100644 index 0000000..5c2fe5d --- /dev/null +++ b/app/src/main/res/xml/note_widget_info.xml @@ -0,0 +1,9 @@ + + + From 9cd4658569fe47fa95cc5915e6a54a728303370b Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sun, 8 Feb 2026 14:37:22 +0700 Subject: [PATCH 25/26] Chore staging: Increase version code --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 45e0f08..1abd46d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,7 +22,7 @@ android { applicationId "com.digiventure.ventnote" minSdk 23 targetSdk = 36 - versionCode 45 + versionCode 46 versionName "1.2.0" testInstrumentationRunner "com.digiventure.utils.CustomTestRunner" From c784d9bdc1e7755db148deddb0f2281dc7d3c5b4 Mon Sep 17 00:00:00 2001 From: Syubban Fakhriya Date: Sun, 8 Feb 2026 16:34:04 +0700 Subject: [PATCH 26/26] Feat staging: Adjust unit test & gradle to prevent read google-service.json when not exist --- app/build.gradle | 11 +++- .../data/google_drive/GoogleDriveService.kt | 5 +- .../data/persistence/NoteLocalService.kt | 11 ++-- .../feature/widget/NoteWidgetFactory.kt | 9 ++- .../feature/widget/NoteWidgetService.kt | 9 ++- .../feature/widget/WidgetRefresher.kt | 14 +++++ .../ventnote/module/ApplicationModule.kt | 29 ++++++--- .../com/digiventure/utils/BaseUnitTest.kt | 2 +- .../ventnote/commons/DateUtilsTest.kt | 27 ++++++-- .../google_drive/GoogleDriveServiceShould.kt | 13 ++-- .../persistence/NoteLocalServiceShould.kt | 12 +++- .../feature/widget/NoteWidgetFactoryTest.kt | 61 +++++++++++++++++++ 12 files changed, 168 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/com/digiventure/ventnote/feature/widget/WidgetRefresher.kt create mode 100644 app/src/test/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactoryTest.kt diff --git a/app/build.gradle b/app/build.gradle index 1abd46d..1b91524 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,10 +8,15 @@ plugins { id 'kotlin-parcelize' id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.10' id 'org.jetbrains.kotlin.plugin.compose' version '2.2.10' +} - // Google Services & Firebase - id 'com.google.gms.google-services' - id 'com.google.firebase.crashlytics' +// Apply Google Services and Crashlytics only if google-services.json exists +if (file('google-services.json').exists()) { + apply plugin: 'com.google.gms.google-services' + apply plugin: 'com.google.firebase.crashlytics' + println 'Google Services and Crashlytics plugins applied' +} else { + println 'google-services.json not found. Skipping Google Services and Crashlytics plugins' } android { diff --git a/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt b/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt index 198b11e..12c5980 100644 --- a/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt +++ b/app/src/main/java/com/digiventure/ventnote/data/google_drive/GoogleDriveService.kt @@ -2,7 +2,7 @@ package com.digiventure.ventnote.data.google_drive import android.app.Application import com.digiventure.ventnote.data.persistence.NoteModel -import com.digiventure.ventnote.feature.widget.NoteWidgetProvider +import com.digiventure.ventnote.feature.widget.WidgetRefresher import com.digiventure.ventnote.module.proxy.DatabaseProxy import com.google.api.client.http.ByteArrayContent import com.google.api.services.drive.Drive @@ -16,6 +16,7 @@ import javax.inject.Inject class GoogleDriveService @Inject constructor( private val app: Application, private val proxy: DatabaseProxy, + private val refresher: WidgetRefresher ) { companion object { private const val FILE_MIME_TYPE = "application/json" @@ -70,7 +71,7 @@ class GoogleDriveService @Inject constructor( proxy.dao().upsertNotes(notes) // Refresh widget after restore - NoteWidgetProvider.refreshWidgets(app) + refresher.refresh(app) Result.success(Unit) } catch (e: Exception) { diff --git a/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteLocalService.kt b/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteLocalService.kt index b33ecc5..d282c15 100644 --- a/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteLocalService.kt +++ b/app/src/main/java/com/digiventure/ventnote/data/persistence/NoteLocalService.kt @@ -2,7 +2,7 @@ package com.digiventure.ventnote.data.persistence import android.app.Application import com.digiventure.ventnote.commons.ErrorMessage -import com.digiventure.ventnote.feature.widget.NoteWidgetProvider +import com.digiventure.ventnote.feature.widget.WidgetRefresher import com.digiventure.ventnote.module.proxy.DatabaseProxy import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch @@ -13,7 +13,8 @@ import javax.inject.Inject class NoteLocalService @Inject constructor( private val app: Application, - private val proxy: DatabaseProxy + private val proxy: DatabaseProxy, + private val refresher: WidgetRefresher ) { fun getNoteList(sortBy: String, order: String): Flow>> { return proxy.dao().getNotes(sortBy, order).map { @@ -28,7 +29,7 @@ class NoteLocalService @Inject constructor( val result = (proxy.dao().deleteNotes(*notes) == notes.size) emit(Result.success(result)) }.onEach { - if (it.isSuccess) NoteWidgetProvider.refreshWidgets(app) + if (it.isSuccess) refresher.refresh(app) }.catch { emit(Result.failure(RuntimeException(ErrorMessage.FAILED_DELETE_ROOM))) } @@ -46,7 +47,7 @@ class NoteLocalService @Inject constructor( val result = proxy.dao().updateWithTimestamp(note) >= 1 emit(Result.success(result)) }.onEach { - if (it.isSuccess) NoteWidgetProvider.refreshWidgets(app) + if (it.isSuccess) refresher.refresh(app) }.catch { emit(Result.failure(RuntimeException(ErrorMessage.FAILED_UPDATE_NOTE_ROOM))) } @@ -56,7 +57,7 @@ class NoteLocalService @Inject constructor( val result = proxy.dao().insertWithTimestamp(note) != -1L emit(Result.success(result)) }.onEach { - if (it.isSuccess) NoteWidgetProvider.refreshWidgets(app) + if (it.isSuccess) refresher.refresh(app) }.catch { emit(Result.failure(RuntimeException(ErrorMessage.FAILED_INSERT_NOTE_ROOM))) } diff --git a/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactory.kt b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactory.kt index 03a6687..38f61ee 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactory.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactory.kt @@ -5,17 +5,20 @@ import android.content.Intent import android.widget.RemoteViews import android.widget.RemoteViewsService import com.digiventure.ventnote.R -import com.digiventure.ventnote.config.NoteDatabase +import com.digiventure.ventnote.module.proxy.DatabaseProxy import com.digiventure.ventnote.data.persistence.NoteModel -class NoteWidgetFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory { +class NoteWidgetFactory( + private val context: Context, + private val proxy: DatabaseProxy +) : RemoteViewsService.RemoteViewsFactory { private var notes: List = emptyList() override fun onCreate() {} override fun onDataSetChanged() { // Fetch notes from database - notes = NoteDatabase.getInstance(context).dao().getSyncNotes() + notes = proxy.dao().getSyncNotes() } override fun onDestroy() { diff --git a/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetService.kt b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetService.kt index 5e0aa4a..90dd878 100644 --- a/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetService.kt +++ b/app/src/main/java/com/digiventure/ventnote/feature/widget/NoteWidgetService.kt @@ -2,9 +2,16 @@ package com.digiventure.ventnote.feature.widget import android.content.Intent import android.widget.RemoteViewsService +import com.digiventure.ventnote.module.proxy.DatabaseProxy +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject +@AndroidEntryPoint class NoteWidgetService : RemoteViewsService() { + @Inject + lateinit var proxy: DatabaseProxy + override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { - return NoteWidgetFactory(this.applicationContext) + return NoteWidgetFactory(this.applicationContext, proxy) } } diff --git a/app/src/main/java/com/digiventure/ventnote/feature/widget/WidgetRefresher.kt b/app/src/main/java/com/digiventure/ventnote/feature/widget/WidgetRefresher.kt new file mode 100644 index 0000000..966026b --- /dev/null +++ b/app/src/main/java/com/digiventure/ventnote/feature/widget/WidgetRefresher.kt @@ -0,0 +1,14 @@ +package com.digiventure.ventnote.feature.widget + +import android.content.Context +import javax.inject.Inject + +interface WidgetRefresher { + fun refresh(context: Context) +} + +class NoteWidgetRefresher @Inject constructor() : WidgetRefresher { + override fun refresh(context: Context) { + NoteWidgetProvider.refreshWidgets(context) + } +} diff --git a/app/src/main/java/com/digiventure/ventnote/module/ApplicationModule.kt b/app/src/main/java/com/digiventure/ventnote/module/ApplicationModule.kt index 01a26b2..8799549 100644 --- a/app/src/main/java/com/digiventure/ventnote/module/ApplicationModule.kt +++ b/app/src/main/java/com/digiventure/ventnote/module/ApplicationModule.kt @@ -2,6 +2,9 @@ package com.digiventure.ventnote.module import android.content.Context import com.digiventure.ventnote.data.local.NoteDataStore +import com.digiventure.ventnote.feature.widget.NoteWidgetRefresher +import com.digiventure.ventnote.feature.widget.WidgetRefresher +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -16,18 +19,24 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -class ApplicationModule { - @Provides +abstract class ApplicationModule { + @Binds @Singleton - fun provideCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob()) + abstract fun bindWidgetRefresher(refresher: NoteWidgetRefresher): WidgetRefresher - @Provides - @Singleton - fun provideExecutorCoroutineDispatcher(): ExecutorCoroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + companion object { + @Provides + @Singleton + fun provideCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob()) - @Provides - @Singleton - fun provideNoteDataStore(@ApplicationContext context: Context): NoteDataStore { - return NoteDataStore(context) + @Provides + @Singleton + fun provideExecutorCoroutineDispatcher(): ExecutorCoroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + + @Provides + @Singleton + fun provideNoteDataStore(@ApplicationContext context: Context): NoteDataStore { + return NoteDataStore(context) + } } } \ No newline at end of file diff --git a/app/src/test/java/com/digiventure/utils/BaseUnitTest.kt b/app/src/test/java/com/digiventure/utils/BaseUnitTest.kt index 3d9bee5..e925e24 100644 --- a/app/src/test/java/com/digiventure/utils/BaseUnitTest.kt +++ b/app/src/test/java/com/digiventure/utils/BaseUnitTest.kt @@ -8,7 +8,7 @@ import org.mockito.junit.MockitoJUnitRunner @RunWith(MockitoJUnitRunner::class) abstract class BaseUnitTest { @get:Rule - var coroutinesTestRule = MainDispatcherRule() + val mainDispatcherRule = MainDispatcherRule() @get:Rule var instantTaskExecutor = InstantTaskExecutorRule() diff --git a/app/src/test/java/com/digiventure/ventnote/commons/DateUtilsTest.kt b/app/src/test/java/com/digiventure/ventnote/commons/DateUtilsTest.kt index 4e9863a..d6ba748 100644 --- a/app/src/test/java/com/digiventure/ventnote/commons/DateUtilsTest.kt +++ b/app/src/test/java/com/digiventure/ventnote/commons/DateUtilsTest.kt @@ -2,26 +2,43 @@ package com.digiventure.ventnote.commons import com.digiventure.utils.BaseUnitTest import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Test +import java.util.Calendar +import java.util.Date +import java.util.TimeZone class DateUtilsTest: BaseUnitTest() { + @Test + fun formatNoteDateShouldReturnFormattedString() { + // Set a fixed date: 2023-10-27 10:30 AM + val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + calendar.set(2023, Calendar.OCTOBER, 27, 10, 30, 0) + val date = calendar.time + + // The default formatter uses EEEE, MMMM d h:mm a + val result = DateUtil.formatNoteDate(date) + + // Assert that it contains basic info + assertTrue(result.contains("October")) + assertTrue(result.contains("27")) + } + @Test fun convertDateStringShouldReturnValidFormattedString() { val expectedDateString = "Thu, Jan 1" assertEquals(expectedDateString, DateUtil.convertDateString( "EEE, MMM d", - "Thu Jan 01 07:00:00 GMT+07:00 1970" + "Thu Jan 01 00:00:00 UTC 1970" )) } @Test fun convertDateStringShouldReturnEmptyStringWhenError() { - val expectedDateString = "" - - assertEquals(expectedDateString, DateUtil.convertDateString( + assertEquals("", DateUtil.convertDateString( "EEE, MMM d", - "Thu Jn 01 07:00:00 GMT+07:00 1970" + "Invalid Date String" )) } } \ No newline at end of file diff --git a/app/src/test/java/com/digiventure/ventnote/data/google_drive/GoogleDriveServiceShould.kt b/app/src/test/java/com/digiventure/ventnote/data/google_drive/GoogleDriveServiceShould.kt index 3064951..2d2eb98 100644 --- a/app/src/test/java/com/digiventure/ventnote/data/google_drive/GoogleDriveServiceShould.kt +++ b/app/src/test/java/com/digiventure/ventnote/data/google_drive/GoogleDriveServiceShould.kt @@ -1,8 +1,10 @@ package com.digiventure.ventnote.data.google_drive +import android.app.Application import com.digiventure.utils.BaseUnitTest import com.digiventure.ventnote.data.persistence.NoteDAO import com.digiventure.ventnote.data.persistence.NoteModel +import com.digiventure.ventnote.feature.widget.WidgetRefresher import com.digiventure.ventnote.module.proxy.DatabaseProxy import com.google.api.services.drive.Drive import com.google.api.services.drive.model.File @@ -20,8 +22,10 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever class GoogleDriveServiceShould: BaseUnitTest() { + private val app: Application = mock() private val proxy: DatabaseProxy = mock() private val dao: NoteDAO = mock() + private val refresher: WidgetRefresher = mock() private val noteList: List = listOf() private val fileName: String = "backup.json" private val fileId: String = "1" @@ -31,7 +35,7 @@ class GoogleDriveServiceShould: BaseUnitTest() { @Before fun setup() { - service = GoogleDriveService(proxy) + service = GoogleDriveService(app, proxy, refresher) } @Test @@ -72,13 +76,14 @@ class GoogleDriveServiceShould: BaseUnitTest() { whenever(drive.files()).thenReturn(filesMock) whenever(filesMock.get(fileId)).thenReturn(getMock) whenever(getMock.executeMediaAsInputStream()).thenReturn(inputStream) - whenever(dao.upsertNotesWithTimestamp(any())).thenAnswer { } + whenever(dao.upsertNotes(any())).thenAnswer { } whenever(proxy.dao()).thenReturn(dao) val result = service.readFile(fileId, drive) assertTrue(result.isSuccess) - verify(proxy.dao(), times(1)).upsertNotesWithTimestamp(any()) + verify(proxy.dao(), times(1)).upsertNotes(any()) + verify(refresher, times(1)).refresh(app) } @Test @@ -103,7 +108,7 @@ class GoogleDriveServiceShould: BaseUnitTest() { whenever(drive.files()).thenReturn(filesMock) whenever(filesMock.get(fileId)).thenReturn(getMock) whenever(getMock.executeMediaAsInputStream()).thenReturn(inputStream) - whenever(dao.upsertNotesWithTimestamp(any())).thenAnswer { + whenever(dao.upsertNotes(any())).thenAnswer { throw exception } whenever(proxy.dao()).thenReturn(dao) diff --git a/app/src/test/java/com/digiventure/ventnote/data/persistence/NoteLocalServiceShould.kt b/app/src/test/java/com/digiventure/ventnote/data/persistence/NoteLocalServiceShould.kt index 8458d6f..c6fabf1 100644 --- a/app/src/test/java/com/digiventure/ventnote/data/persistence/NoteLocalServiceShould.kt +++ b/app/src/test/java/com/digiventure/ventnote/data/persistence/NoteLocalServiceShould.kt @@ -1,7 +1,9 @@ package com.digiventure.ventnote.data.persistence +import android.app.Application import com.digiventure.utils.BaseUnitTest import com.digiventure.ventnote.commons.Constants +import com.digiventure.ventnote.feature.widget.WidgetRefresher import com.digiventure.ventnote.module.proxy.DatabaseProxy import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow @@ -16,8 +18,10 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever class NoteLocalServiceShould: BaseUnitTest() { + private val app: Application = mock() private val proxy: DatabaseProxy = mock() private val dao: NoteDAO = mock() + private val refresher: WidgetRefresher = mock() private val noteList = mock>() private val note = mock() private val sortBy = Constants.CREATED_AT @@ -36,7 +40,7 @@ class NoteLocalServiceShould: BaseUnitTest() { @Before fun setup() { whenever(proxy.dao()).thenReturn(dao) - service = NoteLocalService(proxy) + service = NoteLocalService(app, proxy, refresher) } /** @@ -142,9 +146,11 @@ class NoteLocalServiceShould: BaseUnitTest() { * */ @Test fun deleteNoteListFromDAO() = runTest { + runBlocking { whenever(dao.deleteNotes(note)).thenReturn(1) } service.deleteNoteList(note).first() verify(dao, times(1)).deleteNotes(note) + verify(refresher, times(1)).refresh(app) } @Test @@ -173,9 +179,11 @@ class NoteLocalServiceShould: BaseUnitTest() { * */ @Test fun updateNoteFromDAO() = runTest { + runBlocking { whenever(dao.updateWithTimestamp(note)).thenReturn(1) } service.updateNoteList(note).first() verify(dao, times(1)).updateWithTimestamp(note) + verify(refresher, times(1)).refresh(app) } @Test @@ -204,9 +212,11 @@ class NoteLocalServiceShould: BaseUnitTest() { * */ @Test fun insertNoteFromDAO() = runTest { + runBlocking { whenever(dao.insertWithTimestamp(note)).thenReturn(1L) } service.insertNote(note).first() verify(dao, times(1)).insertWithTimestamp(note) + verify(refresher, times(1)).refresh(app) } @Test diff --git a/app/src/test/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactoryTest.kt b/app/src/test/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactoryTest.kt new file mode 100644 index 0000000..3396029 --- /dev/null +++ b/app/src/test/java/com/digiventure/ventnote/feature/widget/NoteWidgetFactoryTest.kt @@ -0,0 +1,61 @@ +package com.digiventure.ventnote.feature.widget + +import android.content.Context +import com.digiventure.utils.BaseUnitTest +import com.digiventure.ventnote.data.persistence.NoteDAO +import com.digiventure.ventnote.data.persistence.NoteModel +import com.digiventure.ventnote.module.proxy.DatabaseProxy +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.util.Date + +class NoteWidgetFactoryTest : BaseUnitTest() { + private val context: Context = mock() + private val proxy: DatabaseProxy = mock() + private val dao: NoteDAO = mock() + + private lateinit var factory: NoteWidgetFactory + + private val notes = listOf( + NoteModel(1, "Title 1", "Content 1", Date(), Date()), + NoteModel(2, "Title 2", "Content 2", Date(), Date()) + ) + + @Before + fun setup() { + whenever(proxy.dao()).thenReturn(dao) + factory = NoteWidgetFactory(context, proxy) + } + + @Test + fun getCountShouldReturnNoteListSize() { + whenever(dao.getSyncNotes()).thenReturn(notes) + + factory.onDataSetChanged() + + assertEquals(notes.size, factory.getCount()) + } + + @Test + fun getItemIdShouldReturnNoteId() { + whenever(dao.getSyncNotes()).thenReturn(notes) + + factory.onDataSetChanged() + + assertEquals(notes[0].id.toLong(), factory.getItemId(0)) + assertEquals(notes[1].id.toLong(), factory.getItemId(1)) + } + + @Test + fun hasStableIdsShouldReturnTrue() { + assertEquals(true, factory.hasStableIds()) + } + + @Test + fun getViewTypeCountShouldReturnOne() { + assertEquals(1, factory.getViewTypeCount()) + } +}