From 69bb74c15adca34fd1218a8ea1027ba6ee3f659c Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Mon, 23 Oct 2023 14:50:44 +0600 Subject: [PATCH 01/13] Remove unnecessary rules form `kt` file --- build.gradle.kts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b168c990..4e8cdc44 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,13 +59,6 @@ subprojects { targetExclude("bin/**/*.kt") ktlint("1.0.1") - .editorConfigOverride( - mapOf( - "ktlint_code_style" to "android_studio", - "max_line_length" to "off", - "ktlint_function_naming_ignore_when_annotated_with" to "Composable" - ) - ) licenseHeaderFile(rootProject.file("spotless/copyright.kt")) } From b3a3b069e25d6374898bec3b16490f7aeb305c9f Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Wed, 8 Nov 2023 01:22:31 +0600 Subject: [PATCH 02/13] Remove unnecessary permissions --- app/src/main/AndroidManifest.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 17fef16a..91ff8fbc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,8 +32,6 @@ - - From a8d51e77a3bfb8dcfe33e7f7a7453f6b720bf1a2 Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Thu, 9 Nov 2023 12:38:37 +0600 Subject: [PATCH 03/13] Add more example for select image screen --- README.md | 2 + .../SelectImageAndCropScreen.kt | 84 +++++++++++++++++-- buildSrc/src/main/kotlin/Libs.kt | 30 +++---- 3 files changed, 96 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 4ef27869..9b5dc39c 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ Feel free to request features or suggestions for improvements. - [ ] Update `CheckBox` code (see example from AOSP) - [ ] Month-Picker component - [ ] Upgrade OneSignal lib +- [ ] ‼️ Check map example issue: on click freeze +- [ ] New: File browser using MediaStore # Note diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/selectimageandcrop/SelectImageAndCropScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/selectimageandcrop/SelectImageAndCropScreen.kt index 86be3d45..425fd63c 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/selectimageandcrop/SelectImageAndCropScreen.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/selectimageandcrop/SelectImageAndCropScreen.kt @@ -29,6 +29,7 @@ package org.imaginativeworld.whynotcompose.ui.screens.tutorial.selectimageandcro import android.content.res.Configuration import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column @@ -66,6 +67,13 @@ import org.imaginativeworld.whynotcompose.common.compose.compositions.AppCompone import org.imaginativeworld.whynotcompose.common.compose.theme.AppTheme import org.imaginativeworld.whynotcompose.utils.CropImage +/** + * Resources: + * - https://developer.android.com/training/data-storage/shared/documents-files + * - https://developer.android.com/guide/topics/providers/document-provider#client + * - https://developer.android.com/training/data-storage/shared/photopicker + */ + @Composable fun SelectImageAndCropScreen( viewModel: SelectImageAndCropViewModel, @@ -88,7 +96,7 @@ fun SelectImageAndCropScreen( } } - val imageSelectorLauncher = + val imageSelectorLauncher1 = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> if (uri != null) { uCropLauncher.launch( @@ -104,13 +112,53 @@ fun SelectImageAndCropScreen( } } + val imageSelectorLauncher2 = + rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri != null) { + uCropLauncher.launch( + Pair( + first = uri, + second = Uri.fromFile( + File(context.cacheDir, "temp_image_file_${Date().time}") + ) + ) + ) + } else { + context.toast("No image selected!") + } + } + + val imageSelectorLauncher3 = + rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> + if (uri != null) { + uCropLauncher.launch( + Pair( + first = uri, + second = Uri.fromFile( + File(context.cacheDir, "temp_image_file_${Date().time}") + ) + ) + ) + } else { + context.toast("No image selected!") + } + } + // ---------------------------------------------------------------- SelectImageAndCropScreenSkeleton( goBack = goBack, imagePath = imageUri, - onChooseImageClicked = { - imageSelectorLauncher.launch("image/*") + onChooseImage1Clicked = { + imageSelectorLauncher1.launch("image/*") + }, + onChooseImage2Clicked = { + imageSelectorLauncher2.launch(arrayOf("image/*")) + }, + onChooseImage3Clicked = { + imageSelectorLauncher3.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) } ) } @@ -135,7 +183,9 @@ fun SelectImageAndCropScreenSkeletonPreviewDark() { fun SelectImageAndCropScreenSkeleton( goBack: () -> Unit = {}, imagePath: Uri? = null, - onChooseImageClicked: () -> Unit = {} + onChooseImage1Clicked: () -> Unit = {}, + onChooseImage2Clicked: () -> Unit = {}, + onChooseImage3Clicked: () -> Unit = {} ) { Scaffold( Modifier @@ -180,10 +230,32 @@ fun SelectImageAndCropScreenSkeleton( .align(Alignment.CenterHorizontally) .padding(top = 32.dp), onClick = { - onChooseImageClicked() + onChooseImage1Clicked() + } + ) { + Text("Choose Image (GetContent())") + } + + Button( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(top = 8.dp), + onClick = { + onChooseImage2Clicked() + } + ) { + Text("Choose Image (OpenDocument())") + } + + Button( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(top = 8.dp), + onClick = { + onChooseImage3Clicked() } ) { - Text("Choose Image") + Text("Choose Image (PickVisualMedia())") } // ---------------------------------------------------------------- diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index fa3ea235..c6a4ce3f 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -43,7 +43,7 @@ object Libs { } object Coil { - private const val version = "2.4.0" + private const val version = "2.5.0" const val compose = "io.coil-kt:coil-compose:$version" const val svg = "io.coil-kt:coil-svg:$version" @@ -75,7 +75,7 @@ object Libs { const val exoplayer = "com.google.android.exoplayer:exoplayer:2.19.1" object DevTools { - const val kspVersion = "1.9.10-1.0.13" + const val kspVersion = "1.9.20-1.0.14" const val ksp = "com.google.devtools.ksp" } @@ -83,7 +83,7 @@ object Libs { object Firebase { const val crashlyticsGradlePluginVersion = "2.9.9" - const val bom = "com.google.firebase:firebase-bom:32.4.0" + const val bom = "com.google.firebase:firebase-bom:32.5.0" const val analytics = "com.google.firebase:firebase-analytics-ktx" const val crashlyticsGradlePlugin = "com.google.firebase.crashlytics" @@ -103,12 +103,12 @@ object Libs { } object Maps { - private const val version = "4.0.0" + private const val version = "5.0.0" const val secretsGradlePluginVersion = "2.0.1" const val core = "com.google.maps.android:maps-ktx:$version" const val utils = "com.google.maps.android:maps-utils-ktx:$version" - const val compose = "com.google.maps.android:maps-compose:3.1.1" + const val compose = "com.google.maps.android:maps-compose:4.1.1" const val secretsGradlePlugin = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" } @@ -131,7 +131,7 @@ object Libs { } object Kotlin { - const val version = "1.9.10" + const val version = "1.9.20" const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib:$version" @@ -157,9 +157,12 @@ object Libs { object Compose { private const val bomVersion = "2023.10.01" - const val compilerVersion = "1.5.3" + const val compilerVersion = "1.5.4" private const val runtimeTracingVersion = "1.0.0-alpha04" + // TODO: Remove when library gets stable. + private const val material3Version = "1.2.0-alpha10" + const val bom = "androidx.compose:compose-bom:$bomVersion" const val compiler = "androidx.compose.compiler:compiler:$compilerVersion" @@ -172,9 +175,9 @@ object Libs { const val materialIconsExtended = "androidx.compose.material:material-icons-extended" - const val material3 = "androidx.compose.material3:material3:1.2.0-alpha10" + const val material3 = "androidx.compose.material3:material3:$material3Version" const val material3WindowSizeClass = - "androidx.compose.material3:material3-window-size-class:1.2.0-alpha10" + "androidx.compose.material3:material3-window-size-class:$material3Version" const val runtime = "androidx.compose.runtime:runtime" const val runtimeLivedata = "androidx.compose.runtime:runtime-livedata" @@ -188,7 +191,7 @@ object Libs { const val toolingPreview = "androidx.compose.ui:ui-tooling-preview" const val viewBinding = "androidx.compose.ui:ui-viewbinding" - const val animation = "androidx.compose.animation:animation:1.5.0-beta02" + const val animation = "androidx.compose.animation:animation" const val test = "androidx.compose.ui:ui-test" const val uiTest = "androidx.compose.ui:ui-test-junit4" @@ -229,11 +232,10 @@ object Libs { } object Paging { - private const val version = "3.1.1" - private const val composeVersion = "3.2.1" + private const val version = "3.2.1" - const val runtime = "androidx.paging:paging-runtime-ktx:$version" - const val compose = "androidx.paging:paging-compose:$composeVersion" + const val runtime = "androidx.paging:paging-runtime:$version" + const val compose = "androidx.paging:paging-compose:$version" } object Room { From b9a8a53756ebee31f4182afcbad57372f3fb4c34 Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Wed, 29 Nov 2023 23:38:16 +0600 Subject: [PATCH 04/13] Fix map issue --- README.md | 3 +- .../repositories/MapPlaceRepo.kt | 2 +- .../whynotcompose/ui/screens/NavGraphMain.kt | 11 ++++- .../DataFetchAndPagingViewModel.kt | 3 -- .../ui/screens/ui/mapview/MapViewScreen.kt | 8 +++- .../whynotcompose/base/extensions/ExtMoshi.kt | 43 +++++++++++++++++-- build.gradle.kts | 2 +- buildSrc/src/main/kotlin/Libs.kt | 18 ++++---- 8 files changed, 70 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 9b5dc39c..f4c5ce41 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,9 @@ Feel free to request features or suggestions for improvements. - [ ] Update `CheckBox` code (see example from AOSP) - [ ] Month-Picker component - [ ] Upgrade OneSignal lib -- [ ] ‼️ Check map example issue: on click freeze +- [x] Check map example issue: on click freeze - [ ] New: File browser using MediaStore +- [ ] Get sidebar from Emudi app (https://www.youtube.com/watch?v=HNSKJIQtb4c) # Note diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/repositories/MapPlaceRepo.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/repositories/MapPlaceRepo.kt index daf35709..9c7f1336 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/repositories/MapPlaceRepo.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/repositories/MapPlaceRepo.kt @@ -45,7 +45,7 @@ object MapPlaceRepo { MapPlace( name = "Dhaka Division", location = LatLng(23.7807777, 90.3492858), - description = "Igitur ne dolorem quidem. Nam illud vehementer repugnat, eundem beatum esse et multis malis oppressum. Piso, familiaris noster, et alia multa et hoc loco Stoicos irridebat: Quid enim? Nos quidem Virtutes sic natae sumus, ut tibi serviremus, aliud negotii nihil habemus. An hoc usque quaque, aliter in vita? Sed finge non solum callidum eum, qui aliquid improbe faciat, verum etiam praepotentem, ut M. Itaque nostrum est-quod nostrum dico, artis est-ad ea principia, quae accepimus. Qui igitur convenit ab alia voluptate dicere naturam proficisci, in alia summum bonum ponere?" + description = "Igitur ne dolorem + qui+dem. N&am il=lud vehementer repugnat, eundem beatum esse et multis malis oppressum. Piso, familiaris noster, et alia multa et hoc loco Stoicos irridebat: Quid enim? Nos quidem Virtutes sic natae sumus, ut tibi serviremus, aliud negotii nihil habemus. An hoc usque quaque, aliter in vita? Sed finge non solum callidum eum, qui aliquid improbe faciat, verum etiam praepotentem, ut M. Itaque nostrum est-quod nostrum dico, artis est-ad ea principia, quae accepimus. Qui igitur convenit ab alia voluptate dicere naturam proficisci, in alia summum bonum ponere?" ), MapPlace( name = "Khulna Division", diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt index aeed1f6c..83edfeb5 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt @@ -35,8 +35,10 @@ import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController +import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.navArgument import androidx.navigation.navigation import org.imaginativeworld.whynotcompose.base.extensions.getJsonFromObj import org.imaginativeworld.whynotcompose.base.extensions.getObjFromJson @@ -607,7 +609,14 @@ private fun NavGraphBuilder.addUiScreens( ) } - composable(UIsScreen.UiMapViewDetails.route) { backStackEntry -> + composable( + UIsScreen.UiMapViewDetails.route, + arguments = listOf( + navArgument(UIsScreen.UiMapViewDetails.PARAM_ITEM) { + type = NavType.StringType + } + ) + ) { backStackEntry -> backStackEntry.arguments?.let { args -> val item = args.getString(UIsScreen.UiMapViewDetails.PARAM_ITEM) .getObjFromJson() ?: throw Exception("Item cannot be null!") diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/datafetchandpaging/DataFetchAndPagingViewModel.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/datafetchandpaging/DataFetchAndPagingViewModel.kt index c4d8dc0c..f6065c74 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/datafetchandpaging/DataFetchAndPagingViewModel.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/datafetchandpaging/DataFetchAndPagingViewModel.kt @@ -58,11 +58,8 @@ import timber.log.Timber class DataFetchAndPagingViewModel @Inject constructor( private val repository: AppRepository ) : ViewModel() { - private val eventShowLoading = MutableStateFlow(false) - private val eventShowMessage = MutableStateFlow?>(null) - private var items = MutableStateFlow>>(emptyFlow()) // ---------------------------------------------------------------- diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/ui/mapview/MapViewScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/ui/mapview/MapViewScreen.kt index e870357f..2cb7e4a3 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/ui/mapview/MapViewScreen.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/ui/mapview/MapViewScreen.kt @@ -64,6 +64,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -86,6 +87,7 @@ import com.google.maps.android.compose.MapUiSettings import com.google.maps.android.compose.MarkerInfoWindow import com.google.maps.android.compose.rememberCameraPositionState import com.google.maps.android.compose.rememberMarkerState +import kotlinx.coroutines.launch import org.imaginativeworld.whynotcompose.base.extensions.dpToPx import org.imaginativeworld.whynotcompose.common.compose.R as CommonComposeR import org.imaginativeworld.whynotcompose.common.compose.composeutils.bitmapDescriptorFromVector @@ -112,6 +114,8 @@ fun MapScreen( val state by viewModel.state.collectAsState() + val scope = rememberCoroutineScope() + LaunchedEffect(Unit) { viewModel.loadMapPlaces() } @@ -188,7 +192,9 @@ fun MapScreen( false }, onInfoWindowClick = { - gotoDetailsScreen(place) + scope.launch { + gotoDetailsScreen(place) + } }, onInfoWindowClose = { viewModel.clearSelectedMapPlace() diff --git a/base/src/main/java/org/imaginativeworld/whynotcompose/base/extensions/ExtMoshi.kt b/base/src/main/java/org/imaginativeworld/whynotcompose/base/extensions/ExtMoshi.kt index 5e808379..88e15898 100644 --- a/base/src/main/java/org/imaginativeworld/whynotcompose/base/extensions/ExtMoshi.kt +++ b/base/src/main/java/org/imaginativeworld/whynotcompose/base/extensions/ExtMoshi.kt @@ -27,6 +27,7 @@ package org.imaginativeworld.whynotcompose.base.extensions import com.squareup.moshi.Moshi +import java.net.URLDecoder import java.net.URLEncoder import java.util.Date import org.imaginativeworld.whynotcompose.base.network.jsonadapter.DateJsonAdapter @@ -40,16 +41,40 @@ object MoshiUtil { } } -inline fun String?.getObjFromJson(): T? { +/** + * Note for Navigation Component for Compose: + * If the data has special character (eg., + etc.), the url encode-decode will not work properly. + * For example, the "+" will be replaced with a space on decode. + * Because Navigation Component auto convert "%2B" with "+", + * so on decode it replace "+" by space. + */ +inline fun String?.getObjFromJson(urlDecode: Boolean = true): T? { if (this == null) return null Timber.e("getObjFromJson: $this") val jsonAdapter = MoshiUtil.getMoshi().adapter(T::class.java).lenient() - return jsonAdapter.fromJson(this) + val result = jsonAdapter.fromJson( + if (urlDecode) { + this.urlDecode() + } else { + this + } + ) + + Timber.e("getObjFromJson (after processing): $result") + + return result } +/** + * Note for Navigation Component for Compose: + * If the data has special character (eg., + etc.), the url encode-decode will not work properly. + * For example, the "+" will be replaced with a space on decode. + * Because Navigation Component auto convert "%2B" with "+", + * so on decode it replace "+" by space. + */ inline fun T?.getJsonFromObj(urlEncode: Boolean = true): String? { if (this == null) return null @@ -58,10 +83,22 @@ inline fun T?.getJsonFromObj(urlEncode: Boolean = true): String? { val jsonAdapter = MoshiUtil.getMoshi().adapter(T::class.java).lenient() return jsonAdapter.toJson(this).let { json -> - if (urlEncode) json.urlEncode() else json + val result = if (urlEncode) { + json.urlEncode() + } else { + json + } + + Timber.e("getJsonFromObj (after processing): $result") + + result } } fun String.urlEncode(): String { return URLEncoder.encode(this, "utf-8") } + +fun String.urlDecode(): String { + return URLDecoder.decode(this, "utf-8") +} diff --git a/build.gradle.kts b/build.gradle.kts index 4e8cdc44..3924a9d3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,7 +58,7 @@ subprojects { targetExclude("$buildDir/**/*.kt") targetExclude("bin/**/*.kt") - ktlint("1.0.1") + ktlint() licenseHeaderFile(rootProject.file("spotless/copyright.kt")) } diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index c6a4ce3f..aa940fd5 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -56,7 +56,7 @@ object Libs { } object Gradle { - const val version = "8.1.2" + const val version = "8.1.4" } object Yalantis { @@ -65,7 +65,7 @@ object Libs { object Airbnb { object Lottie { - const val compose = "com.airbnb.android:lottie-compose:6.1.0" + const val compose = "com.airbnb.android:lottie-compose:6.2.0" } } @@ -83,7 +83,7 @@ object Libs { object Firebase { const val crashlyticsGradlePluginVersion = "2.9.9" - const val bom = "com.google.firebase:firebase-bom:32.5.0" + const val bom = "com.google.firebase:firebase-bom:32.6.0" const val analytics = "com.google.firebase:firebase-analytics-ktx" const val crashlyticsGradlePlugin = "com.google.firebase.crashlytics" @@ -108,7 +108,7 @@ object Libs { const val core = "com.google.maps.android:maps-ktx:$version" const val utils = "com.google.maps.android:maps-utils-ktx:$version" - const val compose = "com.google.maps.android:maps-compose:4.1.1" + const val compose = "com.google.maps.android:maps-compose:4.3.0" const val secretsGradlePlugin = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" } @@ -148,11 +148,11 @@ object Libs { object AndroidX { const val appcompat = "androidx.appcompat:appcompat:1.6.1" - const val coreKtx = "androidx.core:core-ktx:1.10.0" + const val coreKtx = "androidx.core:core-ktx:1.12.0" const val swipeRefreshLayout = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" object Activity { - const val activityCompose = "androidx.activity:activity-compose:1.8.0" + const val activityCompose = "androidx.activity:activity-compose:1.8.1" } object Compose { @@ -161,7 +161,7 @@ object Libs { private const val runtimeTracingVersion = "1.0.0-alpha04" // TODO: Remove when library gets stable. - private const val material3Version = "1.2.0-alpha10" + private const val material3Version = "1.2.0-alpha11" const val bom = "androidx.compose:compose-bom:$bomVersion" @@ -209,7 +209,7 @@ object Libs { } object Hilt { - const val navigationCompose = "androidx.hilt:hilt-navigation-compose:1.0.0" + const val navigationCompose = "androidx.hilt:hilt-navigation-compose:1.1.0" } object Lifecycle { @@ -224,7 +224,7 @@ object Libs { } object Navigation { - private const val version = "2.7.4" + private const val version = "2.7.5" const val fragment = "androidx.navigation:navigation-fragment-ktx:$version" const val uiKtx = "androidx.navigation:navigation-ui-ktx:$version" From b5b72d7e1c5d420ae734635d3f5915aeced88870 Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Thu, 30 Nov 2023 00:59:38 +0600 Subject: [PATCH 05/13] Add storage cache using shared preference --- .../cache/StorageCacheDataSource.kt | 52 +++++++++++++++++++ .../cms/datasource/cache/StorageCacheKey.kt | 6 +++ .../cms/repositories/UserRepository.kt | 25 ++++----- .../ui/screens/user/add/UserAddViewModel.kt | 2 +- 4 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheDataSource.kt create mode 100644 cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheKey.kt diff --git a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheDataSource.kt b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheDataSource.kt new file mode 100644 index 00000000..daf19722 --- /dev/null +++ b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheDataSource.kt @@ -0,0 +1,52 @@ +package org.imaginativeworld.whynotcompose.cms.datasource.cache + +import android.content.Context +import android.content.SharedPreferences +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton +import org.imaginativeworld.whynotcompose.base.extensions.getJsonFromObj +import org.imaginativeworld.whynotcompose.base.extensions.getObjFromJson + +@Singleton +class StorageCacheDataSource @Inject constructor( + @ApplicationContext context: Context +) { + private val context: Context = context.applicationContext + + @Volatile + private var sharedPref: SharedPreferences? = null + + @PublishedApi + internal fun getSharedPerf(): SharedPreferences { + return sharedPref ?: synchronized(this) { + context.getSharedPreferences( + "${context.packageName}.storage.cache", + Context.MODE_PRIVATE + ) + } + } + + inline fun get(forKey: StorageCacheKey): T? { + val data = getSharedPerf().getString(forKey.name, null) ?: return null + + return data.getObjFromJson(false) + } + + inline fun set(forKey: StorageCacheKey, value: T?) { + getSharedPerf() + .edit() + .apply { + if (value == null) { + remove(forKey.name) + } else { + putString(forKey.name, value.getJsonFromObj(false)) + } + apply() + } + } + + fun reset() { + getSharedPerf().edit().clear().apply() + } +} diff --git a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheKey.kt b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheKey.kt new file mode 100644 index 00000000..48fb489e --- /dev/null +++ b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheKey.kt @@ -0,0 +1,6 @@ +package org.imaginativeworld.whynotcompose.cms.datasource.cache + +sealed class StorageCacheKey(val name: String) { + data class UserList(val page: Long) : StorageCacheKey("user-list-$page") + data class UserDetails(val userId: Int) : StorageCacheKey("user-$userId") +} diff --git a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/repositories/UserRepository.kt b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/repositories/UserRepository.kt index c3b8b8bc..d1df4e06 100644 --- a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/repositories/UserRepository.kt +++ b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/repositories/UserRepository.kt @@ -32,6 +32,8 @@ import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.imaginativeworld.whynotcompose.base.network.SafeApiRequest +import org.imaginativeworld.whynotcompose.cms.datasource.cache.StorageCacheDataSource +import org.imaginativeworld.whynotcompose.cms.datasource.cache.StorageCacheKey import org.imaginativeworld.whynotcompose.cms.db.CMSDatabase import org.imaginativeworld.whynotcompose.cms.models.user.User import org.imaginativeworld.whynotcompose.cms.models.user.UserEntity @@ -40,18 +42,21 @@ import org.imaginativeworld.whynotcompose.cms.network.api.UserApiInterface class UserRepository @Inject constructor( @ApplicationContext private val context: Context, private val api: UserApiInterface, - private val db: CMSDatabase + private val db: CMSDatabase, + private val cache: StorageCacheDataSource ) { suspend fun getUsers(page: Long) = withContext(Dispatchers.IO) { - SafeApiRequest.apiRequest(context) { - api.getUsers(page) - } + cache.get(StorageCacheKey.UserList(page = page)) + ?: SafeApiRequest.apiRequest(context) { + api.getUsers(page) + } } suspend fun getUser(userId: Int) = withContext(Dispatchers.IO) { - SafeApiRequest.apiRequest(context) { - api.getUser(userId) - } + cache.get(StorageCacheKey.UserDetails(userId = userId)) + ?: SafeApiRequest.apiRequest(context) { + api.getUser(userId) + } } suspend fun deleteUser(postId: Int) = withContext(Dispatchers.IO) { @@ -66,16 +71,12 @@ class UserRepository @Inject constructor( } } - suspend fun signIn(user: User) = withContext(Dispatchers.IO) { + suspend fun createUser(user: User) = withContext(Dispatchers.IO) { SafeApiRequest.apiRequest(context) { api.createUser(user) } } - suspend fun signOut() = withContext(Dispatchers.IO) { - db.todoDao().removeAll() - } - // ---------------------------------------------------------------- // Database // ---------------------------------------------------------------- diff --git a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/ui/screens/user/add/UserAddViewModel.kt b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/ui/screens/user/add/UserAddViewModel.kt index 893aa1b4..94a08148 100644 --- a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/ui/screens/user/add/UserAddViewModel.kt +++ b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/ui/screens/user/add/UserAddViewModel.kt @@ -133,7 +133,7 @@ class UserAddViewModel @Inject constructor( status ) - val newUser = repository.signIn( + val newUser = repository.createUser( user ) From 61b56286f0a4d170c8f99e490cf4036887a5afc6 Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Thu, 30 Nov 2023 12:36:17 +0600 Subject: [PATCH 06/13] Refactor `object` with `data object` --- .../whynotcompose/ui/screens/NavGraphMain.kt | 148 +++++++++--------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt index 83edfeb5..1431fe6f 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt @@ -114,101 +114,101 @@ import org.imaginativeworld.whynotcompose.ui.screens.ui.webview.WebViewTarget import org.imaginativeworld.whynotcompose.ui.screens.ui.webview.WebViewViewModel sealed class Screen(val route: String) { - object Home : Screen("home") - object Animations : Screen("animation") - object Compositions : Screen("composition") - object UIs : Screen("ui") - object Tutorials : Screen("tutorial") + data object Home : Screen("home") + data object Animations : Screen("animation") + data object Compositions : Screen("composition") + data object UIs : Screen("ui") + data object Tutorials : Screen("tutorial") } sealed class HomeScreen(val route: String) { - object Splash : HomeScreen("splash") - object HomeIndex : HomeScreen("home/index") + data object Splash : HomeScreen("splash") + data object HomeIndex : HomeScreen("home/index") } sealed class AnimationsScreen(val route: String) { - object AnimationIndex : AnimationsScreen("animation/index") - object AnimationComposeOne : AnimationsScreen("animation/composeone") - object AnimationEmudi : AnimationsScreen("animation/emudi") - object AnimationRunningCar : AnimationsScreen("animation/runningcar") - object AnimationTheStory : AnimationsScreen("animation/thestory") + data object AnimationIndex : AnimationsScreen("animation/index") + data object AnimationComposeOne : AnimationsScreen("animation/composeone") + data object AnimationEmudi : AnimationsScreen("animation/emudi") + data object AnimationRunningCar : AnimationsScreen("animation/runningcar") + data object AnimationTheStory : AnimationsScreen("animation/thestory") } sealed class CompositionsScreen(val route: String) { - object CompositionIndex : CompositionsScreen("composition/index") - - object CompositionAppBar : CompositionsScreen("composition/appbar") - object CompositionButton : CompositionsScreen("composition/button") - object CompositionCard : CompositionsScreen("composition/card") - object CompositionCheckBox : CompositionsScreen("composition/checkbox") - object CompositionDialog : CompositionsScreen("composition/dialog") - object CompositionDropDownMenu : CompositionsScreen("composition/dropdownmenu") - - object CompositionListIndex : CompositionsScreen("composition/list") - object CompositionListColumn : CompositionsScreen("composition/list/column") - object CompositionListRow : CompositionsScreen("composition/list/row") - - object CompositionListLazyColumnIndex : CompositionsScreen("composition/list/lazycolumn") - object CompositionListLazyColumnOne : CompositionsScreen("composition/list/lazycolumn/1") - object CompositionListLazyColumnTwo : CompositionsScreen("composition/list/lazycolumn/2") - - object CompositionListLazyRow : CompositionsScreen("composition/list/lazyrow") - object CompositionListGridVertical : CompositionsScreen("composition/list/grid/vertical") - - object CompositionListItem : CompositionsScreen("composition/listitem") - - object CompositionLoadingIndicator : CompositionsScreen("composition/loadingindicator") - object CompositionRadioButton : CompositionsScreen("composition/radiobutton") - - object CompositionScaffoldIndex : CompositionsScreen("composition/scaffold") - object CompositionScaffoldOne : CompositionsScreen("composition/scaffold/1") - object CompositionScaffoldTwo : CompositionsScreen("composition/scaffold/2") - object CompositionScaffoldThree : CompositionsScreen("composition/scaffold/3") - object CompositionScaffoldFour : CompositionsScreen("composition/scaffold/4") - object CompositionScaffoldFive : CompositionsScreen("composition/scaffold/5") - - object CompositionSnackbar : CompositionsScreen("composition/snackbar") - object CompositionSwitch : CompositionsScreen("composition/switch") - object CompositionTextField : CompositionsScreen("composition/textfield") - object CompositionSwipeToDismiss : CompositionsScreen("composition/swipetodismiss") - object CompositionSwipeRefresh : CompositionsScreen("composition/swiperefresh") - object CompositionBadge : CompositionsScreen("composition/badge") - object CompositionFloatingActionButton : CompositionsScreen("composition/fab") - object CompositionSlider : CompositionsScreen("composition/slider") - object CompositionText : CompositionsScreen("composition/text") - object CompositionBottomNavigation : CompositionsScreen("composition/bottomnavigation") + data object CompositionIndex : CompositionsScreen("composition/index") + + data object CompositionAppBar : CompositionsScreen("composition/appbar") + data object CompositionButton : CompositionsScreen("composition/button") + data object CompositionCard : CompositionsScreen("composition/card") + data object CompositionCheckBox : CompositionsScreen("composition/checkbox") + data object CompositionDialog : CompositionsScreen("composition/dialog") + data object CompositionDropDownMenu : CompositionsScreen("composition/dropdownmenu") + + data object CompositionListIndex : CompositionsScreen("composition/list") + data object CompositionListColumn : CompositionsScreen("composition/list/column") + data object CompositionListRow : CompositionsScreen("composition/list/row") + + data object CompositionListLazyColumnIndex : CompositionsScreen("composition/list/lazycolumn") + data object CompositionListLazyColumnOne : CompositionsScreen("composition/list/lazycolumn/1") + data object CompositionListLazyColumnTwo : CompositionsScreen("composition/list/lazycolumn/2") + + data object CompositionListLazyRow : CompositionsScreen("composition/list/lazyrow") + data object CompositionListGridVertical : CompositionsScreen("composition/list/grid/vertical") + + data object CompositionListItem : CompositionsScreen("composition/listitem") + + data object CompositionLoadingIndicator : CompositionsScreen("composition/loadingindicator") + data object CompositionRadioButton : CompositionsScreen("composition/radiobutton") + + data object CompositionScaffoldIndex : CompositionsScreen("composition/scaffold") + data object CompositionScaffoldOne : CompositionsScreen("composition/scaffold/1") + data object CompositionScaffoldTwo : CompositionsScreen("composition/scaffold/2") + data object CompositionScaffoldThree : CompositionsScreen("composition/scaffold/3") + data object CompositionScaffoldFour : CompositionsScreen("composition/scaffold/4") + data object CompositionScaffoldFive : CompositionsScreen("composition/scaffold/5") + + data object CompositionSnackbar : CompositionsScreen("composition/snackbar") + data object CompositionSwitch : CompositionsScreen("composition/switch") + data object CompositionTextField : CompositionsScreen("composition/textfield") + data object CompositionSwipeToDismiss : CompositionsScreen("composition/swipetodismiss") + data object CompositionSwipeRefresh : CompositionsScreen("composition/swiperefresh") + data object CompositionBadge : CompositionsScreen("composition/badge") + data object CompositionFloatingActionButton : CompositionsScreen("composition/fab") + data object CompositionSlider : CompositionsScreen("composition/slider") + data object CompositionText : CompositionsScreen("composition/text") + data object CompositionBottomNavigation : CompositionsScreen("composition/bottomnavigation") } sealed class UIsScreen(val route: String) { - object UiIndex : UIsScreen("ui/index") + data object UiIndex : UIsScreen("ui/index") - object UiWebView : UIsScreen("ui/webview") - object UiMapView : UIsScreen("ui/mapview") - object UiMapViewDetails : UIsScreen("ui/mapview/details?item={item}") { + data object UiWebView : UIsScreen("ui/webview") + data object UiMapView : UIsScreen("ui/mapview") + data object UiMapViewDetails : UIsScreen("ui/mapview/details?item={item}") { const val PARAM_ITEM = "item" fun createRoute(item: MapPlace) = route.replace("{$PARAM_ITEM}", item.getJsonFromObj() ?: "") } - object UiOtpCodeVerify : UIsScreen("ui/otpcodeverify") + data object UiOtpCodeVerify : UIsScreen("ui/otpcodeverify") } sealed class TutorialsScreen(val route: String) { - object TutorialIndex : TutorialsScreen("tutorial/index") - - object TutorialCounter : TutorialsScreen("tutorial/counter") - object TutorialCounterWithViewModel : TutorialsScreen("tutorial/counter-with-view-model") - object TutorialAnimatedVisibility : TutorialsScreen("tutorial/animated-visibility") - object TutorialLottie : TutorialsScreen("tutorial/lottie") - object TutorialSelectImageAndCrop : TutorialsScreen("tutorial/select-image-and-crop") - object TutorialCaptureImageAndCrop : TutorialsScreen("tutorial/capture-image-and-crop") - object TutorialPermission : TutorialsScreen("tutorial/permission") - object TutorialDataFetchAndPaging : TutorialsScreen("tutorial/data-fetch-and-paging") - object TutorialTicTacToe : TutorialsScreen("tutorial/tic-tac-toe") - object TutorialOneSignalAndBroadcast : TutorialsScreen("tutorial/onesignal-and-broadcast") - object TutorialExoPlayer : TutorialsScreen("tutorial/exoplayer") - object TutorialCMS : TutorialsScreen("tutorial/cms") - object TutorialDeepLink : TutorialsScreen("tutorial/deep-link") + data object TutorialIndex : TutorialsScreen("tutorial/index") + + data object TutorialCounter : TutorialsScreen("tutorial/counter") + data object TutorialCounterWithViewModel : TutorialsScreen("tutorial/counter-with-view-model") + data object TutorialAnimatedVisibility : TutorialsScreen("tutorial/animated-visibility") + data object TutorialLottie : TutorialsScreen("tutorial/lottie") + data object TutorialSelectImageAndCrop : TutorialsScreen("tutorial/select-image-and-crop") + data object TutorialCaptureImageAndCrop : TutorialsScreen("tutorial/capture-image-and-crop") + data object TutorialPermission : TutorialsScreen("tutorial/permission") + data object TutorialDataFetchAndPaging : TutorialsScreen("tutorial/data-fetch-and-paging") + data object TutorialTicTacToe : TutorialsScreen("tutorial/tic-tac-toe") + data object TutorialOneSignalAndBroadcast : TutorialsScreen("tutorial/onesignal-and-broadcast") + data object TutorialExoPlayer : TutorialsScreen("tutorial/exoplayer") + data object TutorialCMS : TutorialsScreen("tutorial/cms") + data object TutorialDeepLink : TutorialsScreen("tutorial/deep-link") } // ================================================================ From cbbb3ad9b22b9c39b2ecc36c071c0e52368243d2 Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Thu, 30 Nov 2023 22:34:21 +0600 Subject: [PATCH 07/13] Add navigation data pass example --- .../whynotcompose/models/DemoData.kt | 15 ++ .../whynotcompose/ui/screens/NavGraphMain.kt | 93 ++++++++++ .../ui/screens/tutorial/index/TutorialList.kt | 12 +- .../navdatapass/NavDataPassHomeScreen.kt | 166 ++++++++++++++++++ .../navdatapass/NavDataPassOneScreen.kt | 153 ++++++++++++++++ .../navdatapass/NavDataPassTwoScreen.kt | 118 +++++++++++++ .../cms/ui/screens/CMSNavGraph.kt | 36 ++-- 7 files changed, 572 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/imaginativeworld/whynotcompose/models/DemoData.kt create mode 100644 app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt create mode 100644 app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt create mode 100644 app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassTwoScreen.kt diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/models/DemoData.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/models/DemoData.kt new file mode 100644 index 00000000..af32c610 --- /dev/null +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/models/DemoData.kt @@ -0,0 +1,15 @@ +package org.imaginativeworld.whynotcompose.models + +import android.os.Parcelable +import androidx.annotation.Keep +import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize + +@Keep +@JsonClass(generateAdapter = true) +@Parcelize +data class DemoData( + val id: Int, + val name: String, + val ranks: List +) : Parcelable diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt index 1431fe6f..9c7d9be8 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt @@ -44,6 +44,7 @@ import org.imaginativeworld.whynotcompose.base.extensions.getJsonFromObj import org.imaginativeworld.whynotcompose.base.extensions.getObjFromJson import org.imaginativeworld.whynotcompose.cms.ui.screens.CMSMainScreen import org.imaginativeworld.whynotcompose.exoplayer.ExoPlayerScreen +import org.imaginativeworld.whynotcompose.models.DemoData import org.imaginativeworld.whynotcompose.models.MapPlace import org.imaginativeworld.whynotcompose.tictactoe.TicTacToeScreen import org.imaginativeworld.whynotcompose.tictactoe.TicTacToeViewModel @@ -99,6 +100,9 @@ import org.imaginativeworld.whynotcompose.ui.screens.tutorial.datafetchandpaging import org.imaginativeworld.whynotcompose.ui.screens.tutorial.deeplinks.DeepLinksScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.index.TutorialIndexScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.lottie.LottieScreen +import org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass.NavDataPassHomeScreen +import org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass.NavDataPassOneScreen +import org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass.NavDataPassTwoScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.onesignalandbroadcast.OneSignalAndBroadcastScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.permission.PermissionScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.selectimageandcrop.SelectImageAndCropScreen @@ -209,6 +213,21 @@ sealed class TutorialsScreen(val route: String) { data object TutorialExoPlayer : TutorialsScreen("tutorial/exoplayer") data object TutorialCMS : TutorialsScreen("tutorial/cms") data object TutorialDeepLink : TutorialsScreen("tutorial/deep-link") + + data object TutorialNavDataPassHome : TutorialsScreen("tutorial/nav-data-pass/home") { + const val KEY_RECEIVED_DATA = "received_data" + } + + data object TutorialNavDataPassScreen1 : + TutorialsScreen("tutorial/nav-data-pass/one/{data}") { + const val PARAM_DATA = "data" + fun createRoute(item: DemoData) = + route.replace("{$PARAM_DATA}", item.getJsonFromObj() ?: "") + } + + data object TutorialNavDataPassScreen2 : TutorialsScreen("tutorial/nav-data-pass/two") { + const val PARAM_DATA = "data" + } } // ================================================================ @@ -886,6 +905,80 @@ private fun NavGraphBuilder.addTutorialIndexScreen( } ) } + + // ================================================================ + + composable(TutorialsScreen.TutorialNavDataPassHome.route) { backStateEntry -> + val receivedData = backStateEntry.savedStateHandle.get( + TutorialsScreen.TutorialNavDataPassHome.KEY_RECEIVED_DATA + ) + + NavDataPassHomeScreen( + receivedData = receivedData, + goBack = { + navController.popBackStack() + }, + gotoScreenOne = { item -> + navController.navigate(TutorialsScreen.TutorialNavDataPassScreen1.createRoute(item)) + }, + gotoScreenTwo = { item -> + // Equivalent: navController.currentBackStackEntry?.savedStateHandle + backStateEntry.savedStateHandle.apply { + set( + TutorialsScreen.TutorialNavDataPassScreen2.PARAM_DATA, + item + ) + } + navController.navigate(TutorialsScreen.TutorialNavDataPassScreen2.route) + } + ) + } + + composable( + TutorialsScreen.TutorialNavDataPassScreen1.route, + arguments = listOf( + navArgument(TutorialsScreen.TutorialNavDataPassScreen1.PARAM_DATA) { + type = NavType.StringType + } + ) + ) { backStackEntry -> + backStackEntry.arguments?.let { args -> + val data = args.getString(TutorialsScreen.TutorialNavDataPassScreen1.PARAM_DATA) + .getObjFromJson() ?: throw Exception("Data cannot be null!") + + NavDataPassOneScreen( + data = data, + goBack = { + navController.popBackStack() + }, + backWithData = { data -> + navController.previousBackStackEntry + ?.savedStateHandle + ?.set( + TutorialsScreen.TutorialNavDataPassHome.KEY_RECEIVED_DATA, + data + ) + + navController.popBackStack() + } + ) + } + } + + composable( + TutorialsScreen.TutorialNavDataPassScreen2.route + ) { + val data: DemoData? = navController.previousBackStackEntry?.savedStateHandle?.get( + TutorialsScreen.TutorialNavDataPassScreen2.PARAM_DATA + ) + + NavDataPassTwoScreen( + data = data, + goBack = { + navController.popBackStack() + } + ) + } } // ================================================================ diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/index/TutorialList.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/index/TutorialList.kt index d4323aa2..c692a15e 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/index/TutorialList.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/index/TutorialList.kt @@ -34,17 +34,17 @@ sealed class TutorialLevel( val name: String, val color: Color ) { - object Beginner : TutorialLevel( + data object Beginner : TutorialLevel( name = "Beginner", color = TailwindCSSColor.Green500 ) - object Intermediate : TutorialLevel( + data object Intermediate : TutorialLevel( name = "Intermediate", color = TailwindCSSColor.Yellow500 ) - object Advanced : TutorialLevel( + data object Advanced : TutorialLevel( name = "Advanced", color = TailwindCSSColor.Red500 ) @@ -135,6 +135,12 @@ data class Tutorial( description = "Example of Deep Link.", route = TutorialsScreen.TutorialDeepLink, level = TutorialLevel.Intermediate + ), + Tutorial( + name = "Navigation Data Pass", + description = "Example of data passing in `Navigation Component`.", + route = TutorialsScreen.TutorialNavDataPassHome, + level = TutorialLevel.Intermediate ) ) } diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt new file mode 100644 index 00000000..2ce8d58f --- /dev/null +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt @@ -0,0 +1,166 @@ +package org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.Divider +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.imaginativeworld.whynotcompose.common.compose.compositions.AppComponent +import org.imaginativeworld.whynotcompose.common.compose.theme.AppTheme +import org.imaginativeworld.whynotcompose.models.DemoData + +/** + * Caution: Passing complex data structures over arguments is considered an anti-pattern. + * Each destination should be responsible for loading UI data based on the minimum necessary + * information, such as item IDs. This simplifies process recreation and avoids potential + * data inconsistencies. + * + * Source: https://developer.android.com/guide/navigation/use-graph/pass-data#supported_argument_types + * + * For more info see: https://stackoverflow.com/a/67133534/2263329 + */ + +@Composable +fun NavDataPassHomeScreen( + receivedData: DemoData?, + goBack: () -> Unit, + gotoScreenOne: (DemoData) -> Unit, + gotoScreenTwo: (DemoData) -> Unit +) { + NavDataPassHomeScreenSkeleton( + receivedData = receivedData, + goBack = goBack, + gotoScreenOne = { + gotoScreenOne( + DemoData( + id = 1, + name = "John Doe", + ranks = listOf("A", "B", "C") + ) + ) + }, + gotoScreenTwo = { + gotoScreenTwo( + DemoData( + id = 1, + name = "John Doe", + ranks = listOf("A", "B", "C") + ) + ) + } + ) +} + +@Preview +@Composable +fun NavDataPassHomeScreenSkeletonPreview() { + AppTheme { + NavDataPassHomeScreenSkeleton( + receivedData = DemoData( + id = 1, + name = "John Doe", + ranks = listOf("A", "B", "C") + ) + ) + } +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun NavDataPassHomeScreenSkeletonPreviewDark() { + AppTheme { + NavDataPassHomeScreenSkeleton( + receivedData = DemoData( + id = 1, + name = "John Doe", + ranks = listOf("A", "B", "C") + ) + ) + } +} + +@Composable +fun NavDataPassHomeScreenSkeleton( + receivedData: DemoData?, + goBack: () -> Unit = {}, + gotoScreenOne: () -> Unit = {}, + gotoScreenTwo: () -> Unit = {} +) { + Scaffold( + Modifier + .navigationBarsPadding() + .imePadding() + .statusBarsPadding() + ) { innerPadding -> + Column( + Modifier + .padding(innerPadding) + .fillMaxSize() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + AppComponent.Header( + "Navigation Data Pass", + goBack = goBack + ) + + // ---------------------------------------------------------------- + // ---------------------------------------------------------------- + + Divider() + + AppComponent.BigSpacer() + + Text( + "Received Data", + fontWeight = FontWeight.Bold + ) + + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = "$receivedData", + textAlign = TextAlign.Center + ) + + AppComponent.BigSpacer() + + // ---------------------------------------------------------------- + + Divider() + + AppComponent.BigSpacer() + + Button(onClick = { + gotoScreenOne() + }) { + Text("Pass data as String") + } + + Button(onClick = { + gotoScreenTwo() + }) { + Text("Pass data as Parcelable") + } + + // ---------------------------------------------------------------- + // ---------------------------------------------------------------- + + AppComponent.BigSpacer() + } + } +} diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt new file mode 100644 index 00000000..67a639a1 --- /dev/null +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt @@ -0,0 +1,153 @@ +package org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.Divider +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.imaginativeworld.whynotcompose.common.compose.compositions.AppComponent +import org.imaginativeworld.whynotcompose.common.compose.theme.AppTheme +import org.imaginativeworld.whynotcompose.models.DemoData + +@Composable +fun NavDataPassOneScreen( + data: DemoData, + goBack: () -> Unit, + backWithData: (DemoData) -> Unit +) { + NavDataPassOneScreenSkeleton( + goBack = goBack, + data = data, + backWithData = { data -> + backWithData(data) + } + ) +} + +@Preview +@Composable +fun NavDataPassOneScreenSkeletonPreview() { + AppTheme { + NavDataPassOneScreenSkeleton( + data = DemoData( + id = 1, + name = "John Doe", + ranks = listOf("A", "B", "C") + ) + ) + } +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun NavDataPassOneScreenSkeletonPreviewDark() { + AppTheme { + NavDataPassOneScreenSkeleton( + data = DemoData( + id = 1, + name = "John Doe", + ranks = listOf("A", "B", "C") + ) + ) + } +} + +@Composable +fun NavDataPassOneScreenSkeleton( + data: DemoData, + goBack: () -> Unit = {}, + backWithData: (DemoData) -> Unit = {} +) { + var text by remember { mutableStateOf("Mahmudul Hasan") } + + Scaffold( + Modifier + .navigationBarsPadding() + .imePadding() + .statusBarsPadding() + ) { innerPadding -> + Column( + Modifier + .padding(innerPadding) + .fillMaxSize() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + AppComponent.Header( + "Data passed as String", + goBack = goBack + ) + + // ---------------------------------------------------------------- + // ---------------------------------------------------------------- + + Divider() + + AppComponent.BigSpacer() + + Text( + "Received Data", + fontWeight = FontWeight.Bold + ) + + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = "$data", + textAlign = TextAlign.Center + ) + + AppComponent.BigSpacer() + + // ---------------------------------------------------------------- + + Divider() + + AppComponent.BigSpacer() + + OutlinedTextField( + value = text, + onValueChange = { text = it }, + label = { + Text("Return Name") + } + ) + + Button(onClick = { + backWithData( + DemoData( + id = 9, + name = text, + ranks = listOf("A+", "B+", "C+") + ) + ) + }) { + Text("Return data") + } + + // ---------------------------------------------------------------- + // ---------------------------------------------------------------- + + AppComponent.BigSpacer() + } + } +} diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassTwoScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassTwoScreen.kt new file mode 100644 index 00000000..4bc85cf9 --- /dev/null +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassTwoScreen.kt @@ -0,0 +1,118 @@ +package org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.Divider +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.imaginativeworld.whynotcompose.common.compose.compositions.AppComponent +import org.imaginativeworld.whynotcompose.common.compose.theme.AppTheme +import org.imaginativeworld.whynotcompose.models.DemoData + +@Composable +fun NavDataPassTwoScreen( + data: DemoData?, + goBack: () -> Unit +) { + NavDataPassTwoScreenSkeleton( + data = data, + goBack = goBack + ) +} + +@Preview +@Composable +fun NavDataPassTwoScreenSkeletonPreview() { + AppTheme { + NavDataPassTwoScreenSkeleton( + data = DemoData( + id = 1, + name = "John Doe", + ranks = listOf("A", "B", "C") + ) + ) + } +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun NavDataPassTwoScreenSkeletonPreviewDark() { + AppTheme { + NavDataPassTwoScreenSkeleton( + data = DemoData( + id = 1, + name = "John Doe", + ranks = listOf("A", "B", "C") + ) + ) + } +} + +@Composable +fun NavDataPassTwoScreenSkeleton( + data: DemoData?, + goBack: () -> Unit = {} +) { + Scaffold( + Modifier + .navigationBarsPadding() + .imePadding() + .statusBarsPadding() + ) { innerPadding -> + Column( + Modifier + .padding(innerPadding) + .fillMaxSize() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + AppComponent.Header( + "Data passed as Parcelable", + goBack = goBack + ) + + // ---------------------------------------------------------------- + // ---------------------------------------------------------------- + + Divider() + + AppComponent.BigSpacer() + + Text( + "Received Data", + fontWeight = FontWeight.Bold + ) + + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = "$data", + textAlign = TextAlign.Center + ) + + // ---------------------------------------------------------------- + // ---------------------------------------------------------------- + + AppComponent.BigSpacer() + } + } +} diff --git a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/ui/screens/CMSNavGraph.kt b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/ui/screens/CMSNavGraph.kt index 8050d276..ab048219 100644 --- a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/ui/screens/CMSNavGraph.kt +++ b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/ui/screens/CMSNavGraph.kt @@ -58,54 +58,54 @@ import org.imaginativeworld.whynotcompose.cms.ui.screens.user.list.UserListScree import org.imaginativeworld.whynotcompose.cms.ui.screens.user.list.UserListViewModel sealed class Screen(val route: String) { - object Splash : Screen("splash") - object User : Screen("user") - object Todo : Screen("todo") - object Post : Screen("post") - object Comment : Screen("comment") + data object Splash : Screen("splash") + data object User : Screen("user") + data object Todo : Screen("todo") + data object Post : Screen("post") + data object Comment : Screen("comment") } sealed class SplashScreen(val route: String) { - object Splash : SplashScreen("splash/index") + data object Splash : SplashScreen("splash/index") } sealed class UserScreen(val route: String) { - object UserList : UserScreen("users") - object UserDetails : UserScreen("users/{userId}") { + data object UserList : UserScreen("users") + data object UserDetails : UserScreen("users/{userId}") { const val USER_ID = "userId" } } sealed class TodoScreen(val route: String) { - object TodoList : TodoScreen("users/{userId}/todos") { + data object TodoList : TodoScreen("users/{userId}/todos") { const val USER_ID = "userId" } - object TodoDetails : TodoScreen("users/{userId}/todos/{todoId}") { + data object TodoDetails : TodoScreen("users/{userId}/todos/{todoId}") { const val USER_ID = "userId" const val TODO_ID = "todoId" } } sealed class PostScreen(val route: String) { - object PostList : PostScreen("users/{userId}/posts") { + data object PostList : PostScreen("users/{userId}/posts") { const val USER_ID = "userId" } - object PostDetails : PostScreen("users/{userId}/posts/{postId}") { + data object PostDetails : PostScreen("users/{userId}/posts/{postId}") { const val USER_ID = "userId" const val POST_ID = "postId" } } sealed class CommentScreen(val route: String) { - object CommentList : CommentScreen("posts/{postId}/comments") { + data object CommentList : CommentScreen("posts/{postId}/comments") { const val POST_ID = "postId" } - object CommentDetails : CommentScreen("posts/{postId}/comments/{commentId}") { + data object CommentDetails : CommentScreen("posts/{postId}/comments/{commentId}") { const val POST_ID = "postId" - const val COMMNET_ID = "commentId" + const val COMMENT_ID = "commentId" } } @@ -436,7 +436,7 @@ private fun NavGraphBuilder.addCommentScreens( "$postId" ) .replaceFirst( - "{${CommentScreen.CommentDetails.COMMNET_ID}}", + "{${CommentScreen.CommentDetails.COMMENT_ID}}", "$commentId" ) ) @@ -448,13 +448,13 @@ private fun NavGraphBuilder.addCommentScreens( CommentScreen.CommentDetails.route, arguments = listOf( navArgument(CommentScreen.CommentDetails.POST_ID) { type = NavType.IntType }, - navArgument(CommentScreen.CommentDetails.COMMNET_ID) { type = NavType.IntType } + navArgument(CommentScreen.CommentDetails.COMMENT_ID) { type = NavType.IntType } ) ) { backStackEntry -> val viewModel: CommentDetailsViewModel = hiltViewModel() val isDarkMode by UIThemeController.isDarkMode.collectAsState() val postId = backStackEntry.arguments?.getInt(CommentScreen.CommentDetails.POST_ID) ?: 0 - val commentId = backStackEntry.arguments?.getInt(CommentScreen.CommentDetails.COMMNET_ID) ?: 0 + val commentId = backStackEntry.arguments?.getInt(CommentScreen.CommentDetails.COMMENT_ID) ?: 0 CommentDetailsScreen( viewModel = viewModel, From 68f64db93ed55dafde600be118d99cc0a231608b Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Thu, 30 Nov 2023 22:45:09 +0600 Subject: [PATCH 08/13] Apply spotless --- .../whynotcompose/models/DemoData.kt | 26 ++++++++++++++++ .../navdatapass/NavDataPassHomeScreen.kt | 26 ++++++++++++++++ .../navdatapass/NavDataPassOneScreen.kt | 26 ++++++++++++++++ .../navdatapass/NavDataPassTwoScreen.kt | 30 ++++++++++++++++--- buildSrc/src/main/kotlin/Libs.kt | 2 +- .../cache/StorageCacheDataSource.kt | 26 ++++++++++++++++ .../cms/datasource/cache/StorageCacheKey.kt | 26 ++++++++++++++++ 7 files changed, 157 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/models/DemoData.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/models/DemoData.kt index af32c610..25076d91 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/models/DemoData.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/models/DemoData.kt @@ -1,3 +1,29 @@ +/* + * Copyright 2023 Md. Mahmudul Hasan Shohag + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ------------------------------------------------------------------------ + * + * Project: Why Not Compose! + * Developed by: @ImaginativeShohag + * + * Md. Mahmudul Hasan Shohag + * imaginativeshohag@gmail.com + * + * Source: https://github.com/ImaginativeShohag/Why-Not-Compose + */ + package org.imaginativeworld.whynotcompose.models import android.os.Parcelable diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt index 2ce8d58f..258f7c86 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt @@ -1,3 +1,29 @@ +/* + * Copyright 2023 Md. Mahmudul Hasan Shohag + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ------------------------------------------------------------------------ + * + * Project: Why Not Compose! + * Developed by: @ImaginativeShohag + * + * Md. Mahmudul Hasan Shohag + * imaginativeshohag@gmail.com + * + * Source: https://github.com/ImaginativeShohag/Why-Not-Compose + */ + package org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass import android.content.res.Configuration diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt index 67a639a1..d8078c31 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt @@ -1,3 +1,29 @@ +/* + * Copyright 2023 Md. Mahmudul Hasan Shohag + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ------------------------------------------------------------------------ + * + * Project: Why Not Compose! + * Developed by: @ImaginativeShohag + * + * Md. Mahmudul Hasan Shohag + * imaginativeshohag@gmail.com + * + * Source: https://github.com/ImaginativeShohag/Why-Not-Compose + */ + package org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass import android.content.res.Configuration diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassTwoScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassTwoScreen.kt index 4bc85cf9..d2b16efb 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassTwoScreen.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassTwoScreen.kt @@ -1,3 +1,29 @@ +/* + * Copyright 2023 Md. Mahmudul Hasan Shohag + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ------------------------------------------------------------------------ + * + * Project: Why Not Compose! + * Developed by: @ImaginativeShohag + * + * Md. Mahmudul Hasan Shohag + * imaginativeshohag@gmail.com + * + * Source: https://github.com/ImaginativeShohag/Why-Not-Compose + */ + package org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass import android.content.res.Configuration @@ -9,15 +35,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button import androidx.compose.material.Divider -import androidx.compose.material.OutlinedTextField import androidx.compose.material.Scaffold import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index aa940fd5..9a0b48f4 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -50,7 +50,7 @@ object Libs { } object DiffPlug { - const val version = "6.22.0" + const val version = "6.23.0" const val spotless = "com.diffplug.spotless" } diff --git a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheDataSource.kt b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheDataSource.kt index daf19722..a7081009 100644 --- a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheDataSource.kt +++ b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheDataSource.kt @@ -1,3 +1,29 @@ +/* + * Copyright 2023 Md. Mahmudul Hasan Shohag + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ------------------------------------------------------------------------ + * + * Project: Why Not Compose! + * Developed by: @ImaginativeShohag + * + * Md. Mahmudul Hasan Shohag + * imaginativeshohag@gmail.com + * + * Source: https://github.com/ImaginativeShohag/Why-Not-Compose + */ + package org.imaginativeworld.whynotcompose.cms.datasource.cache import android.content.Context diff --git a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheKey.kt b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheKey.kt index 48fb489e..64783f48 100644 --- a/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheKey.kt +++ b/cms/src/main/java/org/imaginativeworld/whynotcompose/cms/datasource/cache/StorageCacheKey.kt @@ -1,3 +1,29 @@ +/* + * Copyright 2023 Md. Mahmudul Hasan Shohag + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ------------------------------------------------------------------------ + * + * Project: Why Not Compose! + * Developed by: @ImaginativeShohag + * + * Md. Mahmudul Hasan Shohag + * imaginativeshohag@gmail.com + * + * Source: https://github.com/ImaginativeShohag/Why-Not-Compose + */ + package org.imaginativeworld.whynotcompose.cms.datasource.cache sealed class StorageCacheKey(val name: String) { From 093cc213add4e89c705520502b6789b6289a8d94 Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Fri, 1 Dec 2023 00:12:10 +0600 Subject: [PATCH 09/13] Add example for parameter data pass --- .../whynotcompose/ui/screens/NavGraphMain.kt | 54 ++++++- .../navdatapass/NavDataPassHomeScreen.kt | 75 ++++++++- .../navdatapass/NavDataPassOneScreen.kt | 4 + .../navdatapass/NavDataPassThreeScreen.kt | 147 ++++++++++++++++++ 4 files changed, 272 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassThreeScreen.kt diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt index 9c7d9be8..fe7e2321 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt @@ -102,6 +102,7 @@ import org.imaginativeworld.whynotcompose.ui.screens.tutorial.index.TutorialInde import org.imaginativeworld.whynotcompose.ui.screens.tutorial.lottie.LottieScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass.NavDataPassHomeScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass.NavDataPassOneScreen +import org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass.NavDataPassThreeScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass.NavDataPassTwoScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.onesignalandbroadcast.OneSignalAndBroadcastScreen import org.imaginativeworld.whynotcompose.ui.screens.tutorial.permission.PermissionScreen @@ -228,6 +229,17 @@ sealed class TutorialsScreen(val route: String) { data object TutorialNavDataPassScreen2 : TutorialsScreen("tutorial/nav-data-pass/two") { const val PARAM_DATA = "data" } + + data object TutorialNavDataPassScreen3 : + TutorialsScreen("tutorial/nav-data-pass/three/{id}/{name}/details") { + const val PARAM_ID = "id" + const val PARAM_NAME = "name" + + fun createRoute(id: Int, name: String) = + route + .replace("{$PARAM_ID}", "$id") + .replace("{$PARAM_NAME}", name) + } } // ================================================================ @@ -906,6 +918,8 @@ private fun NavGraphBuilder.addTutorialIndexScreen( ) } + // ================================================================ + // Navigation pass-receive example // ================================================================ composable(TutorialsScreen.TutorialNavDataPassHome.route) { backStateEntry -> @@ -930,6 +944,14 @@ private fun NavGraphBuilder.addTutorialIndexScreen( ) } navController.navigate(TutorialsScreen.TutorialNavDataPassScreen2.route) + }, + gotoScreenThree = { id, name -> + navController.navigate( + TutorialsScreen.TutorialNavDataPassScreen3.createRoute( + id = id, + name = name + ) + ) } ) } @@ -965,9 +987,7 @@ private fun NavGraphBuilder.addTutorialIndexScreen( } } - composable( - TutorialsScreen.TutorialNavDataPassScreen2.route - ) { + composable(TutorialsScreen.TutorialNavDataPassScreen2.route) { val data: DemoData? = navController.previousBackStackEntry?.savedStateHandle?.get( TutorialsScreen.TutorialNavDataPassScreen2.PARAM_DATA ) @@ -979,6 +999,34 @@ private fun NavGraphBuilder.addTutorialIndexScreen( } ) } + + composable( + TutorialsScreen.TutorialNavDataPassScreen3.route, + arguments = listOf( + navArgument(TutorialsScreen.TutorialNavDataPassScreen3.PARAM_ID) { + type = NavType.IntType + }, + navArgument(TutorialsScreen.TutorialNavDataPassScreen3.PARAM_NAME) { + type = NavType.StringType + } + ) + ) { backStackEntry -> + var id = 0 + var name = "" + + backStackEntry.arguments?.apply { + id = getInt(TutorialsScreen.TutorialNavDataPassScreen3.PARAM_ID) + name = getString(TutorialsScreen.TutorialNavDataPassScreen3.PARAM_NAME, "") + } + + NavDataPassThreeScreen( + id = id, + name = name, + goBack = { + navController.popBackStack() + } + ) + } } // ================================================================ diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt index 258f7c86..00e007ee 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassHomeScreen.kt @@ -29,20 +29,28 @@ package org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass import android.content.res.Configuration import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.Divider +import androidx.compose.material.OutlinedTextField import androidx.compose.material.Scaffold import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -66,7 +74,8 @@ fun NavDataPassHomeScreen( receivedData: DemoData?, goBack: () -> Unit, gotoScreenOne: (DemoData) -> Unit, - gotoScreenTwo: (DemoData) -> Unit + gotoScreenTwo: (DemoData) -> Unit, + gotoScreenThree: (id: Int, name: String) -> Unit ) { NavDataPassHomeScreenSkeleton( receivedData = receivedData, @@ -88,7 +97,8 @@ fun NavDataPassHomeScreen( ranks = listOf("A", "B", "C") ) ) - } + }, + gotoScreenThree = gotoScreenThree ) } @@ -125,8 +135,12 @@ fun NavDataPassHomeScreenSkeleton( receivedData: DemoData?, goBack: () -> Unit = {}, gotoScreenOne: () -> Unit = {}, - gotoScreenTwo: () -> Unit = {} + gotoScreenTwo: () -> Unit = {}, + gotoScreenThree: (id: Int, name: String) -> Unit = { _, _ -> } ) { + var id by remember { mutableStateOf("1") } + var name by remember { mutableStateOf("Mahmudul Hasan Shohag") } + Scaffold( Modifier .navigationBarsPadding() @@ -171,16 +185,67 @@ fun NavDataPassHomeScreenSkeleton( AppComponent.BigSpacer() + Text( + "Pass simple data as parameter (Recommended)", + fontWeight = FontWeight.Bold + ) + + OutlinedTextField( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + value = id, + onValueChange = { + val regex = Regex("[^0-9]") + id = regex.replace(it, "") + }, + label = { + Text("ID Parameter") + }, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number + ) + ) + + OutlinedTextField( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + value = name, + onValueChange = { name = it }, + label = { + Text("Name Parameter") + } + ) + + AppComponent.MediumSpacer() + + Button(onClick = { + gotoScreenThree(id.toInt(), name) + }) { + Text("Pass Parameters") + } + + AppComponent.BigSpacer() + + // ---------------------------------------------------------------- + + Divider() + + AppComponent.BigSpacer() + Button(onClick = { gotoScreenOne() }) { - Text("Pass data as String") + Text("Pass complex data as String") } + AppComponent.MediumSpacer() + Button(onClick = { gotoScreenTwo() }) { - Text("Pass data as Parcelable") + Text("Pass complex data as Parcelable") } // ---------------------------------------------------------------- diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt index d8078c31..101b3353 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassOneScreen.kt @@ -29,6 +29,7 @@ package org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass import android.content.res.Configuration import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding @@ -151,6 +152,9 @@ fun NavDataPassOneScreenSkeleton( AppComponent.BigSpacer() OutlinedTextField( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), value = text, onValueChange = { text = it }, label = { diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassThreeScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassThreeScreen.kt new file mode 100644 index 00000000..b3988a52 --- /dev/null +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassThreeScreen.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2023 Md. Mahmudul Hasan Shohag + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ------------------------------------------------------------------------ + * + * Project: Why Not Compose! + * Developed by: @ImaginativeShohag + * + * Md. Mahmudul Hasan Shohag + * imaginativeshohag@gmail.com + * + * Source: https://github.com/ImaginativeShohag/Why-Not-Compose + */ + +package org.imaginativeworld.whynotcompose.ui.screens.tutorial.navdatapass + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Divider +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.imaginativeworld.whynotcompose.common.compose.compositions.AppComponent +import org.imaginativeworld.whynotcompose.common.compose.theme.AppTheme + +@Composable +fun NavDataPassThreeScreen( + id: Int, + name: String, + goBack: () -> Unit +) { + NavDataPassThreeScreenSkeleton( + id = id, + name = name, + goBack = goBack + ) +} + +@Preview +@Composable +fun NavDataPassThreeScreenSkeletonPreview() { + AppTheme { + NavDataPassThreeScreenSkeleton( + id = 1, + name = "Mahmudul Hasan Shohag" + ) + } +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun NavDataPassThreeScreenSkeletonPreviewDark() { + AppTheme { + NavDataPassThreeScreenSkeleton( + id = 1, + name = "Mahmudul Hasan Shohag" + ) + } +} + +@Composable +fun NavDataPassThreeScreenSkeleton( + id: Int, + name: String, + goBack: () -> Unit = {} +) { + Scaffold( + Modifier + .navigationBarsPadding() + .imePadding() + .statusBarsPadding() + ) { innerPadding -> + Column( + Modifier + .padding(innerPadding) + .fillMaxSize() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + AppComponent.Header( + "Data passed as Parameter", + goBack = goBack + ) + + // ---------------------------------------------------------------- + // ---------------------------------------------------------------- + + Divider() + + AppComponent.BigSpacer() + + Text( + "Received Data", + fontWeight = FontWeight.Bold + ) + + AppComponent.MediumSpacer() + + Text( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = "ID: $id" + ) + + Text( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = "Name: $name" + ) + + // ---------------------------------------------------------------- + // ---------------------------------------------------------------- + + AppComponent.BigSpacer() + } + } +} From f318e0b6a9004fc3b356f82386fd341d47fb4656 Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Fri, 1 Dec 2023 00:13:10 +0600 Subject: [PATCH 10/13] Apply spotless --- .../ui/screens/tutorial/navdatapass/NavDataPassThreeScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassThreeScreen.kt b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassThreeScreen.kt index b3988a52..e9231d8d 100644 --- a/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassThreeScreen.kt +++ b/app/src/main/java/org/imaginativeworld/whynotcompose/ui/screens/tutorial/navdatapass/NavDataPassThreeScreen.kt @@ -45,7 +45,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.imaginativeworld.whynotcompose.common.compose.compositions.AppComponent From 6472756e786fe29f869e51b67840212c063872ed Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Fri, 1 Dec 2023 01:30:29 +0600 Subject: [PATCH 11/13] Update moshi --- .../whynotcompose/base/extensions/ExtMoshi.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/org/imaginativeworld/whynotcompose/base/extensions/ExtMoshi.kt b/base/src/main/java/org/imaginativeworld/whynotcompose/base/extensions/ExtMoshi.kt index 88e15898..4aa2ca56 100644 --- a/base/src/main/java/org/imaginativeworld/whynotcompose/base/extensions/ExtMoshi.kt +++ b/base/src/main/java/org/imaginativeworld/whynotcompose/base/extensions/ExtMoshi.kt @@ -24,9 +24,12 @@ * Source: https://github.com/ImaginativeShohag/Why-Not-Compose */ +@file:OptIn(ExperimentalStdlibApi::class) + package org.imaginativeworld.whynotcompose.base.extensions import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter import java.net.URLDecoder import java.net.URLEncoder import java.util.Date @@ -53,7 +56,7 @@ inline fun String?.getObjFromJson(urlDecode: Boolean = true): T? { Timber.e("getObjFromJson: $this") - val jsonAdapter = MoshiUtil.getMoshi().adapter(T::class.java).lenient() + val jsonAdapter = MoshiUtil.getMoshi().adapter().lenient() val result = jsonAdapter.fromJson( if (urlDecode) { @@ -80,7 +83,7 @@ inline fun T?.getJsonFromObj(urlEncode: Boolean = true): String? { Timber.e("getJsonFromObj: $this") - val jsonAdapter = MoshiUtil.getMoshi().adapter(T::class.java).lenient() + val jsonAdapter = MoshiUtil.getMoshi().adapter().lenient() return jsonAdapter.toJson(this).let { json -> val result = if (urlEncode) { From 3af42d89017a1b24d7f89b832471c3c3c20712dd Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Sat, 2 Dec 2023 21:53:59 +0600 Subject: [PATCH 12/13] Update dependency --- app/build.gradle.kts | 1 + buildSrc/src/main/kotlin/Libs.kt | 12 ++++++------ gradle.properties | 3 ++- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0bc8486a..7be80419 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -98,6 +98,7 @@ android { } buildFeatures { + buildConfig = true compose = true // Disable unused AGP features diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index 9a0b48f4..d804bc0f 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -50,13 +50,13 @@ object Libs { } object DiffPlug { - const val version = "6.23.0" + const val version = "6.23.2" const val spotless = "com.diffplug.spotless" } object Gradle { - const val version = "8.1.4" + const val version = "8.2.0" } object Yalantis { @@ -90,7 +90,7 @@ object Libs { } object Hilt { - const val version = "2.48.1" + const val version = "2.49" const val core = "com.google.dagger:hilt-android:$version" const val compiler = "com.google.dagger:hilt-compiler:$version" @@ -157,11 +157,11 @@ object Libs { object Compose { private const val bomVersion = "2023.10.01" - const val compilerVersion = "1.5.4" + const val compilerVersion = "1.5.5" private const val runtimeTracingVersion = "1.0.0-alpha04" // TODO: Remove when library gets stable. - private const val material3Version = "1.2.0-alpha11" + private const val material3Version = "1.2.0-alpha12" const val bom = "androidx.compose:compose-bom:$bomVersion" @@ -239,7 +239,7 @@ object Libs { } object Room { - private const val version = "2.6.0" + private const val version = "2.6.1" const val runtime = "androidx.room:room-runtime:$version" const val compiler = "androidx.room:room-compiler:$version" diff --git a/gradle.properties b/gradle.properties index 72e44221..cf21acf6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -32,7 +32,8 @@ kotlin.code.style=official android.enableR8.fullMode=true # BuildConfig will auto generate. -android.defaults.buildfeatures.buildconfig=true +# The option setting 'android.defaults.buildfeatures.buildconfig=true' is deprecated. +# android.defaults.buildfeatures.buildconfig=true # Generate BuildConfig as a compiled file instead of Java file. # android.enableBuildConfigAsBytecode=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9dba1b4b..93101410 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Apr 14 06:37:20 BDT 2023 +#Fri Dec 01 19:22:45 BDT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From c8aacc54451e010d4c40bf2d41615776c043e6d1 Mon Sep 17 00:00:00 2001 From: Mahmudul Hasan Shohag Date: Sat, 2 Dec 2023 21:57:31 +0600 Subject: [PATCH 13/13] Update `README.md` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f4c5ce41..af843713 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Feel free to request features or suggestions for improvements. - OneSignal and Broadcast (Intermediate) - ExoPlayer (Advanced) - CMS (Advanced) + - Memory and storage caching - [Deep Link](https://developer.android.com/training/app-links) (Intermediate) | ![Counter](images/counter.gif) | ![Animated Visibility](images/animated-visibility.gif) | ![Lottie](images/lottie.gif) |