├── .github └── workflows │ └── android.yml ├── .gitignore ├── .idea ├── .gitignore └── compiler.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── google-services.json ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── id │ │ └── barakkastudio │ │ └── nebenginaja │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── id │ │ │ └── barakkastudio │ │ │ └── nebenginaja │ │ │ ├── MyApp.kt │ │ │ ├── ui │ │ │ ├── JetMainApp.kt │ │ │ ├── JetMainScreen.kt │ │ │ └── MainActivity.kt │ │ │ └── utils │ │ │ └── JetNavigationType.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── id │ └── barakkastudio │ └── nebenginaja │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── Modules.kt │ ├── Versions.kt │ └── dependencies │ └── MyDependencies.kt ├── core ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── google-services.json ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── id │ │ └── barakkastudio │ │ └── core │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── id │ │ │ └── barakkastudio │ │ │ └── core │ │ │ ├── data │ │ │ ├── UiState.kt │ │ │ ├── datasource │ │ │ │ ├── local │ │ │ │ │ └── db │ │ │ │ │ │ ├── AppDatabase.kt │ │ │ │ │ │ ├── dao │ │ │ │ │ │ └── ProductDao.kt │ │ │ │ │ │ └── entity │ │ │ │ │ │ └── ProductEntity.kt │ │ │ │ └── remote │ │ │ │ │ └── ApiService.kt │ │ │ ├── model │ │ │ │ ├── Product.kt │ │ │ │ ├── ProductResponse.kt │ │ │ │ └── mapper │ │ │ │ │ └── ProductMapper.kt │ │ │ └── repository │ │ │ │ └── product │ │ │ │ ├── DbProductRepositoryImpl.kt │ │ │ │ └── ProductRepositoryImpl.kt │ │ │ ├── di │ │ │ ├── DatabaseModule.kt │ │ │ ├── NetworkModule.kt │ │ │ ├── RepositoryModule.kt │ │ │ └── usecase │ │ │ │ └── ProductUseCaseModule.kt │ │ │ ├── domain │ │ │ ├── repository │ │ │ │ └── product │ │ │ │ │ ├── DbProductRepository.kt │ │ │ │ │ └── ProductRepository.kt │ │ │ └── usecase │ │ │ │ ├── BaseUseCase.kt │ │ │ │ ├── BaseUseCaseSuspend.kt │ │ │ │ └── product │ │ │ │ ├── GetProductByIdUseCase.kt │ │ │ │ ├── GetProductsUseCase.kt │ │ │ │ ├── SearchProductUseCase.kt │ │ │ │ └── db │ │ │ │ ├── DeleteProductDbUseCase.kt │ │ │ │ ├── GetProductByIdDbUseCase.kt │ │ │ │ ├── GetProductsDbUseCase.kt │ │ │ │ └── InsertProductDbUseCase.kt │ │ │ ├── network │ │ │ └── interceptor │ │ │ │ └── HttpRequestInterceptor.kt │ │ │ ├── ui │ │ │ ├── component │ │ │ │ └── molecules │ │ │ │ │ └── SearchBar.kt │ │ │ ├── template │ │ │ │ ├── DetailTemplate.kt │ │ │ │ └── MainTemplate.kt │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Shape.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── util │ │ │ ├── Dimens.kt │ │ │ ├── Extensions.kt │ │ │ ├── UtilConstants.kt │ │ │ ├── UtilFunctions.kt │ │ │ └── UtilTests.kt │ └── res │ │ ├── drawable │ │ ├── cart.xml │ │ ├── home.xml │ │ ├── ic_launcher_background.xml │ │ ├── logo_nebenginaja.webp │ │ ├── rival_profile.webp │ │ └── user.xml │ │ └── values │ │ └── string.xml │ └── test │ └── java │ └── id │ └── barakkastudio │ └── core │ ├── ExampleUnitTest.kt │ ├── data │ └── repository │ │ └── product │ │ └── ProductRepositoryImplTest.kt │ └── domain │ └── usecase │ └── product │ └── GetProductsUseCaseTest.kt ├── features └── sample │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── id │ │ └── barakkastudio │ │ └── sample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── id │ │ └── barakkastudio │ │ └── sample │ │ └── ui │ │ ├── cart │ │ ├── CartScreen.kt │ │ ├── CartViewModel.kt │ │ └── section │ │ │ └── CartContent.kt │ │ ├── component │ │ ├── EmptyProduct.kt │ │ ├── ProductCartItem.kt │ │ ├── ProductItem.kt │ │ └── ProgressProduct.kt │ │ ├── detail │ │ ├── DetailScreen.kt │ │ ├── DetailViewModel.kt │ │ └── section │ │ │ ├── DescriptionProduct.kt │ │ │ ├── DetailContent.kt │ │ │ ├── ImageProductPager.kt │ │ │ └── TitleProduct.kt │ │ ├── home │ │ ├── HomeScreen.kt │ │ ├── HomeViewModel.kt │ │ └── section │ │ │ └── HomeContent.kt │ │ ├── navigation │ │ ├── BottomNav.kt │ │ ├── MainNavHost.kt │ │ ├── NavRail.kt │ │ ├── model │ │ │ ├── BottomBar.kt │ │ │ ├── BottomBarScreen.kt │ │ │ └── GeneralScreen.kt │ │ └── navdrawer │ │ │ ├── NavDrawer.kt │ │ │ └── sections │ │ │ ├── NavDrawerHeader.kt │ │ │ ├── NavDrawerMenu.kt │ │ │ └── NavDrawerSection.kt │ │ ├── profile │ │ ├── ProfileScreen.kt │ │ └── section │ │ │ └── ProfileContent.kt │ │ ├── search │ │ ├── SearchScreen.kt │ │ └── section │ │ │ └── SearchContent.kt │ │ └── splash │ │ └── SplashScreen.kt │ └── test │ └── java │ └── id │ └── barakkastudio │ └── sample │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | env: 4 | # The name of the main module repository 5 | main_project_module: app 6 | 7 | # The name of Developer 8 | dev_name: rivaldy 9 | 10 | on: 11 | push: 12 | branches: 13 | - master 14 | - develop 15 | - staging 16 | - develop-M3 17 | 18 | # Allows you to run this workflow manually from the Actions tab 19 | workflow_dispatch: 20 | 21 | jobs: 22 | build: 23 | 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | 29 | # Set Current Date As Env Variable 30 | - name: Set current date as env variable 31 | run: echo "date_today=$(date +'%Y-%m-%d')" >> $GITHUB_ENV 32 | 33 | # Set Repository Name As Env Variable 34 | - name: Set repository name as env variable 35 | run: echo "repository_name=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV 36 | 37 | - name: Set Up JDK 38 | uses: actions/setup-java@v3 39 | with: 40 | distribution: 'zulu' # See 'Supported distributions' for available options 41 | java-version: '17' 42 | cache: 'gradle' 43 | 44 | - name: Change wrapper permissions 45 | run: chmod +x ./gradlew 46 | 47 | # Run Tests Build 48 | - name: Run gradle tests 49 | run: ./gradlew test 50 | 51 | # Run Build Project 52 | - name: Build gradle project 53 | run: ./gradlew build 54 | 55 | # Create APK Debug 56 | - name: Build apk debug project (APK)-${{ env.main_project_module }} module 57 | run: ./gradlew assembleDebug 58 | 59 | # Create APK Release 60 | - name: Build apk release project (APK)-${{ env.main_project_module }} module 61 | run: ./gradlew assemble 62 | 63 | # Create Bundle AAB Release 64 | # Noted for main module build [main_project_module]:bundleRelease 65 | - name: Build app bundle release (AAB)-${{ env.main_project_module }} module 66 | run: ./gradlew ${{ env.main_project_module }}:bundleRelease 67 | 68 | # Upload Artifact Build 69 | # Noted For Output [main_project_module]/build/outputs/apk/debug/app-debug.apk 70 | - name: Upload file debug-${{ env.repository_name }}.apk 71 | uses: actions/upload-artifact@v3 72 | with: 73 | name: ${{ env.date_today }}-${{ env.dev_name }}-${{ env.repository_name }}.apk 74 | path: ${{ env.main_project_module }}/build/outputs/apk/debug/app-debug.apk 75 | 76 | # Upload Artifact Build 77 | # Noted For Output [main_project_module]/build/outputs/apk/debug/ 78 | - name: Upload APK Debug-${{ env.repository_name }} 79 | uses: actions/upload-artifact@v3 80 | with: 81 | name: ${{ env.date_today }}-${{ env.dev_name }}-${{ env.repository_name }}-APK(s) debug generated 82 | path: ${{ env.main_project_module }}/build/outputs/apk/debug/ 83 | 84 | # Noted For Output [main_project_module]/build/outputs/apk/release/ 85 | - name: Upload APK Release-${{ env.repository_name }} 86 | uses: actions/upload-artifact@v3 87 | with: 88 | name: ${{ env.date_today }}-${{ env.dev_name }}-${{ env.repository_name }}-APK(s) release generated 89 | path: ${{ env.main_project_module }}/build/outputs/apk/release/ 90 | 91 | # Noted For Output [main_project_module]/build/outputs/bundle/release/ 92 | - name: Upload AAB (App Bundle) Release-${{ env.repository_name }} 93 | uses: actions/upload-artifact@v3 94 | with: 95 | name: ${{ env.date_today }}-${{ env.dev_name }}-${{ env.repository_name }}-App bundle(s) AAB release generated 96 | path: ${{ env.main_project_module }}/build/outputs/bundle/release/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | /.idea/codeStyles 11 | /.idea/sonarlint 12 | .DS_Store 13 | /build 14 | /captures 15 | .externalNativeBuild 16 | .cxx 17 | local.properties 18 | /.idea/kotlinc.xml 19 | /.idea/deploymentTargetDropDown.xml 20 | /.idea/.name 21 | /.idea/appInsightsSettings.xml 22 | /.idea/migrations.xml 23 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | vcs.xml 5 | misc.xml 6 | /git_toolbox_prj.xml 7 | gradle.xml 8 | /inspectionProfiles/Project_Default.xml -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Rivaldy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dependencies.MyDependencies 2 | 3 | plugins { 4 | id("com.android.application") 5 | id("org.jetbrains.kotlin.android") 6 | id("dagger.hilt.android.plugin") 7 | id("kotlin-kapt") 8 | } 9 | 10 | @Suppress("UnstableApiUsage") 11 | android { 12 | namespace = "id.barakkastudio.nebenginaja" 13 | compileSdk = Versions.compile_sdk 14 | 15 | defaultConfig { 16 | applicationId = "id.barakkastudio.nebenginaja" 17 | minSdk = Versions.min_sdk 18 | targetSdk = Versions.target_sdk 19 | versionCode = Versions.version_code 20 | versionName = Versions.version_name 21 | 22 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 23 | vectorDrawables { 24 | useSupportLibrary = true 25 | } 26 | } 27 | 28 | buildTypes { 29 | release { 30 | isMinifyEnabled = false 31 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 32 | } 33 | } 34 | compileOptions { 35 | sourceCompatibility = JavaVersion.VERSION_11 36 | targetCompatibility = JavaVersion.VERSION_11 37 | } 38 | kotlinOptions { 39 | jvmTarget = JavaVersion.VERSION_11.toString() 40 | } 41 | buildFeatures { 42 | compose = true 43 | } 44 | composeOptions { 45 | kotlinCompilerExtensionVersion = Versions.compose_compiler 46 | } 47 | packagingOptions { 48 | resources { 49 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 50 | } 51 | } 52 | tasks.withType().configureEach { 53 | kotlinOptions { 54 | freeCompilerArgs = freeCompilerArgs + listOf( 55 | "-opt-in=kotlin.RequiresOptIn", 56 | "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", 57 | "-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi", 58 | ) 59 | } 60 | } 61 | } 62 | 63 | dependencies { 64 | implementation(project(Modules.core)) 65 | implementation(project(Modules.sample)) 66 | 67 | // TESTING 68 | testImplementation(MyDependencies.junit) 69 | androidTestImplementation(MyDependencies.test_ext_junit) 70 | androidTestImplementation(MyDependencies.espresso_core) 71 | androidTestImplementation(MyDependencies.junit_compose) 72 | debugImplementation(MyDependencies.ui_tooling) 73 | debugImplementation(MyDependencies.ui_test_manifest) 74 | 75 | // Hilt 76 | implementation(MyDependencies.hilt_android) 77 | kapt(MyDependencies.hilt_android_compiler) 78 | kapt(MyDependencies.hilt_compose_compiler) 79 | } -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "607026654260", 4 | "project_id": "nebengin-aja-id-01", 5 | "storage_bucket": "nebengin-aja-id-01.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:607026654260:android:51de97fab722422a946e61", 11 | "android_client_info": { 12 | "package_name": "id.barakkastudio.nebenginaja" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "607026654260-s7ahdi6a05bhvvjhfit3eavrbbclot36.apps.googleusercontent.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "AIzaSyCwqgKIUtLH0XC2aMeU-NHJFjdZjRIbDWQ" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "607026654260-s7ahdi6a05bhvvjhfit3eavrbbclot36.apps.googleusercontent.com", 31 | "client_type": 3 32 | } 33 | ] 34 | } 35 | } 36 | } 37 | ], 38 | "configuration_version": "1" 39 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/id/barakkastudio/nebenginaja/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.nebenginaja 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("id.barakkastudio.nebenginaja", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/id/barakkastudio/nebenginaja/MyApp.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.nebenginaja 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | /** Created by github.com/im-o on 12/16/2022. */ 7 | 8 | @HiltAndroidApp 9 | class MyApp : Application() -------------------------------------------------------------------------------- /app/src/main/java/id/barakkastudio/nebenginaja/ui/JetMainApp.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.nebenginaja.ui 2 | 3 | import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.Modifier 6 | import id.barakkastudio.nebenginaja.utils.JetNavigationType 7 | 8 | /** Created by github.com/im-o on 10/28/2023. */ 9 | 10 | @Composable 11 | fun JetMainApp( 12 | windowSize: WindowWidthSizeClass, 13 | modifier: Modifier = Modifier, 14 | ) { 15 | val navigationType: JetNavigationType = when (windowSize) { 16 | WindowWidthSizeClass.Compact -> { 17 | JetNavigationType.BOTTOM_NAVIGATION 18 | } 19 | 20 | WindowWidthSizeClass.Medium -> { 21 | JetNavigationType.NAVIGATION_RAIL 22 | } 23 | 24 | WindowWidthSizeClass.Expanded -> { 25 | JetNavigationType.PERMANENT_NAVIGATION_DRAWER 26 | } 27 | 28 | else -> { 29 | JetNavigationType.BOTTOM_NAVIGATION 30 | } 31 | } 32 | 33 | JetMainScreen(navigationType = navigationType, modifier = modifier) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/id/barakkastudio/nebenginaja/ui/JetMainScreen.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.nebenginaja.ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.tooling.preview.Devices 7 | import androidx.compose.ui.tooling.preview.Preview 8 | import androidx.navigation.NavHostController 9 | import androidx.navigation.compose.currentBackStackEntryAsState 10 | import androidx.navigation.compose.rememberNavController 11 | import id.barakkastudio.core.ui.theme.JetShopeeTheme 12 | import id.barakkastudio.nebenginaja.utils.JetNavigationType 13 | import id.barakkastudio.sample.ui.navigation.BottomNav 14 | import id.barakkastudio.sample.ui.navigation.NavRail 15 | import id.barakkastudio.sample.ui.navigation.model.BottomBarScreen 16 | import id.barakkastudio.sample.ui.navigation.navdrawer.NavDrawer 17 | 18 | /** Created by github.com/im-o on 12/12/2022. */ 19 | 20 | @Composable 21 | fun JetMainScreen( 22 | modifier: Modifier = Modifier, 23 | navigationType: JetNavigationType, 24 | navController: NavHostController = rememberNavController(), 25 | ) { 26 | val navBackStackEntry by navController.currentBackStackEntryAsState() 27 | val currentDestination = navBackStackEntry?.destination 28 | 29 | val navigationItemContentList = listOf( 30 | BottomBarScreen.Home, 31 | BottomBarScreen.Cart, 32 | BottomBarScreen.Profile 33 | ) 34 | 35 | when (navigationType) { 36 | JetNavigationType.BOTTOM_NAVIGATION -> { 37 | BottomNav( 38 | modifier = modifier, 39 | navigationItemContentList = navigationItemContentList, 40 | navController = navController, 41 | currentDestination = currentDestination, 42 | ) 43 | } 44 | 45 | JetNavigationType.NAVIGATION_RAIL -> { 46 | NavRail( 47 | modifier = modifier, 48 | navigationItemContentList = navigationItemContentList, 49 | navController = navController, 50 | currentDestination = currentDestination, 51 | ) 52 | } 53 | 54 | JetNavigationType.PERMANENT_NAVIGATION_DRAWER -> { 55 | NavDrawer( 56 | modifier = modifier, 57 | navigationItemContentList = navigationItemContentList, 58 | navController = navController, 59 | currentDestination = currentDestination, 60 | ) 61 | } 62 | } 63 | } 64 | 65 | @Preview(showBackground = true, device = Devices.PIXEL_4) 66 | @Composable 67 | fun DefaultPreview() { 68 | JetShopeeTheme { 69 | JetMainScreen(navigationType = JetNavigationType.BOTTOM_NAVIGATION) 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/main/java/id/barakkastudio/nebenginaja/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.nebenginaja.ui 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.material3.Surface 7 | import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass 8 | import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.getValue 11 | import androidx.compose.runtime.mutableStateOf 12 | import androidx.compose.runtime.remember 13 | import androidx.compose.runtime.setValue 14 | import androidx.compose.ui.tooling.preview.Devices 15 | import androidx.compose.ui.tooling.preview.Preview 16 | import dagger.hilt.android.AndroidEntryPoint 17 | import id.barakkastudio.core.ui.theme.JetShopeeTheme 18 | import id.barakkastudio.sample.ui.splash.SplashScreen 19 | 20 | @AndroidEntryPoint 21 | class MainActivity : ComponentActivity() { 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContent { 25 | JetShopeeTheme { 26 | Surface { 27 | val windowSize = calculateWindowSizeClass(this) 28 | var showSplashScreen by remember { mutableStateOf(true) } 29 | if (showSplashScreen) { 30 | SplashScreen(onTimeout = { showSplashScreen = false }) 31 | } else { 32 | JetMainApp(windowSize = windowSize.widthSizeClass) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | @Preview(showBackground = true, device = Devices.PIXEL_4) 41 | @Composable 42 | fun JetShopeePreview() { 43 | JetShopeeTheme { 44 | Surface { 45 | JetMainApp(windowSize = WindowWidthSizeClass.Compact) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/id/barakkastudio/nebenginaja/utils/JetNavigationType.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.nebenginaja.utils 2 | 3 | /** Created by github.com/im-o on 10/28/2023. */ 4 | 5 | enum class JetNavigationType { 6 | BOTTOM_NAVIGATION, NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER 7 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | NebenginAja 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/test/java/id/barakkastudio/nebenginaja/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.nebenginaja 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | dependencies { 3 | classpath("com.google.dagger:hilt-android-gradle-plugin:${Versions.hilt_android}") 4 | classpath("com.google.gms:google-services:${Versions.google_services}") 5 | } 6 | } 7 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 8 | plugins { 9 | id("com.android.application") version Versions.gradle_plugin apply false 10 | id("com.android.library") version Versions.gradle_plugin apply false 11 | id("org.jetbrains.kotlin.android") version Versions.kotlin_gradle_plugin apply false 12 | } -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Modules.kt: -------------------------------------------------------------------------------- 1 | /** Created by github.com/im-o on 12/13/2022. */ 2 | 3 | object Modules { 4 | const val app = ":app" 5 | const val core = ":core" 6 | const val sample = ":features:sample" 7 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Versions.kt: -------------------------------------------------------------------------------- 1 | /** Created by github.com/im-o on 12/12/2022. */ 2 | 3 | object Versions { 4 | const val compile_sdk = 33 5 | const val min_sdk = 24 6 | const val target_sdk = 33 7 | const val version_code = 1 8 | const val version_name = "1.0" 9 | 10 | const val gradle_plugin = "7.3.1" 11 | const val google_services = "4.3.15" 12 | const val kotlin_gradle_plugin = "1.8.21" 13 | const val compose_compiler = "1.4.7" 14 | 15 | const val core_ktx_version = "1.9.0" 16 | const val lifecycle_ktx_version = "2.6.1" 17 | const val activity_compose_version = "1.7.1" 18 | const val material_compose_version = "1.3.1" 19 | const val material_3_version = "1.1.2" 20 | const val junit_version = "4.13.2" 21 | const val test_ext_junit_version = "1.1.5" 22 | const val espresso_test_version = "3.5.1" 23 | const val navigation_compose = "2.5.3" 24 | const val mockito_kotlin_version = "4.1.0" 25 | const val coroutines_test_version = "1.6.4" 26 | 27 | const val retrofit_version = "2.9.0" 28 | const val okHttp3_version = "4.9.3" 29 | const val coil_version = "2.2.2" 30 | const val hilt_android = "2.45" 31 | const val hilt_compose = "1.0.0" 32 | const val room_version = "2.4.3" 33 | const val compose_ui_version = "1.3.1" 34 | const val accompanist_pager_version = "0.28.0" 35 | const val accompanist_systemuicontroller_version = "0.28.0" 36 | 37 | const val realtime_database_version = "20.2.1" 38 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dependencies/MyDependencies.kt: -------------------------------------------------------------------------------- 1 | package dependencies 2 | 3 | /** Created by github.com/im-o on 12/13/2022. */ 4 | 5 | object MyDependencies { 6 | 7 | // DEFAULT DEPENDENCIES 8 | const val core_ktx = "androidx.core:core-ktx:${Versions.core_ktx_version}" 9 | const val lifecycle_ktx = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle_ktx_version}" 10 | const val activity_compose = "androidx.activity:activity-compose:${Versions.activity_compose_version}" 11 | const val ui_compose = "androidx.compose.ui:ui:${Versions.compose_ui_version}" 12 | const val ui_tooling_preview = "androidx.compose.ui:ui-tooling-preview:${Versions.compose_ui_version}" 13 | 14 | const val material_compose = "androidx.compose.material:material:${Versions.material_compose_version}" 15 | const val material_3 = "androidx.compose.material3:material3:${Versions.material_3_version}" 16 | const val junit = "junit:junit:${Versions.junit_version}" 17 | const val test_ext_junit = "androidx.test.ext:junit:${Versions.test_ext_junit_version}" 18 | const val espresso_core = "androidx.test.espresso:espresso-core:${Versions.espresso_test_version}" 19 | const val junit_compose = "androidx.compose.ui:ui-test-junit4:${Versions.compose_ui_version}" 20 | const val ui_tooling = "androidx.compose.ui:ui-tooling:${Versions.compose_ui_version}" 21 | const val ui_test_manifest = "androidx.compose.ui:ui-test-manifest:${Versions.compose_ui_version}" 22 | 23 | const val navigation_compose = "androidx.navigation:navigation-compose:${Versions.navigation_compose}" 24 | 25 | // MOCKITO-KOTLIN 26 | const val mockito_kotlin = "org.mockito.kotlin:mockito-kotlin:${Versions.mockito_kotlin_version}" 27 | 28 | // COROUTINES-TEST 29 | const val coroutines_test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutines_test_version}" 30 | 31 | // REMOTE 32 | const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit_version}" 33 | const val retrofit2_converter_gson = "com.squareup.retrofit2:converter-gson:${Versions.retrofit_version}" 34 | const val retrofit2_adapter_rxjava2 = "com.squareup.retrofit2:adapter-rxjava2:${Versions.retrofit_version}" 35 | const val okhttp3 = "com.squareup.okhttp3:logging-interceptor:${Versions.okHttp3_version}" 36 | const val coil = "io.coil-kt:coil-compose:${Versions.coil_version}" 37 | 38 | // HILT 39 | const val hilt_android = "com.google.dagger:hilt-android:${Versions.hilt_android}" 40 | const val hilt_android_compiler = "com.google.dagger:hilt-android-compiler:${Versions.hilt_android}" 41 | const val hilt_compose = "androidx.hilt:hilt-navigation-compose:${Versions.hilt_compose}" 42 | const val hilt_compose_compiler = "androidx.hilt:hilt-compiler:${Versions.hilt_compose}" 43 | 44 | // ROOM 45 | const val room = "androidx.room:room-runtime:${Versions.room_version}" 46 | const val room_kapt = "androidx.room:room-compiler:${Versions.room_version}" 47 | const val room_ktx = "androidx.room:room-ktx:${Versions.room_version}" // optional - Kotlin Extensions and Coroutines support for Room 48 | 49 | // PAGER 50 | const val accompanist_pager = "com.google.accompanist:accompanist-pager:${Versions.accompanist_pager_version}" 51 | const val accompanist_pager_indicator = "com.google.accompanist:accompanist-pager-indicators:${Versions.accompanist_pager_version}" 52 | 53 | // SYSTEMUI-CONTROLLER 54 | const val accompanist_systemuicontroller = "com.google.accompanist:accompanist-systemuicontroller:${Versions.accompanist_systemuicontroller_version}" 55 | 56 | // FIREBASE 57 | const val realtime_database = "com.google.firebase:firebase-database-ktx:${Versions.realtime_database_version}" 58 | } -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dependencies.MyDependencies 2 | 3 | plugins { 4 | id("com.android.library") 5 | id("org.jetbrains.kotlin.android") 6 | id("dagger.hilt.android.plugin") 7 | id("kotlin-kapt") 8 | id("com.google.gms.google-services") 9 | } 10 | 11 | @Suppress("UnstableApiUsage") 12 | android { 13 | namespace = "id.barakkastudio.core" 14 | compileSdk = Versions.compile_sdk 15 | 16 | defaultConfig { 17 | minSdk = Versions.min_sdk 18 | targetSdk = Versions.target_sdk 19 | 20 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 21 | consumerProguardFiles("consumer-rules.pro") 22 | } 23 | 24 | buildTypes { 25 | release { 26 | isMinifyEnabled = false 27 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 28 | } 29 | } 30 | compileOptions { 31 | sourceCompatibility = JavaVersion.VERSION_11 32 | targetCompatibility = JavaVersion.VERSION_11 33 | } 34 | kotlinOptions { 35 | jvmTarget = JavaVersion.VERSION_11.toString() 36 | } 37 | buildFeatures { 38 | compose = true 39 | } 40 | composeOptions { 41 | kotlinCompilerExtensionVersion = Versions.compose_compiler 42 | } 43 | packagingOptions { 44 | resources { 45 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 46 | } 47 | } 48 | tasks.withType().configureEach { 49 | kotlinOptions { 50 | freeCompilerArgs = freeCompilerArgs + listOf( 51 | "-opt-in=kotlin.RequiresOptIn", 52 | "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", 53 | "-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi", 54 | ) 55 | } 56 | } 57 | } 58 | 59 | dependencies { 60 | 61 | // DEFAULT DEPENDENCIES 62 | api(MyDependencies.core_ktx) 63 | api(MyDependencies.lifecycle_ktx) 64 | 65 | // COMPOSE 66 | api(MyDependencies.activity_compose) 67 | api(platform("androidx.compose:compose-bom:2023.06.00")) 68 | api("androidx.compose.ui:ui") 69 | api("androidx.compose.ui:ui-graphics") 70 | api("androidx.compose.ui:ui-tooling-preview") 71 | api("androidx.compose.material3:material3") 72 | api("androidx.compose.material3:material3-window-size-class") 73 | api(MyDependencies.navigation_compose) 74 | 75 | // FIREBASE 76 | api(MyDependencies.realtime_database) 77 | 78 | // TESTING 79 | testImplementation(MyDependencies.junit) 80 | androidTestImplementation(MyDependencies.test_ext_junit) 81 | androidTestImplementation(MyDependencies.espresso_core) 82 | androidTestImplementation(platform("androidx.compose:compose-bom:2023.06.00")) 83 | androidTestImplementation("androidx.compose.ui:ui-test-junit4") 84 | debugImplementation("androidx.compose.ui:ui-tooling") 85 | debugImplementation("androidx.compose.ui:ui-test-manifest") 86 | 87 | // MOCKITO-KOTLIN 88 | testImplementation(MyDependencies.mockito_kotlin) 89 | 90 | // COROUTINES TEST 91 | testImplementation(MyDependencies.coroutines_test) 92 | 93 | // REMOTE 94 | api(MyDependencies.retrofit) 95 | api(MyDependencies.retrofit2_converter_gson) 96 | api(MyDependencies.retrofit2_adapter_rxjava2) 97 | api(MyDependencies.okhttp3) 98 | 99 | // COIL 100 | api(MyDependencies.coil) 101 | 102 | // Hilt 103 | implementation(MyDependencies.hilt_android) 104 | kapt(MyDependencies.hilt_android_compiler) 105 | api(MyDependencies.hilt_compose) { 106 | exclude("androidx.lifecycle", "lifecycle-viewmodel-ktx") 107 | } 108 | kapt(MyDependencies.hilt_compose_compiler) 109 | 110 | // Room 111 | api(MyDependencies.room) 112 | kapt(MyDependencies.room_kapt) 113 | api(MyDependencies.room_ktx) 114 | 115 | // PAGER 116 | api(MyDependencies.accompanist_pager) 117 | api(MyDependencies.accompanist_pager_indicator) 118 | 119 | // System UI Controller 120 | api(MyDependencies.accompanist_systemuicontroller) 121 | } -------------------------------------------------------------------------------- /core/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/core/consumer-rules.pro -------------------------------------------------------------------------------- /core/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "607026654260", 4 | "firebase_url": "https://nebengin-aja-id-01-default-rtdb.firebaseio.com", 5 | "project_id": "nebengin-aja-id-01", 6 | "storage_bucket": "nebengin-aja-id-01.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:607026654260:android:61af487fd601ea24946e61", 12 | "android_client_info": { 13 | "package_name": "id.barakkastudio.core" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "607026654260-8htmth6p2hto60i1s6ldllncdqicu4t2.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "id.barakkastudio.core", 22 | "certificate_hash": "65fec611e0e4af8602aa2d5fafb3151058ebfd38" 23 | } 24 | }, 25 | { 26 | "client_id": "607026654260-s7ahdi6a05bhvvjhfit3eavrbbclot36.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyCwqgKIUtLH0XC2aMeU-NHJFjdZjRIbDWQ" 33 | } 34 | ], 35 | "services": { 36 | "appinvite_service": { 37 | "other_platform_oauth_client": [ 38 | { 39 | "client_id": "607026654260-s7ahdi6a05bhvvjhfit3eavrbbclot36.apps.googleusercontent.com", 40 | "client_type": 3 41 | } 42 | ] 43 | } 44 | } 45 | }, 46 | { 47 | "client_info": { 48 | "mobilesdk_app_id": "1:607026654260:android:51de97fab722422a946e61", 49 | "android_client_info": { 50 | "package_name": "id.barakkastudio.nebenginaja" 51 | } 52 | }, 53 | "oauth_client": [ 54 | { 55 | "client_id": "607026654260-s7ahdi6a05bhvvjhfit3eavrbbclot36.apps.googleusercontent.com", 56 | "client_type": 3 57 | } 58 | ], 59 | "api_key": [ 60 | { 61 | "current_key": "AIzaSyCwqgKIUtLH0XC2aMeU-NHJFjdZjRIbDWQ" 62 | } 63 | ], 64 | "services": { 65 | "appinvite_service": { 66 | "other_platform_oauth_client": [ 67 | { 68 | "client_id": "607026654260-s7ahdi6a05bhvvjhfit3eavrbbclot36.apps.googleusercontent.com", 69 | "client_type": 3 70 | } 71 | ] 72 | } 73 | } 74 | } 75 | ], 76 | "configuration_version": "1" 77 | } -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/src/androidTest/java/id/barakkastudio/core/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("id.barakkastudio.core.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/UiState.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data 2 | 3 | /** Created by github.com/im-o on 12/20/2022. */ 4 | 5 | sealed class UiState { 6 | 7 | object Loading : UiState() 8 | 9 | data class Success(val data: T) : UiState() 10 | 11 | data class Error(val errorMessage: String) : UiState() 12 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/datasource/local/db/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.datasource.local.db 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import id.barakkastudio.core.data.datasource.local.db.dao.ProductDao 6 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 7 | 8 | /** Created by github.com/im-o on 12/27/2022. */ 9 | 10 | @Database( 11 | entities = [ProductEntity::class], 12 | version = 1, 13 | exportSchema = false 14 | ) 15 | 16 | abstract class AppDatabase : RoomDatabase() { 17 | abstract fun productDao(): ProductDao 18 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/datasource/local/db/dao/ProductDao.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.datasource.local.db.dao 2 | 3 | import androidx.room.* 4 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 5 | 6 | /** Created by github.com/im-o on 12/27/2022. */ 7 | 8 | @Dao 9 | interface ProductDao { 10 | 11 | @Query("SELECT * FROM table_shop") 12 | fun getProducts(): MutableList 13 | 14 | @Query("SELECT * FROM table_shop WHERE id=:id ") 15 | fun getProductById(id: Long): ProductEntity 16 | 17 | @Insert(onConflict = OnConflictStrategy.REPLACE) 18 | suspend fun insertProduct(product: ProductEntity): Long 19 | 20 | @Delete 21 | suspend fun deleteProduct(product: ProductEntity): Int 22 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/datasource/local/db/entity/ProductEntity.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.datasource.local.db.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | 6 | /** Created by github.com/im-o on 12/27/2022. */ 7 | 8 | @Entity(tableName = "table_shop", primaryKeys = ["id"]) 9 | data class ProductEntity( 10 | @ColumnInfo(name = "id") 11 | val id: Int? = null, 12 | 13 | @ColumnInfo(name = "description") 14 | val description: String? = null, 15 | 16 | @ColumnInfo(name = "price") 17 | val price: Double? = null, 18 | 19 | @ColumnInfo(name = "thumbnail") 20 | val thumbnail: String? = null, 21 | 22 | @ColumnInfo(name = "title") 23 | val title: String? = null 24 | ) 25 | -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/datasource/remote/ApiService.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.datasource.remote 2 | 3 | import id.barakkastudio.core.data.model.Product 4 | import id.barakkastudio.core.data.model.ProductResponse 5 | import retrofit2.http.* 6 | 7 | /** Created by github.com/im-o on 10/1/2022. */ 8 | 9 | interface ApiService { 10 | 11 | @GET("products") 12 | suspend fun getProducts(): ProductResponse 13 | 14 | @GET("products/{id}") 15 | suspend fun getProductById( 16 | @Path("id") id: Int 17 | ): Product 18 | 19 | @GET("products/search") 20 | suspend fun searchProduct( 21 | @Query("q") query: String 22 | ): ProductResponse 23 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/model/Product.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.model 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Product( 7 | @SerializedName("brand") 8 | val brand: String? = null, 9 | @SerializedName("category") 10 | val category: String? = null, 11 | @SerializedName("description") 12 | val description: String? = null, 13 | @SerializedName("discountPercentage") 14 | val discountPercentage: Double? = null, 15 | @SerializedName("id") 16 | val id: Int? = null, 17 | @SerializedName("images") 18 | val images: List? = null, 19 | @SerializedName("price") 20 | val price: Double? = null, 21 | @SerializedName("rating") 22 | val rating: Double? = null, 23 | @SerializedName("stock") 24 | val stock: Int? = null, 25 | @SerializedName("thumbnail") 26 | val thumbnail: String? = null, 27 | @SerializedName("title") 28 | val title: String? = null 29 | ) -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/model/ProductResponse.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.model 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class ProductResponse( 7 | @SerializedName("limit") 8 | val limit: Int? = null, 9 | @SerializedName("products") 10 | val products: MutableList? = null, 11 | @SerializedName("skip") 12 | val skip: Int? = null, 13 | @SerializedName("total") 14 | val total: Int? = null 15 | ) -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/model/mapper/ProductMapper.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.model.mapper 2 | 3 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 4 | import id.barakkastudio.core.data.model.Product 5 | 6 | /** Created by github.com/im-o on 12/27/2022. */ 7 | 8 | object ProductMapper { 9 | fun mapFromProductToEntity(product: Product) = ProductEntity( 10 | product.id, product.description, product.price, product.thumbnail, product.title 11 | ) 12 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/repository/product/DbProductRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.repository.product 2 | 3 | import id.barakkastudio.core.data.datasource.local.db.AppDatabase 4 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 5 | import id.barakkastudio.core.domain.repository.product.DbProductRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.flowOf 8 | import javax.inject.Inject 9 | import javax.inject.Singleton 10 | 11 | /** Created by github.com/im-o on 12/27/2022. */ 12 | 13 | @Singleton 14 | class DbProductRepositoryImpl @Inject constructor( 15 | private val db: AppDatabase 16 | ) : DbProductRepository { 17 | 18 | override fun getProductsDb(): Flow> { 19 | return flowOf(db.productDao().getProducts()) 20 | } 21 | 22 | override fun getProductByIdDb(id: Long): Flow { 23 | return flowOf(db.productDao().getProductById(id)) 24 | } 25 | 26 | override suspend fun insertProductDb(product: ProductEntity): Long { 27 | return db.productDao().insertProduct(product) 28 | } 29 | 30 | override suspend fun deleteProductDb(product: ProductEntity): Int { 31 | return db.productDao().deleteProduct(product) 32 | } 33 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/data/repository/product/ProductRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.repository.product 2 | 3 | import id.barakkastudio.core.data.datasource.remote.ApiService 4 | import id.barakkastudio.core.data.model.Product 5 | import id.barakkastudio.core.data.model.ProductResponse 6 | import id.barakkastudio.core.domain.repository.product.ProductRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import kotlinx.coroutines.flow.flowOf 10 | import javax.inject.Inject 11 | import javax.inject.Singleton 12 | 13 | /** Created by github.com/im-o on 12/16/2022. */ 14 | 15 | @Singleton 16 | class ProductRepositoryImpl @Inject constructor( 17 | private val apiService: ApiService 18 | ) : ProductRepository { 19 | 20 | override fun getProductsApiCall(): Flow { // this is sample not using `suspend` 21 | return flow { 22 | emit(apiService.getProducts()) 23 | } 24 | } 25 | 26 | override fun getProductByIdApiCall(id: Int): Flow { 27 | return flow { 28 | emit(apiService.getProductById(id)) 29 | } 30 | } 31 | 32 | override suspend fun searchProductApiCall(query: String): Flow { 33 | return flowOf(apiService.searchProduct(query)) 34 | } 35 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | import id.barakkastudio.core.data.datasource.local.db.AppDatabase 11 | import id.barakkastudio.core.util.UtilConstants.DB_JET_SHOPEE 12 | import javax.inject.Singleton 13 | 14 | /** Created by github.com/im-o on 12/27/2022. */ 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object DatabaseModule { 19 | 20 | @Provides 21 | @Singleton 22 | fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase { 23 | return Room.databaseBuilder(context, AppDatabase::class.java, DB_JET_SHOPEE).build() 24 | } 25 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.di 2 | 3 | import com.google.gson.GsonBuilder 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | import id.barakkastudio.core.BuildConfig 9 | import id.barakkastudio.core.data.datasource.remote.ApiService 10 | import id.barakkastudio.core.network.interceptor.HttpRequestInterceptor 11 | import okhttp3.OkHttpClient 12 | import okhttp3.logging.HttpLoggingInterceptor 13 | import retrofit2.Retrofit 14 | import retrofit2.converter.gson.GsonConverterFactory 15 | import javax.inject.Singleton 16 | 17 | /** Created by github.com/im-o on 12/16/2022. */ 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | object NetworkModule { 22 | 23 | @Provides 24 | @Singleton 25 | fun provideLoggingInterceptor(): HttpLoggingInterceptor { 26 | return if (BuildConfig.DEBUG) HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) 27 | else HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.NONE) 28 | } 29 | 30 | @Provides 31 | @Singleton 32 | fun provideOkHttpClient(logging: HttpLoggingInterceptor): OkHttpClient { 33 | return OkHttpClient.Builder() 34 | .addInterceptor(logging) 35 | .addInterceptor(HttpRequestInterceptor()) 36 | .build() 37 | } 38 | 39 | @Provides 40 | @Singleton 41 | fun provideConverterFactory(): GsonConverterFactory { 42 | return GsonConverterFactory.create(GsonBuilder().create()) 43 | } 44 | 45 | @Provides 46 | @Singleton 47 | fun provideRetrofit(converterFactory: GsonConverterFactory, okHttpClient: OkHttpClient): Retrofit { 48 | return Retrofit.Builder() 49 | .baseUrl("https://dummyjson.com/") 50 | .addConverterFactory(converterFactory) 51 | .client(okHttpClient) 52 | .build() 53 | } 54 | 55 | @Provides 56 | @Singleton 57 | fun provideApiService(retrofit: Retrofit): ApiService { 58 | return retrofit.create(ApiService::class.java) 59 | } 60 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.di 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import id.barakkastudio.core.data.datasource.local.db.AppDatabase 8 | import id.barakkastudio.core.data.datasource.remote.ApiService 9 | import id.barakkastudio.core.data.repository.product.DbProductRepositoryImpl 10 | import id.barakkastudio.core.data.repository.product.ProductRepositoryImpl 11 | import id.barakkastudio.core.domain.repository.product.DbProductRepository 12 | import id.barakkastudio.core.domain.repository.product.ProductRepository 13 | import javax.inject.Singleton 14 | 15 | /** Created by github.com/im-o on 12/17/2022. */ 16 | 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | object RepositoryModule { 20 | 21 | @Provides 22 | @Singleton 23 | fun provideProductRepository(apiService: ApiService): ProductRepository { 24 | return ProductRepositoryImpl(apiService) 25 | } 26 | 27 | @Provides 28 | @Singleton 29 | fun provideDbProductRepository(db: AppDatabase): DbProductRepository { 30 | return DbProductRepositoryImpl(db) 31 | } 32 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/di/usecase/ProductUseCaseModule.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.di.usecase 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.android.components.ViewModelComponent 7 | import id.barakkastudio.core.domain.repository.product.DbProductRepository 8 | import id.barakkastudio.core.domain.repository.product.ProductRepository 9 | import id.barakkastudio.core.domain.usecase.product.GetProductByIdUseCase 10 | import id.barakkastudio.core.domain.usecase.product.GetProductsUseCase 11 | import id.barakkastudio.core.domain.usecase.product.SearchProductUseCase 12 | import id.barakkastudio.core.domain.usecase.product.db.DeleteProductDbUseCase 13 | import id.barakkastudio.core.domain.usecase.product.db.GetProductByIdDbUseCase 14 | import id.barakkastudio.core.domain.usecase.product.db.GetProductsDbUseCase 15 | import id.barakkastudio.core.domain.usecase.product.db.InsertProductDbUseCase 16 | 17 | /** Created by github.com/im-o on 5/1/2023. */ 18 | 19 | @Module 20 | @InstallIn(ViewModelComponent::class) 21 | object ProductUseCaseModule { 22 | 23 | @Provides 24 | fun provideGetProductUseCase(productRepository: ProductRepository): GetProductsUseCase { 25 | return GetProductsUseCase(productRepository) 26 | } 27 | 28 | @Provides 29 | fun provideSearchProductUseCase(productRepository: ProductRepository): SearchProductUseCase { 30 | return SearchProductUseCase(productRepository) 31 | } 32 | 33 | @Provides 34 | fun provideGetProductByIdUseCase(productRepository: ProductRepository): GetProductByIdUseCase { 35 | return GetProductByIdUseCase(productRepository) 36 | } 37 | 38 | @Provides 39 | fun provideGetProductByIdDbUseCase(dbProductRepository: DbProductRepository): GetProductByIdDbUseCase { 40 | return GetProductByIdDbUseCase(dbProductRepository) 41 | } 42 | 43 | @Provides 44 | fun provideGetProductsDbUseCase(dbProductRepository: DbProductRepository): GetProductsDbUseCase { 45 | return GetProductsDbUseCase(dbProductRepository) 46 | } 47 | 48 | @Provides 49 | fun provideInsertProductDbUseCase(dbProductRepository: DbProductRepository): InsertProductDbUseCase { 50 | return InsertProductDbUseCase(dbProductRepository) 51 | } 52 | 53 | @Provides 54 | fun provideDeleteProductDbUseCase(dbProductRepository: DbProductRepository): DeleteProductDbUseCase { 55 | return DeleteProductDbUseCase(dbProductRepository) 56 | } 57 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/repository/product/DbProductRepository.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.repository.product 2 | 3 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | /** Created by github.com/im-o on 12/27/2022. */ 7 | 8 | interface DbProductRepository { 9 | fun getProductsDb(): Flow> 10 | fun getProductByIdDb(id: Long): Flow 11 | suspend fun insertProductDb(product: ProductEntity): Long 12 | suspend fun deleteProductDb(product: ProductEntity): Int 13 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/repository/product/ProductRepository.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.repository.product 2 | 3 | import id.barakkastudio.core.data.model.Product 4 | import id.barakkastudio.core.data.model.ProductResponse 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | /** Created by github.com/im-o on 12/16/2022. */ 8 | 9 | interface ProductRepository { 10 | fun getProductsApiCall(): Flow // this is sample not using `suspend` 11 | fun getProductByIdApiCall(id: Int): Flow 12 | suspend fun searchProductApiCall(query: String): Flow // this is sample using `suspend` 13 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/usecase/BaseUseCase.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase 2 | 3 | abstract class BaseUseCase { 4 | abstract fun execute(params: Params): T 5 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/usecase/BaseUseCaseSuspend.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase 2 | 3 | abstract class BaseUseCaseSuspend { 4 | abstract suspend fun execute(params: Params): T 5 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/usecase/product/GetProductByIdUseCase.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase.product 2 | 3 | import id.barakkastudio.core.data.model.Product 4 | import id.barakkastudio.core.domain.repository.product.ProductRepository 5 | import id.barakkastudio.core.domain.usecase.BaseUseCase 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetProductByIdUseCase @Inject constructor( 10 | private val productRepository: ProductRepository 11 | ) : BaseUseCase>() { 12 | override fun execute(params: Int): Flow { 13 | return productRepository.getProductByIdApiCall(params) 14 | } 15 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/usecase/product/GetProductsUseCase.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase.product 2 | 3 | import id.barakkastudio.core.data.model.ProductResponse 4 | import id.barakkastudio.core.domain.repository.product.ProductRepository 5 | import id.barakkastudio.core.domain.usecase.BaseUseCase 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetProductsUseCase @Inject constructor( 10 | private val productRepository: ProductRepository 11 | ) : BaseUseCase>() { 12 | override fun execute(params: Unit): Flow { 13 | return productRepository.getProductsApiCall() 14 | } 15 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/usecase/product/SearchProductUseCase.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase.product 2 | 3 | import id.barakkastudio.core.data.model.ProductResponse 4 | import id.barakkastudio.core.domain.repository.product.ProductRepository 5 | import id.barakkastudio.core.domain.usecase.BaseUseCaseSuspend 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class SearchProductUseCase @Inject constructor( 10 | private val productRepository: ProductRepository 11 | ) : BaseUseCaseSuspend>() { 12 | override suspend fun execute(params: String): Flow { 13 | return productRepository.searchProductApiCall(params) 14 | } 15 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/usecase/product/db/DeleteProductDbUseCase.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase.product.db 2 | 3 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 4 | import id.barakkastudio.core.domain.repository.product.DbProductRepository 5 | import id.barakkastudio.core.domain.usecase.BaseUseCaseSuspend 6 | import javax.inject.Inject 7 | 8 | /** Created by github.com/im-o on 5/2/2023. */ 9 | 10 | class DeleteProductDbUseCase @Inject constructor( 11 | private val dbProductRepository: DbProductRepository 12 | ) : BaseUseCaseSuspend() { 13 | override suspend fun execute(params: ProductEntity): Int { 14 | return dbProductRepository.deleteProductDb(params) 15 | } 16 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/usecase/product/db/GetProductByIdDbUseCase.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase.product.db 2 | 3 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 4 | import id.barakkastudio.core.domain.repository.product.DbProductRepository 5 | import id.barakkastudio.core.domain.usecase.BaseUseCase 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | /** Created by github.com/im-o on 5/2/2023. */ 10 | 11 | class GetProductByIdDbUseCase @Inject constructor( 12 | private val dbProductRepository: DbProductRepository 13 | ) : BaseUseCase>() { 14 | override fun execute(params: Long): Flow { 15 | return dbProductRepository.getProductByIdDb(params) 16 | } 17 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/usecase/product/db/GetProductsDbUseCase.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase.product.db 2 | 3 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 4 | import id.barakkastudio.core.domain.repository.product.DbProductRepository 5 | import id.barakkastudio.core.domain.usecase.BaseUseCase 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | /** Created by github.com/im-o on 5/2/2023. */ 10 | 11 | class GetProductsDbUseCase @Inject constructor( 12 | private val dbProductRepository: DbProductRepository 13 | ) : BaseUseCase>>() { 14 | override fun execute(params: Unit): Flow> { 15 | return dbProductRepository.getProductsDb() 16 | } 17 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/domain/usecase/product/db/InsertProductDbUseCase.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase.product.db 2 | 3 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 4 | import id.barakkastudio.core.domain.repository.product.DbProductRepository 5 | import id.barakkastudio.core.domain.usecase.BaseUseCaseSuspend 6 | import javax.inject.Inject 7 | 8 | /** Created by github.com/im-o on 5/2/2023. */ 9 | 10 | class InsertProductDbUseCase @Inject constructor( 11 | private val dbProductRepository: DbProductRepository 12 | ) : BaseUseCaseSuspend() { 13 | override suspend fun execute(params: ProductEntity): Long { 14 | return dbProductRepository.insertProductDb(params) 15 | } 16 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/network/interceptor/HttpRequestInterceptor.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.network.interceptor 2 | 3 | import id.barakkastudio.core.util.UtilFunctions.logE 4 | import okhttp3.Interceptor 5 | import okhttp3.Response 6 | 7 | /** Created by github.com/im-o on 12/16/2022. */ 8 | 9 | internal class HttpRequestInterceptor : Interceptor { 10 | override fun intercept(chain: Interceptor.Chain): Response { 11 | val originalRequest = chain.request() 12 | val request = originalRequest.newBuilder().url(originalRequest.url).build() 13 | logE("HttpRequestInterceptor : $request") 14 | return chain.proceed(request) 15 | } 16 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/ui/component/molecules/SearchBar.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.ui.component.molecules 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.border 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.heightIn 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.layout.size 11 | import androidx.compose.foundation.shape.RoundedCornerShape 12 | import androidx.compose.foundation.text.KeyboardActions 13 | import androidx.compose.foundation.text.KeyboardOptions 14 | import androidx.compose.material.icons.Icons 15 | import androidx.compose.material.icons.filled.Clear 16 | import androidx.compose.material.icons.filled.Search 17 | import androidx.compose.material3.Icon 18 | import androidx.compose.material3.IconButton 19 | import androidx.compose.material3.MaterialTheme 20 | import androidx.compose.material3.Text 21 | import androidx.compose.material3.TextField 22 | import androidx.compose.material3.TextFieldDefaults 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.runtime.getValue 25 | import androidx.compose.runtime.mutableStateOf 26 | import androidx.compose.runtime.remember 27 | import androidx.compose.runtime.setValue 28 | import androidx.compose.ui.Modifier 29 | import androidx.compose.ui.draw.clip 30 | import androidx.compose.ui.focus.FocusRequester 31 | import androidx.compose.ui.focus.focusRequester 32 | import androidx.compose.ui.focus.onFocusChanged 33 | import androidx.compose.ui.graphics.Color 34 | import androidx.compose.ui.res.stringResource 35 | import androidx.compose.ui.text.TextStyle 36 | import androidx.compose.ui.text.input.ImeAction 37 | import androidx.compose.ui.text.input.KeyboardType 38 | import id.barakkastudio.core.R 39 | import id.barakkastudio.core.util.Dimens 40 | 41 | /** Created by github.com/im-o on 12/27/2022. */ 42 | 43 | @Composable 44 | fun SearchBar( 45 | query: String, 46 | modifier: Modifier = Modifier, 47 | isEnabled: (Boolean) = true, 48 | onSearchClicked: () -> Unit = {}, 49 | onQueryChange: (String) -> Unit = {}, 50 | ) { 51 | var isTextFieldFocused by remember { mutableStateOf(false) } 52 | 53 | Box( 54 | modifier = modifier 55 | .fillMaxWidth() 56 | .padding(start = Dimens.dp16, end = Dimens.dp16, top = Dimens.dp8, bottom = Dimens.dp8) 57 | .clickable { onSearchClicked() } 58 | ) { 59 | TextField( 60 | value = query, 61 | onValueChange = onQueryChange, 62 | enabled = isEnabled, 63 | modifier = modifier 64 | .focusRequester(FocusRequester()) 65 | .onFocusChanged { isTextFieldFocused = it.isFocused } 66 | .fillMaxWidth() 67 | .heightIn(min = Dimens.dp48, max = Dimens.dp48) 68 | .clip(shape = RoundedCornerShape(Dimens.dp8)) 69 | .border( 70 | border = BorderStroke(Dimens.dp1, MaterialTheme.colorScheme.primary.copy(alpha = 0.4f)), 71 | shape = RoundedCornerShape(Dimens.dp8) 72 | ), 73 | textStyle = TextStyle(fontSize = Dimens.sp14), 74 | leadingIcon = { 75 | Icon( 76 | imageVector = Icons.Default.Search, 77 | contentDescription = null, 78 | modifier = Modifier.size(Dimens.dp20), 79 | ) 80 | }, 81 | trailingIcon = { 82 | if (isTextFieldFocused && query.isNotEmpty()) { 83 | IconButton(onClick = { onQueryChange("") }) { 84 | Icon( 85 | imageVector = Icons.Filled.Clear, 86 | contentDescription = stringResource(R.string.clear), 87 | modifier = Modifier.size(Dimens.dp20), 88 | ) 89 | } 90 | } 91 | }, 92 | colors = TextFieldDefaults.colors( 93 | focusedContainerColor = MaterialTheme.colorScheme.surface, 94 | focusedIndicatorColor = Color.Transparent, 95 | focusedTextColor = MaterialTheme.colorScheme.onSurface, 96 | disabledIndicatorColor = Color.Transparent, 97 | unfocusedIndicatorColor = Color.Transparent, 98 | ), 99 | placeholder = { 100 | Text( 101 | text = stringResource(R.string.search_product), 102 | fontSize = Dimens.sp14, 103 | ) 104 | }, 105 | keyboardOptions = KeyboardOptions( 106 | keyboardType = KeyboardType.Text, 107 | imeAction = ImeAction.Search 108 | ), 109 | keyboardActions = KeyboardActions(onSearch = {}), 110 | ) 111 | } 112 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/ui/template/DetailTemplate.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.ui.template 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.PaddingValues 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.height 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.Scaffold 12 | import androidx.compose.material3.Surface 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.unit.dp 17 | import androidx.compose.ui.zIndex 18 | 19 | /** Created by github.com/im-o on 5/10/2023. */ 20 | 21 | @Composable 22 | fun DetailTemplate( 23 | modifier: Modifier = Modifier, 24 | topBar: @Composable () -> Unit = {}, 25 | bottomBar: @Composable () -> Unit = {}, 26 | content: @Composable (PaddingValues) -> Unit 27 | ) { 28 | Scaffold( 29 | topBar = { 30 | topBar() 31 | }, 32 | bottomBar = { 33 | bottomBar() 34 | } 35 | ) { innerPadding -> 36 | Surface( 37 | modifier = modifier 38 | .padding(innerPadding) 39 | .fillMaxSize(), 40 | color = MaterialTheme.colorScheme.background 41 | ) { 42 | Box( 43 | modifier = Modifier 44 | .fillMaxSize() 45 | .padding(bottom = 56.dp), 46 | contentAlignment = Alignment.BottomCenter 47 | ) { 48 | content(innerPadding) 49 | } 50 | 51 | // Add a fixed bottom bar with elevation 52 | Surface( 53 | modifier = Modifier 54 | .fillMaxWidth() 55 | .height(56.dp) 56 | .padding(bottom = 8.dp) 57 | .zIndex(1f) 58 | .background(MaterialTheme.colorScheme.surface), 59 | shadowElevation = 8.dp, 60 | color = MaterialTheme.colorScheme.surface, 61 | ) { 62 | // Add bottom bar content here 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/ui/template/MainTemplate.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.ui.template 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Scaffold 8 | import androidx.compose.material3.Surface 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | 12 | /** Created by github.com/im-o on 5/10/2023. */ 13 | 14 | @Composable 15 | fun MainTemplate( 16 | modifier: Modifier = Modifier, 17 | topBar: @Composable () -> Unit = {}, 18 | bottomBar: @Composable () -> Unit = {}, 19 | content: @Composable (PaddingValues) -> Unit 20 | ) { 21 | Scaffold( 22 | topBar = { 23 | topBar() 24 | }, 25 | bottomBar = { 26 | bottomBar() 27 | }, 28 | containerColor = MaterialTheme.colorScheme.primary, 29 | ) { innerPadding -> 30 | Surface( 31 | modifier = modifier 32 | .padding(innerPadding) 33 | .fillMaxSize(), 34 | color = MaterialTheme.colorScheme.background 35 | ) { 36 | content(innerPadding) 37 | } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Gray200 = Color(0xFFF0F0F0) 6 | 7 | val md_theme_light_primary = Color(0xFF006879) 8 | val md_theme_light_onPrimary = Color(0xFFFFFFFF) 9 | val md_theme_light_primaryContainer = Color(0xFFA9EDFF) 10 | val md_theme_light_onPrimaryContainer = Color(0xFF001F26) 11 | val md_theme_light_secondary = Color(0xFF4B6268) 12 | val md_theme_light_onSecondary = Color(0xFFFFFFFF) 13 | val md_theme_light_secondaryContainer = Color(0xFFCEE7EE) 14 | val md_theme_light_onSecondaryContainer = Color(0xFF061F24) 15 | val md_theme_light_tertiary = Color(0xFF565D7E) 16 | val md_theme_light_onTertiary = Color(0xFFFFFFFF) 17 | val md_theme_light_tertiaryContainer = Color(0xFFDDE1FF) 18 | val md_theme_light_onTertiaryContainer = Color(0xFF121A37) 19 | val md_theme_light_error = Color(0xFFBA1A1A) 20 | val md_theme_light_errorContainer = Color(0xFFFFDAD6) 21 | val md_theme_light_onError = Color(0xFFFFFFFF) 22 | val md_theme_light_onErrorContainer = Color(0xFF410002) 23 | val md_theme_light_background = Color(0xFFFBFCFD) 24 | val md_theme_light_onBackground = Color(0xFF191C1D) 25 | val md_theme_light_surface = Color(0xFFFBFCFD) 26 | val md_theme_light_onSurface = Color(0xFF191C1D) 27 | val md_theme_light_surfaceVariant = Color(0xFFDBE4E7) 28 | val md_theme_light_onSurfaceVariant = Color(0xFF3F484B) 29 | val md_theme_light_outline = Color(0xFF6F797B) 30 | val md_theme_light_inverseOnSurface = Color(0xFFEFF1F2) 31 | val md_theme_light_inverseSurface = Color(0xFF2E3132) 32 | val md_theme_light_inversePrimary = Color(0xFF54D7F3) 33 | val md_theme_light_surfaceTint = Color(0xFF006879) 34 | val md_theme_light_outlineVariant = Color(0xFFBFC8CB) 35 | val md_theme_light_scrim = Color(0xFF000000) 36 | 37 | val md_theme_dark_primary = Color(0xFF54D7F3) 38 | val md_theme_dark_onPrimary = Color(0xFF003640) 39 | val md_theme_dark_primaryContainer = Color(0xFF004E5B) 40 | val md_theme_dark_onPrimaryContainer = Color(0xFFA9EDFF) 41 | val md_theme_dark_secondary = Color(0xFFB2CBD2) 42 | val md_theme_dark_onSecondary = Color(0xFF1C343A) 43 | val md_theme_dark_secondaryContainer = Color(0xFF334A50) 44 | val md_theme_dark_onSecondaryContainer = Color(0xFFCEE7EE) 45 | val md_theme_dark_tertiary = Color(0xFFBEC5EB) 46 | val md_theme_dark_onTertiary = Color(0xFF282F4D) 47 | val md_theme_dark_tertiaryContainer = Color(0xFF3E4565) 48 | val md_theme_dark_onTertiaryContainer = Color(0xFFDDE1FF) 49 | val md_theme_dark_error = Color(0xFFFFB4AB) 50 | val md_theme_dark_errorContainer = Color(0xFF93000A) 51 | val md_theme_dark_onError = Color(0xFF690005) 52 | val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) 53 | val md_theme_dark_background = Color(0xFF191C1D) 54 | val md_theme_dark_onBackground = Color(0xFFE1E3E4) 55 | val md_theme_dark_surface = Color(0xFF191C1D) 56 | val md_theme_dark_onSurface = Color(0xFFE1E3E4) 57 | val md_theme_dark_surfaceVariant = Color(0xFF3F484B) 58 | val md_theme_dark_onSurfaceVariant = Color(0xFFBFC8CB) 59 | val md_theme_dark_outline = Color(0xFF899295) 60 | val md_theme_dark_inverseOnSurface = Color(0xFF191C1D) 61 | val md_theme_dark_inverseSurface = Color(0xFFE1E3E4) 62 | val md_theme_dark_inversePrimary = Color(0xFF006879) 63 | val md_theme_dark_surfaceTint = Color(0xFF54D7F3) 64 | val md_theme_dark_outlineVariant = Color(0xFF3F484B) 65 | val md_theme_dark_scrim = Color(0xFF000000) 66 | -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.SideEffect 13 | import androidx.compose.ui.graphics.toArgb 14 | import androidx.compose.ui.platform.LocalContext 15 | import androidx.compose.ui.platform.LocalView 16 | import androidx.core.view.WindowCompat 17 | 18 | private val LightColorScheme = lightColorScheme( 19 | primary = md_theme_light_primary, 20 | onPrimary = md_theme_light_onPrimary, 21 | primaryContainer = md_theme_light_primaryContainer, 22 | onPrimaryContainer = md_theme_light_onPrimaryContainer, 23 | secondary = md_theme_light_secondary, 24 | onSecondary = md_theme_light_onSecondary, 25 | secondaryContainer = md_theme_light_secondaryContainer, 26 | onSecondaryContainer = md_theme_light_onSecondaryContainer, 27 | tertiary = md_theme_light_tertiary, 28 | onTertiary = md_theme_light_onTertiary, 29 | tertiaryContainer = md_theme_light_tertiaryContainer, 30 | onTertiaryContainer = md_theme_light_onTertiaryContainer, 31 | error = md_theme_light_error, 32 | errorContainer = md_theme_light_errorContainer, 33 | onError = md_theme_light_onError, 34 | onErrorContainer = md_theme_light_onErrorContainer, 35 | background = md_theme_light_background, 36 | onBackground = md_theme_light_onBackground, 37 | surface = md_theme_light_surface, 38 | onSurface = md_theme_light_onSurface, 39 | surfaceVariant = md_theme_light_surfaceVariant, 40 | onSurfaceVariant = md_theme_light_onSurfaceVariant, 41 | outline = md_theme_light_outline, 42 | inverseOnSurface = md_theme_light_inverseOnSurface, 43 | inverseSurface = md_theme_light_inverseSurface, 44 | inversePrimary = md_theme_light_inversePrimary, 45 | surfaceTint = md_theme_light_surfaceTint, 46 | outlineVariant = md_theme_light_outlineVariant, 47 | scrim = md_theme_light_scrim, 48 | ) 49 | 50 | private val DarkColorScheme = darkColorScheme( 51 | primary = md_theme_dark_primary, 52 | onPrimary = md_theme_dark_onPrimary, 53 | primaryContainer = md_theme_dark_primaryContainer, 54 | onPrimaryContainer = md_theme_dark_onPrimaryContainer, 55 | secondary = md_theme_dark_secondary, 56 | onSecondary = md_theme_dark_onSecondary, 57 | secondaryContainer = md_theme_dark_secondaryContainer, 58 | onSecondaryContainer = md_theme_dark_onSecondaryContainer, 59 | tertiary = md_theme_dark_tertiary, 60 | onTertiary = md_theme_dark_onTertiary, 61 | tertiaryContainer = md_theme_dark_tertiaryContainer, 62 | onTertiaryContainer = md_theme_dark_onTertiaryContainer, 63 | error = md_theme_dark_error, 64 | errorContainer = md_theme_dark_errorContainer, 65 | onError = md_theme_dark_onError, 66 | onErrorContainer = md_theme_dark_onErrorContainer, 67 | background = md_theme_dark_background, 68 | onBackground = md_theme_dark_onBackground, 69 | surface = md_theme_dark_surface, 70 | onSurface = md_theme_dark_onSurface, 71 | surfaceVariant = md_theme_dark_surfaceVariant, 72 | onSurfaceVariant = md_theme_dark_onSurfaceVariant, 73 | outline = md_theme_dark_outline, 74 | inverseOnSurface = md_theme_dark_inverseOnSurface, 75 | inverseSurface = md_theme_dark_inverseSurface, 76 | inversePrimary = md_theme_dark_inversePrimary, 77 | surfaceTint = md_theme_dark_surfaceTint, 78 | outlineVariant = md_theme_dark_outlineVariant, 79 | scrim = md_theme_dark_scrim, 80 | ) 81 | 82 | @Composable 83 | fun JetShopeeTheme( 84 | darkTheme: Boolean = isSystemInDarkTheme(), 85 | // Dynamic color is available on Android 12+ 86 | dynamicColor: Boolean = false, 87 | content: @Composable () -> Unit 88 | ) { 89 | val colorScheme = when { 90 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 91 | val context = LocalContext.current 92 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 93 | } 94 | 95 | darkTheme -> DarkColorScheme 96 | else -> LightColorScheme 97 | } 98 | val view = LocalView.current 99 | if (!view.isInEditMode) { 100 | SideEffect { 101 | val window = (view.context as Activity).window 102 | window.statusBarColor = colorScheme.primary.toArgb() 103 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme 104 | } 105 | } 106 | 107 | MaterialTheme( 108 | colorScheme = colorScheme, 109 | typography = Typography, 110 | content = content 111 | ) 112 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/util/Dimens.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.util 2 | 3 | import androidx.compose.ui.unit.Dp 4 | import androidx.compose.ui.unit.TextUnit 5 | import androidx.compose.ui.unit.dp 6 | import androidx.compose.ui.unit.sp 7 | 8 | /** Created by github.com/im-o on 5/12/2023. */ 9 | 10 | object Dimens { 11 | val dp0: Dp = 0.dp 12 | val dp1: Dp = 1.dp 13 | val dp3: Dp = 3.dp 14 | val dp8: Dp = 8.dp 15 | val dp10: Dp = 10.dp 16 | val dp12: Dp = 12.dp 17 | val dp16: Dp = 16.dp 18 | val dp20: Dp = 20.dp 19 | val dp24: Dp = 24.dp 20 | val dp32: Dp = 32.dp 21 | val dp42: Dp = 42.dp 22 | val dp48: Dp = 48.dp 23 | val dp60: Dp = 60.dp 24 | val dp120: Dp = 120.dp 25 | 26 | val sp14: TextUnit = 14.sp 27 | val sp18: TextUnit = 18.sp 28 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.util 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | 6 | /** Created by github.com/im-o on 5/12/2023. */ 7 | 8 | object Extensions { 9 | fun Context.myToast(message: String) { 10 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 11 | } 12 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/util/UtilConstants.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.util 2 | 3 | /** Created by github.com/im-o on 12/27/2022. */ 4 | 5 | object UtilConstants { 6 | const val DB_JET_SHOPEE = "db_nebenginaja.db" 7 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/util/UtilFunctions.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.util 2 | 3 | import android.util.Log 4 | import id.barakkastudio.core.BuildConfig 5 | import java.text.NumberFormat 6 | import java.util.Locale 7 | 8 | /** Created by github.com/im-o on 12/1/2022. */ 9 | 10 | object UtilFunctions { 11 | private val localeID = Locale("in", "ID") 12 | 13 | fun logE(message: String) { 14 | if (BuildConfig.DEBUG) Log.e("ERROR_IMO", message) 15 | } 16 | 17 | fun Double?.fromDollarToRupiah(): String { 18 | val localId = localeID 19 | val formatter = NumberFormat.getCurrencyInstance(localId) 20 | val fakeDollarToday = 15000.0 21 | val intValue = (this ?: 0.0) * fakeDollarToday 22 | return when { 23 | intValue > 0 -> formatter.format(intValue).replace(",00", "") 24 | intValue < 0 -> formatter.format(intValue).replace(",00", "") 25 | else -> "Rp0" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/id/barakkastudio/core/util/UtilTests.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.util 2 | 3 | import id.barakkastudio.core.data.model.Product 4 | import id.barakkastudio.core.data.model.ProductResponse 5 | 6 | /** Created by github.com/im-o on 5/3/2023. */ 7 | 8 | object UtilTests { 9 | val dummyProduct = Product("Product", "Product 1") 10 | val dummyProductResponse = ProductResponse(0, mutableListOf(dummyProduct)) 11 | } -------------------------------------------------------------------------------- /core/src/main/res/drawable/cart.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/home.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/logo_nebenginaja.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/core/src/main/res/drawable/logo_nebenginaja.webp -------------------------------------------------------------------------------- /core/src/main/res/drawable/rival_profile.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/core/src/main/res/drawable/rival_profile.webp -------------------------------------------------------------------------------- /core/src/main/res/drawable/user.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/src/main/res/values/string.xml: -------------------------------------------------------------------------------- 1 | 2 | NebenginAja 3 | Home 4 | Cart 5 | Profile 6 | Product Thumbnail 7 | No Product List 8 | Load Product… 9 | Can\'t load product, try again. 10 | Detail Product 11 | Description 12 | Clear 13 | - 14 | Search Product… 15 | BUY 16 | Add to Cart 17 | Added to cart! 18 | Thanks for buying 🎉 19 | Remove %s from cart. 20 | about_page 21 | Rivaldy 22 | Exploring the limitless possibilities of mobile development through the power of Jetpack Compose and the Flutter Framework 🚀 23 | https://github.com/im-o 24 | Back 25 | -------------------------------------------------------------------------------- /core/src/test/java/id/barakkastudio/core/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /core/src/test/java/id/barakkastudio/core/data/repository/product/ProductRepositoryImplTest.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.data.repository.product 2 | 3 | import id.barakkastudio.core.data.datasource.remote.ApiService 4 | import id.barakkastudio.core.util.UtilTests 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi 6 | import kotlinx.coroutines.flow.first 7 | import kotlinx.coroutines.test.runTest 8 | import org.junit.Assert.assertEquals 9 | import org.junit.Assert.assertTrue 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.junit.runner.RunWith 13 | import org.mockito.Mock 14 | import org.mockito.junit.MockitoJUnitRunner 15 | import org.mockito.kotlin.given 16 | import org.mockito.kotlin.whenever 17 | 18 | /** Created by github.com/im-o on 5/3/2023. */ 19 | @ExperimentalCoroutinesApi 20 | @RunWith(MockitoJUnitRunner::class) 21 | class ProductRepositoryImplTest { 22 | 23 | @Mock 24 | private lateinit var apiService: ApiService 25 | private lateinit var productRepositoryImpl: ProductRepositoryImpl 26 | 27 | @Before 28 | fun setUp() { 29 | productRepositoryImpl = ProductRepositoryImpl(apiService) 30 | } 31 | 32 | @Test 33 | fun `getProductsApiCall should return the correct data`() = runTest { 34 | // Given 35 | val expectedResponse = UtilTests.dummyProductResponse 36 | whenever(apiService.getProducts()).thenReturn(expectedResponse) 37 | 38 | // When 39 | val actualResponse = productRepositoryImpl.getProductsApiCall().first() 40 | 41 | // Then 42 | assertEquals(expectedResponse, actualResponse) 43 | } 44 | 45 | @Test 46 | fun `getProductsApiCall should return error flow when an exception occurs`() = runTest { 47 | // Given 48 | val expectedException = RuntimeException("An error occurred") 49 | given(apiService.getProducts()).willThrow(expectedException) 50 | 51 | // When 52 | val actualException = runCatching { productRepositoryImpl.getProductsApiCall().first() } 53 | 54 | // Then 55 | assertTrue(actualException.isFailure) 56 | assertEquals(expectedException, actualException.exceptionOrNull()) 57 | } 58 | 59 | @Test 60 | fun `getProductByIdApiCall should return the correct data`() = runTest { 61 | // Given 62 | val expectedResponse = UtilTests.dummyProduct 63 | val productId = 1 64 | whenever(apiService.getProductById(productId)).thenReturn(expectedResponse) 65 | 66 | // When 67 | val actualResponse = productRepositoryImpl.getProductByIdApiCall(productId).first() 68 | 69 | // Then 70 | assertEquals(expectedResponse, actualResponse) 71 | } 72 | 73 | @Test 74 | fun `getProductByIdApiCall should return error flow when an exception occurs`() = runTest { 75 | // Given 76 | val expectedException = RuntimeException("An error occurred") 77 | val productId = 1 78 | given(apiService.getProductById(productId)).willThrow(expectedException) 79 | 80 | // When 81 | val actualException = runCatching { 82 | productRepositoryImpl.getProductByIdApiCall(productId).first() 83 | } 84 | 85 | // Then 86 | assertTrue(actualException.isFailure) 87 | assertEquals(expectedException, actualException.exceptionOrNull()) 88 | } 89 | 90 | @Test 91 | fun `searchProductApiCall should return the correct data`() = runTest { 92 | // Given 93 | val expectedResponse = UtilTests.dummyProductResponse 94 | val query = "test" 95 | whenever(apiService.searchProduct(query)).thenReturn(expectedResponse) 96 | 97 | // When 98 | val actualResponse = productRepositoryImpl.searchProductApiCall(query).first() 99 | 100 | // Then 101 | assertEquals(expectedResponse, actualResponse) 102 | } 103 | 104 | @Test 105 | fun `searchProductApiCall should return error flow when an exception occurs`() = runTest { 106 | // Given 107 | val expectedException = RuntimeException("An error occurred") 108 | val query = "test" 109 | given(apiService.searchProduct(query)).willThrow(expectedException) 110 | 111 | // When 112 | val actualException = runCatching { 113 | productRepositoryImpl.searchProductApiCall(query).first() 114 | } 115 | 116 | // Then 117 | assertTrue(actualException.isFailure) 118 | assertEquals(expectedException, actualException.exceptionOrNull()) 119 | } 120 | } -------------------------------------------------------------------------------- /core/src/test/java/id/barakkastudio/core/domain/usecase/product/GetProductsUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.core.domain.usecase.product 2 | 3 | import id.barakkastudio.core.data.model.Product 4 | import id.barakkastudio.core.data.model.ProductResponse 5 | import id.barakkastudio.core.domain.repository.product.ProductRepository 6 | import kotlinx.coroutines.ExperimentalCoroutinesApi 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.first 9 | import kotlinx.coroutines.flow.flow 10 | import kotlinx.coroutines.flow.flowOf 11 | import kotlinx.coroutines.test.runTest 12 | import org.junit.Assert.assertEquals 13 | import org.junit.Assert.assertTrue 14 | import org.junit.Before 15 | import org.junit.Test 16 | import org.junit.runner.RunWith 17 | import org.mockito.Mock 18 | import org.mockito.junit.MockitoJUnitRunner 19 | import org.mockito.kotlin.given 20 | import org.mockito.kotlin.whenever 21 | 22 | /** Created by github.com/im-o on 5/2/2023. */ 23 | 24 | @ExperimentalCoroutinesApi 25 | @RunWith(MockitoJUnitRunner::class) 26 | class GetProductsUseCaseTest { 27 | 28 | @Mock 29 | private lateinit var productRepository: ProductRepository 30 | private lateinit var getProductsUseCase: GetProductsUseCase 31 | 32 | @Before 33 | fun setUp() { 34 | getProductsUseCase = GetProductsUseCase(productRepository) 35 | } 36 | 37 | @Test 38 | fun `execute should return flow of product response`() = runTest { 39 | // given 40 | val productResponse = ProductResponse(0, mutableListOf(Product("Product", "Product 1"))) 41 | whenever(productRepository.getProductsApiCall()).thenReturn(flowOf(productResponse)) 42 | 43 | // when 44 | val result = getProductsUseCase.execute(Unit).first() 45 | 46 | // then 47 | assertEquals(productResponse, result) 48 | } 49 | 50 | @Test 51 | fun `execute should return error flow when an exception occurs`() = runTest { 52 | // Given 53 | val exception = Exception("An error occurred") 54 | val flow: Flow = flow { throw exception } 55 | given(productRepository.getProductsApiCall()).willReturn(flow) 56 | 57 | // When 58 | val result = runCatching { getProductsUseCase.execute(Unit).first() } 59 | 60 | // Then 61 | assertTrue(result.isFailure) 62 | assertEquals(exception, result.exceptionOrNull()) 63 | } 64 | } -------------------------------------------------------------------------------- /features/sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/sample/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dependencies.MyDependencies 2 | 3 | plugins { 4 | id("com.android.library") 5 | id("org.jetbrains.kotlin.android") 6 | id("dagger.hilt.android.plugin") 7 | id("kotlin-kapt") 8 | } 9 | 10 | @Suppress("UnstableApiUsage") 11 | android { 12 | namespace = "id.barakkastudio.sample" 13 | compileSdk = Versions.compile_sdk 14 | 15 | defaultConfig { 16 | minSdk = Versions.min_sdk 17 | targetSdk = Versions.target_sdk 18 | 19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 20 | consumerProguardFiles("consumer-rules.pro") 21 | } 22 | 23 | buildTypes { 24 | release { 25 | isMinifyEnabled = false 26 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility = JavaVersion.VERSION_11 31 | targetCompatibility = JavaVersion.VERSION_11 32 | } 33 | kotlinOptions { 34 | jvmTarget = JavaVersion.VERSION_11.toString() 35 | } 36 | buildFeatures { 37 | compose = true 38 | } 39 | composeOptions { 40 | kotlinCompilerExtensionVersion = Versions.compose_compiler 41 | } 42 | packagingOptions { 43 | resources { 44 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 45 | } 46 | } 47 | tasks.withType().configureEach { 48 | kotlinOptions { 49 | freeCompilerArgs = freeCompilerArgs + listOf( 50 | "-opt-in=kotlin.RequiresOptIn", 51 | "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", 52 | "-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi", 53 | ) 54 | } 55 | } 56 | } 57 | 58 | dependencies { 59 | implementation(project(Modules.core)) 60 | 61 | // TESTING 62 | testImplementation(MyDependencies.junit) 63 | androidTestImplementation(MyDependencies.test_ext_junit) 64 | androidTestImplementation(MyDependencies.espresso_core) 65 | androidTestImplementation(MyDependencies.junit_compose) 66 | debugImplementation(MyDependencies.ui_tooling) 67 | debugImplementation(MyDependencies.ui_test_manifest) 68 | 69 | // Hilt 70 | implementation(MyDependencies.hilt_android) 71 | kapt(MyDependencies.hilt_android_compiler) 72 | kapt(MyDependencies.hilt_compose_compiler) 73 | } -------------------------------------------------------------------------------- /features/sample/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/features/sample/consumer-rules.pro -------------------------------------------------------------------------------- /features/sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /features/sample/src/androidTest/java/id/barakkastudio/sample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("id.barakkastudio.sample.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /features/sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/cart/CartScreen.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.cart 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Scaffold 9 | import androidx.compose.material3.Text 10 | import androidx.compose.material3.TopAppBar 11 | import androidx.compose.material3.TopAppBarDefaults 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.collectAsState 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.res.stringResource 18 | import androidx.hilt.navigation.compose.hiltViewModel 19 | import id.barakkastudio.core.R 20 | import id.barakkastudio.core.data.UiState 21 | import id.barakkastudio.core.ui.theme.Gray200 22 | import id.barakkastudio.sample.ui.cart.section.CartContent 23 | import id.barakkastudio.sample.ui.component.ProgressProduct 24 | 25 | /** Created by github.com/im-o on 12/12/2022. */ 26 | 27 | @Composable 28 | fun CartScreen( 29 | viewModel: CartViewModel = hiltViewModel(), 30 | navigateToDetail: (Int) -> Unit, 31 | ) { 32 | Scaffold( 33 | topBar = { 34 | TopAppBar( 35 | title = { 36 | Text(text = stringResource(R.string.cart)) 37 | }, 38 | colors = TopAppBarDefaults.topAppBarColors( 39 | containerColor = MaterialTheme.colorScheme.primary, 40 | titleContentColor = Color.White, 41 | ), 42 | ) 43 | }, content = { 44 | Box( 45 | contentAlignment = Alignment.Center, 46 | modifier = Modifier 47 | .fillMaxSize() 48 | .background(Gray200) 49 | .padding(it) 50 | ) { 51 | viewModel.uiStateDbProducts.collectAsState(initial = UiState.Loading).value.let { uiState -> 52 | when (uiState) { 53 | is UiState.Loading -> { 54 | viewModel.getProductsDb() 55 | ProgressProduct() 56 | } 57 | 58 | is UiState.Success -> { 59 | CartContent(products = uiState.data, viewModel = viewModel, navigateToDetail = navigateToDetail) 60 | } 61 | 62 | is UiState.Error -> { 63 | Text(text = stringResource(R.string.error_product), color = MaterialTheme.colorScheme.onSurface) 64 | } 65 | } 66 | } 67 | } 68 | }) 69 | 70 | } 71 | -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/cart/CartViewModel.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.cart 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import id.barakkastudio.core.data.UiState 7 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 8 | import id.barakkastudio.core.domain.usecase.product.db.DeleteProductDbUseCase 9 | import id.barakkastudio.core.domain.usecase.product.db.GetProductsDbUseCase 10 | import kotlinx.coroutines.CoroutineDispatcher 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.flow.MutableStateFlow 13 | import kotlinx.coroutines.flow.StateFlow 14 | import kotlinx.coroutines.flow.catch 15 | import kotlinx.coroutines.launch 16 | import javax.inject.Inject 17 | 18 | /** Created by github.com/im-o on 12/16/2022. */ 19 | 20 | @HiltViewModel 21 | class CartViewModel @Inject constructor( 22 | private val getProductsDbUseCase: GetProductsDbUseCase, 23 | private val deleteProductDbUseCase: DeleteProductDbUseCase, 24 | ) : ViewModel() { 25 | 26 | private val _uiStateDbProducts: MutableStateFlow>> = MutableStateFlow(UiState.Loading) 27 | val uiStateDbProducts: StateFlow>> = _uiStateDbProducts 28 | 29 | fun getProductsDb(dispatcher: CoroutineDispatcher = Dispatchers.Default) { 30 | viewModelScope.launch(dispatcher) { 31 | try { 32 | getProductsDbUseCase.execute(Unit).catch { 33 | _uiStateDbProducts.value = UiState.Error(it.message.toString()) 34 | }.collect { product -> 35 | _uiStateDbProducts.value = UiState.Success(product) 36 | } 37 | } catch (e: Exception) { 38 | _uiStateDbProducts.value = UiState.Error(e.message.toString()) 39 | } 40 | } 41 | } 42 | 43 | fun deleteProductDb(product: ProductEntity) { 44 | viewModelScope.launch { 45 | val intDelete = deleteProductDbUseCase.execute(product) 46 | if (intDelete == 1) getProductsDb() 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/cart/section/CartContent.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.cart.section 2 | 3 | import androidx.compose.animation.core.tween 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.PaddingValues 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.lazy.LazyColumn 10 | import androidx.compose.foundation.lazy.items 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.platform.LocalContext 14 | import androidx.compose.ui.res.stringResource 15 | import androidx.compose.ui.unit.dp 16 | import id.barakkastudio.core.R 17 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 18 | import id.barakkastudio.core.util.Extensions.myToast 19 | import id.barakkastudio.sample.ui.cart.CartViewModel 20 | import id.barakkastudio.sample.ui.component.EmptyProduct 21 | import id.barakkastudio.sample.ui.component.ProductCartItem 22 | 23 | /** Created by github.com/im-o on 5/12/2023. */ 24 | 25 | @OptIn(ExperimentalFoundationApi::class) 26 | @Composable 27 | fun CartContent( 28 | products: MutableList, 29 | navigateToDetail: (Int) -> Unit, 30 | viewModel: CartViewModel 31 | ) { 32 | val context = LocalContext.current 33 | 34 | LazyColumn( 35 | modifier = Modifier.fillMaxSize(), 36 | content = { 37 | items(products, key = { it.id ?: -1 }) { product -> 38 | val strRemoveCart = stringResource(id = R.string.remove_from_cart_, product.title.toString()) 39 | ProductCartItem( 40 | modifier = Modifier 41 | .fillMaxWidth() 42 | .animateItemPlacement(tween(durationMillis = 100)) 43 | .clickable { 44 | navigateToDetail(product.id ?: return@clickable) 45 | }, 46 | product = product, 47 | onRemoveClicked = { 48 | viewModel.deleteProductDb(product) 49 | context.myToast(strRemoveCart) 50 | } 51 | ) 52 | } 53 | }, contentPadding = PaddingValues(8.dp) 54 | ) 55 | if (products.isEmpty()) EmptyProduct() 56 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/component/EmptyProduct.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.component 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.wrapContentWidth 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.res.stringResource 13 | import id.barakkastudio.core.R 14 | 15 | /** Created by github.com/im-o on 5/10/2023. */ 16 | 17 | @Composable 18 | fun EmptyProduct() { 19 | Box( 20 | contentAlignment = Alignment.Center, 21 | modifier = Modifier.fillMaxSize() 22 | ) { 23 | Text( 24 | text = stringResource(R.string.no_product), 25 | color = MaterialTheme.colorScheme.onSurface, 26 | modifier = Modifier 27 | .fillMaxWidth() 28 | .wrapContentWidth(Alignment.CenterHorizontally) 29 | ) 30 | } 31 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/component/ProductCartItem.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.component 2 | 3 | 4 | import android.graphics.Bitmap 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.Spacer 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.layout.size 11 | import androidx.compose.foundation.layout.wrapContentHeight 12 | import androidx.compose.foundation.shape.RoundedCornerShape 13 | import androidx.compose.material.icons.Icons 14 | import androidx.compose.material.icons.outlined.Delete 15 | import androidx.compose.material3.Card 16 | import androidx.compose.material3.CardDefaults 17 | import androidx.compose.material3.CircularProgressIndicator 18 | import androidx.compose.material3.Icon 19 | import androidx.compose.material3.IconButton 20 | import androidx.compose.material3.MaterialTheme 21 | import androidx.compose.material3.Text 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.Alignment 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.draw.clip 26 | import androidx.compose.ui.graphics.Color 27 | import androidx.compose.ui.layout.ContentScale 28 | import androidx.compose.ui.platform.LocalContext 29 | import androidx.compose.ui.res.stringResource 30 | import androidx.compose.ui.text.font.FontWeight 31 | import androidx.compose.ui.text.style.TextOverflow 32 | import androidx.compose.ui.tooling.preview.Preview 33 | import androidx.compose.ui.unit.dp 34 | import coil.compose.SubcomposeAsyncImage 35 | import coil.request.ImageRequest 36 | import id.barakkastudio.core.R 37 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 38 | import id.barakkastudio.core.ui.theme.Shapes 39 | import id.barakkastudio.core.util.UtilFunctions.fromDollarToRupiah 40 | 41 | @Composable 42 | fun ProductCartItem( 43 | modifier: Modifier = Modifier, 44 | product: ProductEntity = ProductEntity(), 45 | onRemoveClicked: () -> Unit 46 | ) { 47 | Card( 48 | shape = RoundedCornerShape(3.dp), 49 | colors = CardDefaults.cardColors(), 50 | modifier = modifier 51 | .padding(8.dp) 52 | .fillMaxWidth() 53 | .wrapContentHeight() 54 | ) { 55 | Row( 56 | modifier = modifier 57 | .fillMaxWidth() 58 | .wrapContentHeight() 59 | ) { 60 | SubcomposeAsyncImage( 61 | model = ImageRequest.Builder(LocalContext.current) 62 | .data(product.thumbnail) 63 | .placeholder(R.drawable.logo_nebenginaja) 64 | .crossfade(true) 65 | .size(150, 150) 66 | .bitmapConfig(Bitmap.Config.RGB_565) 67 | .build(), 68 | loading = { 69 | CircularProgressIndicator( 70 | color = Color.LightGray, 71 | modifier = Modifier.padding(16.dp) 72 | ) 73 | }, 74 | contentDescription = stringResource(R.string.product_thumbnail), 75 | contentScale = ContentScale.Crop, 76 | modifier = Modifier 77 | .size(90.dp) 78 | .padding(8.dp) 79 | .clip(Shapes.medium) 80 | ) 81 | Column( 82 | modifier = Modifier 83 | .padding(8.dp) 84 | .weight(1.0f) 85 | .align(Alignment.CenterVertically) 86 | ) { 87 | Text( 88 | text = product.title ?: "", 89 | maxLines = 1, 90 | overflow = TextOverflow.Ellipsis, 91 | style = MaterialTheme.typography.bodyMedium.copy( 92 | fontWeight = FontWeight.ExtraBold 93 | ), 94 | color = Color.Black 95 | ) 96 | Spacer(modifier = Modifier.size(3.dp)) 97 | Text( 98 | text = product.price.fromDollarToRupiah(), 99 | maxLines = 1, 100 | overflow = TextOverflow.Ellipsis, 101 | style = MaterialTheme.typography.bodySmall, 102 | color = MaterialTheme.colorScheme.secondary 103 | ) 104 | } 105 | IconButton( 106 | modifier = Modifier.align(Alignment.CenterVertically), 107 | onClick = { onRemoveClicked() } 108 | ) { 109 | Icon(Icons.Outlined.Delete, "Delete Icon") 110 | } 111 | } 112 | } 113 | } 114 | 115 | @Preview(showBackground = true) 116 | @Composable 117 | fun ProductCartItemPreview() { 118 | ProductCartItem( 119 | modifier = Modifier, 120 | product = ProductEntity( 121 | id = 1, 122 | title = "Product Title", 123 | price = 100000.0, 124 | thumbnail = "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" 125 | ), 126 | onRemoveClicked = {} 127 | ) 128 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/component/ProductItem.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.component 2 | 3 | 4 | import android.graphics.Bitmap 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.defaultMinSize 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.material3.Card 11 | import androidx.compose.material3.CardDefaults 12 | import androidx.compose.material3.CircularProgressIndicator 13 | import androidx.compose.material3.Divider 14 | import androidx.compose.material3.MaterialTheme 15 | import androidx.compose.material3.Text 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.graphics.Color 19 | import androidx.compose.ui.layout.ContentScale 20 | import androidx.compose.ui.platform.LocalContext 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.text.style.TextOverflow 24 | import androidx.compose.ui.tooling.preview.Preview 25 | import androidx.compose.ui.unit.dp 26 | import coil.compose.SubcomposeAsyncImage 27 | import coil.request.ImageRequest 28 | import id.barakkastudio.core.R 29 | import id.barakkastudio.core.data.model.Product 30 | import id.barakkastudio.core.ui.theme.Gray200 31 | import id.barakkastudio.core.util.UtilFunctions.fromDollarToRupiah 32 | 33 | @Composable 34 | fun ProductItem( 35 | modifier: Modifier = Modifier, 36 | product: Product = Product() 37 | ) { 38 | Card( 39 | shape = RoundedCornerShape(8.dp), 40 | colors = CardDefaults.cardColors( 41 | containerColor = Color.White 42 | ), 43 | modifier = modifier 44 | .padding(8.dp) 45 | .defaultMinSize() 46 | ) { 47 | Column( 48 | modifier = modifier.defaultMinSize() 49 | ) { 50 | SubcomposeAsyncImage( 51 | model = ImageRequest.Builder(LocalContext.current) 52 | .data(product.thumbnail) 53 | .placeholder(R.drawable.logo_nebenginaja) 54 | .crossfade(true) 55 | .size(150, 150) 56 | .bitmapConfig(Bitmap.Config.RGB_565) 57 | .build(), 58 | loading = { 59 | CircularProgressIndicator( 60 | color = Color.LightGray, 61 | modifier = Modifier.padding(48.dp) 62 | ) 63 | }, 64 | contentDescription = stringResource(R.string.product_thumbnail), 65 | contentScale = ContentScale.Crop, 66 | modifier = modifier.height(180.dp) 67 | ) 68 | Divider(color = Gray200, thickness = 1.dp) 69 | Column( 70 | modifier = modifier.padding(horizontal = 16.dp, vertical = 8.dp) 71 | ) { 72 | Text( 73 | text = product.title ?: "", 74 | maxLines = 1, 75 | overflow = TextOverflow.Ellipsis, 76 | style = MaterialTheme.typography.bodyMedium.copy( 77 | fontWeight = FontWeight.ExtraBold 78 | ), 79 | color = Color.Black 80 | ) 81 | Text( 82 | text = product.price.fromDollarToRupiah(), 83 | maxLines = 1, 84 | overflow = TextOverflow.Ellipsis, 85 | style = MaterialTheme.typography.bodySmall, 86 | color = MaterialTheme.colorScheme.secondary 87 | ) 88 | } 89 | } 90 | } 91 | } 92 | 93 | @Preview(showBackground = true) 94 | @Composable 95 | fun ProductItemPreview() { 96 | ProductItem( 97 | product = Product( 98 | id = 1, 99 | title = "Product Title", 100 | price = 100000.0, 101 | thumbnail = "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" 102 | ), 103 | modifier = Modifier 104 | ) 105 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/component/ProgressProduct.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.component 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.foundation.layout.wrapContentWidth 8 | import androidx.compose.material3.CircularProgressIndicator 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.material3.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.res.stringResource 15 | import androidx.compose.ui.unit.dp 16 | import id.barakkastudio.core.R 17 | 18 | /** Created by github.com/im-o on 5/10/2023. */ 19 | 20 | @Composable 21 | fun ProgressProduct() { 22 | Column { 23 | CircularProgressIndicator( 24 | modifier = Modifier 25 | .fillMaxWidth() 26 | .wrapContentWidth(Alignment.CenterHorizontally) 27 | ) 28 | Spacer(modifier = Modifier.size(32.dp)) 29 | Text( 30 | modifier = Modifier 31 | .fillMaxWidth() 32 | .wrapContentWidth(Alignment.CenterHorizontally), 33 | text = stringResource(R.string.load_product), 34 | color = MaterialTheme.colorScheme.onSurface 35 | ) 36 | } 37 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/detail/DetailScreen.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.detail 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.ArrowBack 9 | import androidx.compose.material3.Icon 10 | import androidx.compose.material3.IconButton 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.Scaffold 13 | import androidx.compose.material3.Text 14 | import androidx.compose.material3.TopAppBar 15 | import androidx.compose.material3.TopAppBarDefaults 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.runtime.collectAsState 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.graphics.Color 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.hilt.navigation.compose.hiltViewModel 23 | import id.barakkastudio.core.R 24 | import id.barakkastudio.core.data.UiState 25 | import id.barakkastudio.core.ui.theme.Gray200 26 | import id.barakkastudio.sample.ui.component.ProgressProduct 27 | import id.barakkastudio.sample.ui.detail.section.DetailContent 28 | 29 | /** Created by github.com/im-o on 12/22/2022. */ 30 | 31 | @Composable 32 | fun DetailScreen( 33 | productId: Int, 34 | viewModel: DetailViewModel = hiltViewModel(), 35 | navigateBack: () -> Unit 36 | ) { 37 | Scaffold( 38 | topBar = { 39 | TopAppBar( 40 | title = { 41 | Text(text = stringResource(R.string.detail_product)) 42 | }, 43 | navigationIcon = { 44 | IconButton(onClick = { navigateBack() }) { 45 | Icon( 46 | imageVector = Icons.Filled.ArrowBack, 47 | contentDescription = "Back Icon", 48 | tint = Color.White, 49 | ) 50 | } 51 | }, 52 | colors = TopAppBarDefaults.topAppBarColors( 53 | containerColor = MaterialTheme.colorScheme.primary, 54 | titleContentColor = Color.White, 55 | ) 56 | ) 57 | }, content = { 58 | Box( 59 | contentAlignment = Alignment.Center, 60 | modifier = Modifier 61 | .fillMaxSize() 62 | .background(Gray200) 63 | .padding(it) 64 | ) { 65 | viewModel.uiStateProduct.collectAsState(initial = UiState.Loading).value.let { uiState -> 66 | when (uiState) { 67 | is UiState.Loading -> { 68 | viewModel.getProductByIdApiCall(productId) 69 | ProgressProduct() 70 | } 71 | 72 | is UiState.Success -> { 73 | DetailContent(product = uiState.data, viewModel = viewModel) 74 | } 75 | 76 | is UiState.Error -> { 77 | Text(text = stringResource(R.string.error_product), color = MaterialTheme.colorScheme.onSurface) 78 | } 79 | } 80 | } 81 | } 82 | }) 83 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/detail/DetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.detail 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import id.barakkastudio.core.data.UiState 7 | import id.barakkastudio.core.data.datasource.local.db.entity.ProductEntity 8 | import id.barakkastudio.core.data.model.Product 9 | import id.barakkastudio.core.data.model.mapper.ProductMapper.mapFromProductToEntity 10 | import id.barakkastudio.core.domain.usecase.product.GetProductByIdUseCase 11 | import id.barakkastudio.core.domain.usecase.product.db.GetProductByIdDbUseCase 12 | import id.barakkastudio.core.domain.usecase.product.db.InsertProductDbUseCase 13 | import kotlinx.coroutines.CoroutineScope 14 | import kotlinx.coroutines.Dispatchers 15 | import kotlinx.coroutines.flow.MutableStateFlow 16 | import kotlinx.coroutines.flow.StateFlow 17 | import kotlinx.coroutines.flow.catch 18 | import kotlinx.coroutines.flow.launchIn 19 | import kotlinx.coroutines.flow.onEach 20 | import kotlinx.coroutines.launch 21 | import javax.inject.Inject 22 | 23 | /** Created by github.com/im-o on 12/16/2022. */ 24 | 25 | @HiltViewModel 26 | class DetailViewModel @Inject constructor( 27 | private val getProductByIdUseCase: GetProductByIdUseCase, 28 | private val getProductByIdDbUseCase: GetProductByIdDbUseCase, 29 | private val insertProductDbUseCase: InsertProductDbUseCase, 30 | ) : ViewModel() { 31 | 32 | private val _uiStateProduct: MutableStateFlow> = MutableStateFlow(UiState.Loading) 33 | val uiStateProduct: StateFlow> = _uiStateProduct 34 | 35 | private val _uiStateDbProduct: MutableStateFlow> = MutableStateFlow(UiState.Loading) 36 | val uiStateDbProduct: StateFlow> = _uiStateDbProduct 37 | 38 | fun getProductByIdApiCall(id: Int) { 39 | getProductByIdUseCase.execute(id).onEach { 40 | _uiStateProduct.value = UiState.Success(it) 41 | }.catch { e -> 42 | _uiStateProduct.value = UiState.Error(e.message.toString()) 43 | }.launchIn(viewModelScope) 44 | } 45 | 46 | fun getProductByIdDb(id: Long) { 47 | CoroutineScope(Dispatchers.IO).launch { 48 | try { 49 | getProductByIdDbUseCase.execute(id).catch { 50 | _uiStateDbProduct.value = UiState.Error(it.message.toString()) 51 | }.collect { product -> 52 | _uiStateDbProduct.value = UiState.Success(product) 53 | } 54 | } catch (e: Exception) { 55 | _uiStateDbProduct.value = UiState.Error(e.message.toString()) 56 | } 57 | } 58 | } 59 | 60 | fun insertProductDb(product: Product) { 61 | viewModelScope.launch { 62 | val longInsertStatus = insertProductDbUseCase.execute(mapFromProductToEntity(product)) 63 | if (longInsertStatus > 0) getProductByIdDb((product.id ?: -1).toLong()) 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/detail/section/DescriptionProduct.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.detail.section 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.res.stringResource 13 | import androidx.compose.ui.text.font.FontWeight 14 | import androidx.compose.ui.unit.dp 15 | import androidx.compose.ui.unit.sp 16 | import id.barakkastudio.core.R 17 | import id.barakkastudio.core.data.model.Product 18 | 19 | /** Created by github.com/im-o on 5/12/2023. */ 20 | 21 | @Composable 22 | internal fun DescriptionProduct(product: Product) { 23 | Column( 24 | modifier = Modifier.padding( 25 | horizontal = 16.dp, 26 | vertical = 16.dp 27 | ) 28 | ) { 29 | Text( 30 | text = stringResource(R.string.description), 31 | style = MaterialTheme.typography.bodyMedium.copy( 32 | fontWeight = FontWeight.Normal, fontSize = 18.sp 33 | ), 34 | color = Color.Black 35 | ) 36 | Spacer(modifier = Modifier.size(8.dp)) 37 | Text( 38 | text = product.description ?: stringResource(R.string.dash), 39 | style = MaterialTheme.typography.bodySmall.copy( 40 | fontWeight = FontWeight.Light, fontSize = 16.sp 41 | ), 42 | color = Color.DarkGray 43 | ) 44 | } 45 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/detail/section/DetailContent.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.detail.section 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.Spacer 9 | import androidx.compose.foundation.layout.fillMaxHeight 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.foundation.layout.fillMaxWidth 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.foundation.layout.size 14 | import androidx.compose.foundation.lazy.LazyColumn 15 | import androidx.compose.material3.Divider 16 | import androidx.compose.material3.MaterialTheme 17 | import androidx.compose.material3.Text 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.collectAsState 20 | import androidx.compose.runtime.getValue 21 | import androidx.compose.runtime.mutableStateOf 22 | import androidx.compose.runtime.remember 23 | import androidx.compose.runtime.setValue 24 | import androidx.compose.ui.Alignment 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.graphics.Color 27 | import androidx.compose.ui.platform.LocalContext 28 | import androidx.compose.ui.res.stringResource 29 | import androidx.compose.ui.text.style.TextAlign 30 | import androidx.compose.ui.unit.dp 31 | import id.barakkastudio.core.R 32 | import id.barakkastudio.core.data.UiState 33 | import id.barakkastudio.core.data.model.Product 34 | import id.barakkastudio.core.ui.theme.Gray200 35 | import id.barakkastudio.core.util.Dimens 36 | import id.barakkastudio.core.util.Extensions.myToast 37 | import id.barakkastudio.core.util.UtilFunctions.logE 38 | import id.barakkastudio.sample.ui.detail.DetailViewModel 39 | 40 | /** Created by github.com/im-o on 5/12/2023. */ 41 | 42 | @Composable 43 | fun DetailContent( 44 | product: Product, 45 | viewModel: DetailViewModel 46 | ) { 47 | val context = LocalContext.current 48 | val strBuy = stringResource(R.string.buy) 49 | val strAddedCart = stringResource(R.string.added_to_cart) 50 | val strThanks = stringResource(R.string.thank_you_buy) 51 | val productId = product.id?.toLong() ?: -1 52 | var buyText by remember { mutableStateOf(strBuy) } 53 | var isAlreadyOnCart by remember { mutableStateOf(false) } 54 | 55 | LazyColumn( 56 | modifier = Modifier 57 | .fillMaxSize() 58 | .fillMaxHeight() 59 | .background(color = Color.White), 60 | ) { 61 | item { 62 | ImageProductPager(product = product) 63 | Spacer(modifier = Modifier.size(Dimens.dp8)) 64 | TitleProduct(product = product) 65 | Divider(color = Gray200, thickness = 10.dp) 66 | DescriptionProduct(product = product) 67 | } 68 | } 69 | 70 | Box( 71 | modifier = Modifier.fillMaxSize(), 72 | Alignment.BottomStart 73 | ) { 74 | Row(horizontalArrangement = Arrangement.SpaceEvenly) { 75 | viewModel.uiStateDbProduct.collectAsState(initial = UiState.Loading).value.let { uiState -> 76 | when (uiState) { 77 | is UiState.Loading -> { 78 | viewModel.getProductByIdDb(productId) 79 | } 80 | 81 | is UiState.Success -> { 82 | isAlreadyOnCart = true 83 | } 84 | 85 | is UiState.Error -> { 86 | logE(uiState.errorMessage) 87 | } 88 | } 89 | } 90 | if (!isAlreadyOnCart) { 91 | Text( 92 | text = stringResource(R.string.add_to_cart), 93 | modifier = Modifier 94 | .background(MaterialTheme.colorScheme.secondary) 95 | .fillMaxWidth() 96 | .weight(1f) 97 | .padding(vertical = 20.dp) 98 | .clickable { 99 | context.myToast(strAddedCart) 100 | viewModel.insertProductDb(product) 101 | }, 102 | textAlign = TextAlign.Center, 103 | color = Color.White, 104 | ) 105 | } 106 | Text( 107 | text = buyText, 108 | modifier = Modifier 109 | .background(MaterialTheme.colorScheme.primary) 110 | .fillMaxWidth() 111 | .weight(1f) 112 | .padding(vertical = 20.dp) 113 | .clickable { 114 | context.myToast(strThanks) 115 | buyText = strThanks 116 | }, 117 | color = Color.White, 118 | textAlign = TextAlign.Center, 119 | ) 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/detail/section/ImageProductPager.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.detail.section 2 | 3 | import androidx.compose.animation.core.animateIntAsState 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.Spacer 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.layout.size 13 | import androidx.compose.foundation.layout.width 14 | import androidx.compose.foundation.shape.CircleShape 15 | import androidx.compose.foundation.shape.RoundedCornerShape 16 | import androidx.compose.material3.CircularProgressIndicator 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.runtime.getValue 19 | import androidx.compose.ui.Alignment 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.draw.clip 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.compose.ui.layout.ContentScale 24 | import androidx.compose.ui.platform.LocalContext 25 | import androidx.compose.ui.res.stringResource 26 | import androidx.compose.ui.unit.dp 27 | import coil.compose.SubcomposeAsyncImage 28 | import coil.request.ImageRequest 29 | import com.google.accompanist.pager.ExperimentalPagerApi 30 | import com.google.accompanist.pager.HorizontalPager 31 | import com.google.accompanist.pager.PagerState 32 | import com.google.accompanist.pager.rememberPagerState 33 | import id.barakkastudio.core.R 34 | import id.barakkastudio.core.data.model.Product 35 | import id.barakkastudio.core.util.Dimens 36 | 37 | /** Created by github.com/im-o on 5/12/2023. */ 38 | 39 | @OptIn(ExperimentalPagerApi::class) 40 | @Composable 41 | fun ImageProductPager(product: Product) { 42 | val items = product.images 43 | val pagerState = rememberPagerState() 44 | 45 | HorizontalPager( 46 | count = items?.size ?: 0, 47 | state = pagerState 48 | ) { page -> 49 | SubcomposeAsyncImage( 50 | model = ImageRequest.Builder(LocalContext.current) 51 | .data(items?.get(page)) 52 | .placeholder(R.drawable.logo_nebenginaja) 53 | .crossfade(true) 54 | .build(), 55 | loading = { 56 | CircularProgressIndicator( 57 | color = Color.LightGray, 58 | modifier = Modifier.padding(48.dp) 59 | ) 60 | }, 61 | contentDescription = stringResource(R.string.product_thumbnail), 62 | contentScale = ContentScale.Fit, 63 | modifier = Modifier.height(260.dp) 64 | ) 65 | } 66 | Spacer(modifier = Modifier.size(Dimens.dp8)) 67 | HorizontalTabs( 68 | items = items ?: emptyList(), 69 | pagerState = pagerState 70 | ) 71 | } 72 | 73 | @OptIn(ExperimentalPagerApi::class) 74 | @Composable 75 | private fun HorizontalTabs( 76 | items: List?, 77 | pagerState: PagerState, 78 | ) { 79 | val dotRadius = 4.dp 80 | val dotSpacing = 8.dp 81 | 82 | Box( 83 | modifier = Modifier 84 | .height(dotRadius * 2) 85 | .fillMaxWidth() 86 | ) { 87 | Row( 88 | modifier = Modifier.align(Alignment.Center), 89 | horizontalArrangement = Arrangement.spacedBy(dotSpacing), 90 | ) { 91 | items?.forEachIndexed { index, _ -> 92 | val animatedWidth by animateIntAsState( 93 | targetValue = if (pagerState.currentPage == index) dotRadius.value.toInt() * 12 else dotRadius.value.toInt() * 2, label = "" 94 | ) 95 | Box( 96 | modifier = Modifier 97 | .height(dotRadius * 2) 98 | .width(animatedWidth.dp) 99 | .clip( 100 | if (pagerState.currentPage == index) { 101 | RoundedCornerShape(dotRadius) 102 | } else { 103 | CircleShape 104 | } 105 | ) 106 | .background( 107 | if (pagerState.currentPage == index) Color.Gray else Color.LightGray 108 | ), 109 | ) 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/detail/section/TitleProduct.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.detail.section 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.res.stringResource 11 | import androidx.compose.ui.text.font.FontWeight 12 | import androidx.compose.ui.text.style.TextOverflow 13 | import androidx.compose.ui.unit.dp 14 | import androidx.compose.ui.unit.sp 15 | import id.barakkastudio.core.R 16 | import id.barakkastudio.core.data.model.Product 17 | import id.barakkastudio.core.util.UtilFunctions.fromDollarToRupiah 18 | 19 | /** Created by github.com/im-o on 5/12/2023. */ 20 | 21 | @Composable 22 | fun TitleProduct(product: Product) { 23 | Column( 24 | modifier = Modifier.padding( 25 | horizontal = 16.dp, 26 | vertical = 8.dp 27 | ) 28 | ) { 29 | Text( 30 | text = product.title ?: stringResource(R.string.dash), 31 | maxLines = 2, 32 | overflow = TextOverflow.Ellipsis, 33 | style = MaterialTheme.typography.bodyMedium.copy( 34 | fontWeight = FontWeight.Normal, fontSize = 28.sp 35 | ), 36 | color = Color.Black 37 | ) 38 | Text( 39 | text = product.price.fromDollarToRupiah(), 40 | style = MaterialTheme.typography.bodySmall.copy( 41 | fontWeight = FontWeight.Light, fontSize = 20.sp 42 | ), 43 | color = MaterialTheme.colorScheme.secondary 44 | ) 45 | } 46 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/home/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.home 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.collectAsState 10 | import androidx.compose.runtime.getValue 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.res.stringResource 15 | import androidx.hilt.navigation.compose.hiltViewModel 16 | import id.barakkastudio.core.R 17 | import id.barakkastudio.core.data.UiState 18 | import id.barakkastudio.core.data.model.ProductResponse 19 | import id.barakkastudio.core.ui.component.molecules.SearchBar 20 | import id.barakkastudio.core.ui.template.MainTemplate 21 | import id.barakkastudio.core.ui.theme.Gray200 22 | import id.barakkastudio.sample.ui.component.ProgressProduct 23 | import id.barakkastudio.sample.ui.home.section.HomeContent 24 | 25 | /** Created by github.com/im-o on 12/12/2022. */ 26 | 27 | @Composable 28 | fun HomeScreen( 29 | modifier: Modifier = Modifier, 30 | viewModel: HomeViewModel = hiltViewModel(), 31 | navigateToDetail: (Int) -> Unit, 32 | navigateToSearch: () -> Unit, 33 | ) { 34 | val uiStateProduct by remember { viewModel.uiStateProduct }.collectAsState() 35 | 36 | MainTemplate( 37 | modifier = modifier, 38 | topBar = { 39 | SearchBar( 40 | query = "", 41 | onQueryChange = {}, 42 | modifier = Modifier.background(MaterialTheme.colorScheme.primary), 43 | isEnabled = false, 44 | onSearchClicked = { navigateToSearch() } 45 | ) 46 | }, 47 | content = { 48 | Box( 49 | contentAlignment = Alignment.Center, 50 | modifier = modifier 51 | .fillMaxSize() 52 | .background(Gray200) 53 | ) { 54 | when (uiStateProduct) { 55 | is UiState.Loading -> { 56 | viewModel.getProductsApiCall() 57 | ProgressProduct() 58 | } 59 | 60 | is UiState.Success -> { 61 | HomeContent( 62 | modifier = modifier, 63 | listProduct = (uiStateProduct as UiState.Success).data.products, 64 | navigateToDetail = navigateToDetail, 65 | ) 66 | } 67 | 68 | is UiState.Error -> { 69 | Text(text = stringResource(R.string.error_product), color = MaterialTheme.colorScheme.onSurface) 70 | } 71 | } 72 | } 73 | }) 74 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.home 2 | 3 | import androidx.compose.runtime.State 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import id.barakkastudio.core.data.UiState 9 | import id.barakkastudio.core.data.model.ProductResponse 10 | import id.barakkastudio.core.domain.usecase.product.GetProductsUseCase 11 | import id.barakkastudio.core.domain.usecase.product.SearchProductUseCase 12 | import kotlinx.coroutines.flow.MutableStateFlow 13 | import kotlinx.coroutines.flow.StateFlow 14 | import kotlinx.coroutines.flow.catch 15 | import kotlinx.coroutines.flow.launchIn 16 | import kotlinx.coroutines.flow.onEach 17 | import kotlinx.coroutines.launch 18 | import javax.inject.Inject 19 | 20 | /** Created by github.com/im-o on 12/16/2022. */ 21 | 22 | @HiltViewModel 23 | class HomeViewModel @Inject constructor( 24 | private val getProductsUseCase: GetProductsUseCase, 25 | private val searchProductUseCase: SearchProductUseCase 26 | ) : ViewModel() { 27 | 28 | private val _uiStateProduct: MutableStateFlow> = MutableStateFlow(UiState.Loading) 29 | val uiStateProduct: StateFlow> = _uiStateProduct 30 | 31 | private val _query = mutableStateOf("") 32 | val query: State get() = _query 33 | 34 | fun getProductsApiCall() { // this is sample not using `suspend` 35 | getProductsUseCase.execute(Unit).onEach { product -> 36 | _uiStateProduct.value = UiState.Success(product) 37 | }.catch { e -> 38 | _uiStateProduct.value = UiState.Error(e.message.toString()) 39 | }.launchIn(viewModelScope) 40 | } 41 | 42 | fun searchProductApiCall(query: String) { 43 | _query.value = query 44 | viewModelScope.launch { 45 | try { 46 | searchProductUseCase.execute(_query.value) 47 | .catch { 48 | _uiStateProduct.value = UiState.Error(it.message.toString()) 49 | } 50 | .collect { product -> 51 | _uiStateProduct.value = UiState.Success(product) 52 | } 53 | } catch (e: Exception) { 54 | _uiStateProduct.value = UiState.Error(e.message.toString()) 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/home/section/HomeContent.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.home.section 2 | 3 | import androidx.compose.animation.core.tween 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.PaddingValues 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.lazy.grid.GridCells 11 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 12 | import androidx.compose.foundation.lazy.grid.items 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.unit.dp 16 | import id.barakkastudio.core.data.model.Product 17 | import id.barakkastudio.sample.ui.component.EmptyProduct 18 | import id.barakkastudio.sample.ui.component.ProductItem 19 | 20 | /** Created by github.com/im-o on 5/10/2023. */ 21 | 22 | @OptIn(ExperimentalFoundationApi::class) 23 | @Composable 24 | fun HomeContent( 25 | modifier: Modifier, 26 | listProduct: MutableList?, 27 | navigateToDetail: (Int) -> Unit, 28 | ) { 29 | Column( 30 | modifier = modifier.fillMaxSize() 31 | ) { 32 | if (listProduct != null) { 33 | LazyVerticalGrid( 34 | columns = GridCells.Adaptive(140.dp), 35 | content = { 36 | items(listProduct, key = { it.id ?: -1 }) { product -> 37 | ProductItem( 38 | product = product, 39 | modifier = modifier 40 | .fillMaxWidth() 41 | .animateItemPlacement(tween(durationMillis = 100)) 42 | .clickable { 43 | navigateToDetail(product.id ?: return@clickable) 44 | } 45 | ) 46 | } 47 | }, contentPadding = PaddingValues(8.dp) 48 | ) 49 | if (listProduct.isEmpty()) EmptyProduct() 50 | } else EmptyProduct() 51 | } 52 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/BottomNav.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.Arrangement 7 | import androidx.compose.foundation.layout.Box 8 | import androidx.compose.foundation.layout.Row 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.shape.RoundedCornerShape 13 | import androidx.compose.material3.Icon 14 | import androidx.compose.material3.MaterialTheme 15 | import androidx.compose.material3.Scaffold 16 | import androidx.compose.material3.Text 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.draw.clip 21 | import androidx.compose.ui.graphics.Color 22 | import androidx.compose.ui.res.painterResource 23 | import androidx.compose.ui.res.stringResource 24 | import androidx.compose.ui.unit.dp 25 | import androidx.navigation.NavDestination 26 | import androidx.navigation.NavDestination.Companion.hierarchy 27 | import androidx.navigation.NavGraph.Companion.findStartDestination 28 | import androidx.navigation.NavHostController 29 | import androidx.navigation.compose.rememberNavController 30 | import id.barakkastudio.sample.ui.navigation.model.BottomBar 31 | import id.barakkastudio.sample.ui.navigation.model.BottomBarScreen 32 | 33 | /** Created by github.com/im-o on 5/13/2023. */ 34 | 35 | @Composable 36 | fun BottomNav( 37 | modifier: Modifier = Modifier, 38 | navigationItemContentList: List, 39 | navController: NavHostController = rememberNavController(), 40 | currentDestination: NavDestination? 41 | ) { 42 | Scaffold( 43 | modifier = modifier, 44 | containerColor = MaterialTheme.colorScheme.primary, 45 | bottomBar = { 46 | // show and hide bottom navigation 47 | if (currentDestination?.route == BottomBarScreen.Home.route || 48 | currentDestination?.route == BottomBarScreen.Cart.route || 49 | currentDestination?.route == BottomBarScreen.Profile.route 50 | ) { 51 | BottomBar( 52 | modifier = modifier, 53 | navController = navController, 54 | navigationItemContentList = navigationItemContentList, 55 | currentDestination = currentDestination 56 | ) 57 | } 58 | }, 59 | ) { 60 | MainNavHost(navController = navController, innerPadding = it) 61 | } 62 | } 63 | 64 | @Composable 65 | fun BottomBar( 66 | modifier: Modifier = Modifier, 67 | navController: NavHostController, 68 | navigationItemContentList: List, 69 | currentDestination: NavDestination? 70 | ) { 71 | Row( 72 | modifier = modifier 73 | .padding(start = 0.dp, end = 0.dp, top = 10.dp, bottom = 10.dp) 74 | .background(Color.Transparent) 75 | .fillMaxWidth(), 76 | horizontalArrangement = Arrangement.SpaceEvenly, 77 | verticalAlignment = Alignment.CenterVertically 78 | ) { 79 | navigationItemContentList.forEach { screen -> 80 | BottomNavItem( 81 | screen = screen, 82 | currentDestination = currentDestination, 83 | navController = navController 84 | ) 85 | } 86 | } 87 | } 88 | 89 | @Composable 90 | fun BottomNavItem( 91 | screen: BottomBar, 92 | currentDestination: NavDestination?, 93 | navController: NavHostController 94 | ) { 95 | val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true 96 | val background = if (selected) MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.1f) else Color.Transparent 97 | val contentColor = if (selected) MaterialTheme.colorScheme.background else MaterialTheme.colorScheme.background.copy(alpha = 0.4f) 98 | 99 | Box( 100 | modifier = Modifier 101 | .height(42.dp) 102 | .clip(RoundedCornerShape(10.dp)) 103 | .background(background) 104 | .clickable(onClick = { 105 | navController.navigate(screen.route) { 106 | popUpTo(navController.graph.findStartDestination().id) 107 | launchSingleTop = true 108 | } 109 | }) 110 | ) { 111 | Row( 112 | modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp), 113 | verticalAlignment = Alignment.CenterVertically, 114 | horizontalArrangement = Arrangement.spacedBy(4.dp) 115 | ) { 116 | Icon( 117 | painter = painterResource(id = if (selected) screen.iconFocused else screen.icon), 118 | contentDescription = stringResource(id = screen.titleResId), 119 | tint = contentColor 120 | ) 121 | AnimatedVisibility(visible = selected) { 122 | Text( 123 | text = stringResource(id = screen.titleResId), 124 | color = contentColor 125 | ) 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/MainNavHost.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.navigation.NavHostController 8 | import androidx.navigation.NavType 9 | import androidx.navigation.compose.NavHost 10 | import androidx.navigation.compose.composable 11 | import androidx.navigation.navArgument 12 | import id.barakkastudio.sample.ui.cart.CartScreen 13 | import id.barakkastudio.sample.ui.detail.DetailScreen 14 | import id.barakkastudio.sample.ui.home.HomeScreen 15 | import id.barakkastudio.sample.ui.navigation.model.BottomBarScreen 16 | import id.barakkastudio.sample.ui.navigation.model.GeneralScreen 17 | import id.barakkastudio.sample.ui.profile.ProfileScreen 18 | import id.barakkastudio.sample.ui.search.SearchScreen 19 | 20 | /** Created by github.com/im-o on 5/13/2023. */ 21 | 22 | @Composable 23 | fun MainNavHost( 24 | navController: NavHostController, 25 | innerPadding: PaddingValues 26 | ) { 27 | NavHost( 28 | navController = navController, 29 | startDestination = BottomBarScreen.Home.route, 30 | modifier = Modifier.padding(innerPadding) 31 | ) { 32 | composable(BottomBarScreen.Home.route) { 33 | HomeScreen( 34 | navigateToDetail = { productId -> 35 | navController.navigate(GeneralScreen.DetailProduct.createRoute(productId)) 36 | }, 37 | navigateToSearch = { 38 | navController.navigate(GeneralScreen.SearchProduct.route) 39 | } 40 | ) 41 | } 42 | composable(BottomBarScreen.Cart.route) { 43 | CartScreen( 44 | navigateToDetail = { productId -> 45 | navController.navigate(GeneralScreen.DetailProduct.createRoute(productId)) 46 | } 47 | ) 48 | } 49 | composable(BottomBarScreen.Profile.route) { 50 | ProfileScreen() 51 | } 52 | composable( 53 | route = GeneralScreen.DetailProduct.route, 54 | arguments = listOf(navArgument("productId") { type = NavType.IntType }), 55 | ) { 56 | val id = it.arguments?.getInt("productId") ?: -1 57 | DetailScreen( 58 | productId = id, 59 | navigateBack = { 60 | navController.navigateUp() 61 | }, 62 | ) 63 | } 64 | composable( 65 | route = GeneralScreen.SearchProduct.route, 66 | ) { 67 | SearchScreen( 68 | navigateToDetail = { productId -> 69 | navController.navigate(GeneralScreen.DetailProduct.createRoute(productId)) 70 | }, 71 | navigateBack = { 72 | navController.navigateUp() 73 | } 74 | ) 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/NavRail.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.PaddingValues 8 | import androidx.compose.foundation.layout.Row 9 | import androidx.compose.foundation.layout.Spacer 10 | import androidx.compose.foundation.layout.fillMaxHeight 11 | import androidx.compose.foundation.layout.fillMaxSize 12 | import androidx.compose.foundation.layout.fillMaxWidth 13 | import androidx.compose.foundation.layout.height 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.layout.width 16 | import androidx.compose.foundation.lazy.LazyColumn 17 | import androidx.compose.foundation.lazy.items 18 | import androidx.compose.foundation.shape.RoundedCornerShape 19 | import androidx.compose.material3.Icon 20 | import androidx.compose.material3.MaterialTheme 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.draw.clip 25 | import androidx.compose.ui.graphics.Color 26 | import androidx.compose.ui.res.painterResource 27 | import androidx.compose.ui.res.stringResource 28 | import androidx.navigation.NavDestination 29 | import androidx.navigation.NavDestination.Companion.hierarchy 30 | import androidx.navigation.NavGraph.Companion.findStartDestination 31 | import androidx.navigation.NavHostController 32 | import id.barakkastudio.core.util.Dimens 33 | import id.barakkastudio.sample.ui.navigation.model.BottomBar 34 | 35 | /** Created by github.com/im-o on 11/2/2023. */ 36 | 37 | @Composable 38 | fun NavRail( 39 | navigationItemContentList: List, 40 | navController: NavHostController, 41 | modifier: Modifier = Modifier, 42 | currentDestination: NavDestination? 43 | ) { 44 | Row( 45 | modifier = modifier 46 | .fillMaxSize() 47 | .background(MaterialTheme.colorScheme.primary) 48 | ) { 49 | LazyColumn( 50 | modifier = modifier.fillMaxHeight(), 51 | contentPadding = PaddingValues(Dimens.dp12) 52 | ) { 53 | items(navigationItemContentList) { screen -> 54 | NavRailItem( 55 | screen = screen, 56 | currentDestination = currentDestination, 57 | navController = navController, 58 | modifier = modifier, 59 | ) 60 | Spacer(modifier = modifier.height(Dimens.dp24)) 61 | } 62 | } 63 | MainNavHost(navController = navController, innerPadding = PaddingValues(end = Dimens.dp0)) 64 | } 65 | } 66 | 67 | @Composable 68 | fun NavRailItem( 69 | screen: BottomBar, 70 | currentDestination: NavDestination?, 71 | navController: NavHostController, 72 | modifier: Modifier = Modifier, 73 | ) { 74 | val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true 75 | val background = if (selected) MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.1f) else Color.Transparent 76 | val contentColor = if (selected) MaterialTheme.colorScheme.background else MaterialTheme.colorScheme.background.copy(alpha = 0.4f) 77 | 78 | Box( 79 | modifier = modifier 80 | .height(Dimens.dp42) 81 | .width(Dimens.dp42) 82 | .clip(RoundedCornerShape(Dimens.dp10)) 83 | .background(background) 84 | .clickable(onClick = { 85 | navController.navigate(screen.route) { 86 | popUpTo(navController.graph.findStartDestination().id) 87 | launchSingleTop = true 88 | } 89 | }) 90 | ) { 91 | Row( 92 | modifier = modifier 93 | .padding(Dimens.dp8) 94 | .fillMaxWidth(), 95 | verticalAlignment = Alignment.CenterVertically, 96 | horizontalArrangement = Arrangement.spacedBy(Dimens.dp0) 97 | ) { 98 | Icon( 99 | painter = painterResource(id = if (selected) screen.iconFocused else screen.icon), 100 | contentDescription = stringResource(id = screen.titleResId), 101 | tint = contentColor 102 | ) 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/model/BottomBar.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation.model 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.annotation.StringRes 5 | 6 | /** Created by github.com/im-o on 12/12/2022. */ 7 | 8 | sealed class BottomBar( 9 | val route: String, 10 | @StringRes val titleResId: Int, 11 | @DrawableRes val icon: Int, 12 | @DrawableRes val iconFocused: Int 13 | ) -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/model/BottomBarScreen.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation.model 2 | 3 | import id.barakkastudio.core.R 4 | 5 | /** Created by github.com/im-o on 5/8/2023. */ 6 | 7 | sealed class BottomBarScreen(val route: String) { 8 | object Home : BottomBar( 9 | route = "home", 10 | titleResId = R.string.home, 11 | icon = R.drawable.home, 12 | iconFocused = R.drawable.home 13 | ) 14 | 15 | object Cart : BottomBar( 16 | route = "cart", 17 | titleResId = R.string.cart, 18 | icon = R.drawable.cart, 19 | iconFocused = R.drawable.cart 20 | ) 21 | 22 | object Profile : BottomBar( 23 | route = "profile", 24 | titleResId = R.string.profile, 25 | icon = R.drawable.user, 26 | iconFocused = R.drawable.user 27 | ) 28 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/model/GeneralScreen.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation.model 2 | 3 | /** Created by github.com/im-o on 5/10/2023. */ 4 | 5 | sealed class GeneralScreen(val route: String) { 6 | 7 | object DetailProduct : BottomBarScreen("home/{productId}") { 8 | fun createRoute(productId: Int) = "home/$productId" 9 | } 10 | 11 | object SearchProduct : GeneralScreen(route = "home/search") 12 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/navdrawer/NavDrawer.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation.navdrawer 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.material3.DrawerValue 5 | import androidx.compose.material3.ModalDrawerSheet 6 | import androidx.compose.material3.ModalNavigationDrawer 7 | import androidx.compose.material3.rememberDrawerState 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.rememberCoroutineScope 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.RectangleShape 12 | import androidx.navigation.NavDestination 13 | import androidx.navigation.NavHostController 14 | import id.barakkastudio.core.util.Dimens 15 | import id.barakkastudio.sample.ui.navigation.MainNavHost 16 | import id.barakkastudio.sample.ui.navigation.model.BottomBar 17 | import id.barakkastudio.sample.ui.navigation.navdrawer.sections.NavDrawerSection 18 | import kotlinx.coroutines.launch 19 | 20 | /** Created by github.com/im-o on 11/14/2023. */ 21 | 22 | @Composable 23 | fun NavDrawer( 24 | modifier: Modifier = Modifier, 25 | navigationItemContentList: List, 26 | navController: NavHostController, 27 | currentDestination: NavDestination? 28 | ) { 29 | val drawerState = rememberDrawerState(DrawerValue.Closed) 30 | val coroutineScope = rememberCoroutineScope() 31 | 32 | ModalNavigationDrawer( 33 | drawerState = drawerState, 34 | drawerContent = { 35 | ModalDrawerSheet( 36 | drawerShape = RectangleShape 37 | ) { 38 | NavDrawerSection( 39 | modifier = modifier, 40 | navigationItemContentList = navigationItemContentList, 41 | navController = navController, 42 | currentDestination = currentDestination, 43 | onDrawerAction = { 44 | coroutineScope.launch { 45 | if (drawerState.isOpen) drawerState.close() else drawerState.open() 46 | } 47 | }, 48 | ) 49 | } 50 | } 51 | ) { 52 | MainNavHost(navController = navController, innerPadding = PaddingValues(end = Dimens.dp0)) 53 | } 54 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/navdrawer/sections/NavDrawerHeader.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation.navdrawer.sections 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.foundation.layout.width 10 | import androidx.compose.foundation.shape.CircleShape 11 | import androidx.compose.material3.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.draw.clip 16 | import androidx.compose.ui.draw.shadow 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.layout.ContentScale 19 | import androidx.compose.ui.res.painterResource 20 | import androidx.compose.ui.res.stringResource 21 | import androidx.compose.ui.text.TextStyle 22 | import androidx.compose.ui.text.font.FontWeight 23 | import id.barakkastudio.core.R 24 | import id.barakkastudio.core.util.Dimens 25 | 26 | /** Created by github.com/im-o on 11/16/2023. */ 27 | 28 | @Composable 29 | fun NavDrawerHeader( 30 | modifier: Modifier = Modifier, 31 | ) { 32 | Row { 33 | Image( 34 | modifier = modifier 35 | .size(Dimens.dp60) 36 | .shadow(elevation = Dimens.dp1, shape = CircleShape) 37 | .clip(shape = CircleShape), 38 | painter = painterResource(id = R.drawable.rival_profile), 39 | contentDescription = stringResource(id = R.string.rivaldy), 40 | contentScale = ContentScale.Crop, 41 | ) 42 | Spacer(modifier = modifier.width(Dimens.dp16)) 43 | Column(modifier = modifier.align(Alignment.CenterVertically)) { 44 | Text( 45 | text = stringResource(id = R.string.rivaldy), 46 | style = TextStyle( 47 | fontWeight = FontWeight.Bold, 48 | color = Color.White, 49 | fontSize = Dimens.sp18, 50 | ), 51 | ) 52 | Spacer(modifier = modifier.height(Dimens.dp3)) 53 | Text( 54 | text = stringResource(id = R.string.profile_web), 55 | style = TextStyle( 56 | fontWeight = FontWeight.Bold, 57 | color = Color.White.copy(alpha = 0.5f), 58 | fontSize = Dimens.sp14, 59 | ), 60 | ) 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/navdrawer/sections/NavDrawerMenu.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation.navdrawer.sections 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.Column 8 | import androidx.compose.foundation.layout.Row 9 | import androidx.compose.foundation.layout.Spacer 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.foundation.layout.fillMaxWidth 12 | import androidx.compose.foundation.layout.height 13 | import androidx.compose.foundation.layout.padding 14 | import androidx.compose.foundation.layout.width 15 | import androidx.compose.foundation.shape.RoundedCornerShape 16 | import androidx.compose.material3.Icon 17 | import androidx.compose.material3.MaterialTheme 18 | import androidx.compose.material3.Text 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.draw.clip 23 | import androidx.compose.ui.graphics.Color 24 | import androidx.compose.ui.res.painterResource 25 | import androidx.compose.ui.res.stringResource 26 | import androidx.navigation.NavDestination 27 | import androidx.navigation.NavDestination.Companion.hierarchy 28 | import androidx.navigation.NavGraph.Companion.findStartDestination 29 | import androidx.navigation.NavHostController 30 | import id.barakkastudio.core.util.Dimens 31 | import id.barakkastudio.sample.ui.navigation.model.BottomBar 32 | 33 | /** Created by github.com/im-o on 11/17/2023. */ 34 | 35 | @Composable 36 | fun NavDrawerMenu( 37 | modifier: Modifier = Modifier, 38 | navigationItemContentList: List, 39 | navController: NavHostController, 40 | currentDestination: NavDestination?, 41 | onDrawerAction: () -> Unit, 42 | ) { 43 | Column( 44 | modifier = modifier.fillMaxSize() 45 | ) { 46 | navigationItemContentList.forEach { screen -> 47 | NavDrawerItem( 48 | modifier = modifier, 49 | screen = screen, 50 | currentDestination = currentDestination, 51 | navController = navController, 52 | onDrawerAction = onDrawerAction, 53 | ) 54 | Spacer(modifier = modifier.height(Dimens.dp16)) 55 | } 56 | } 57 | } 58 | 59 | @Composable 60 | fun NavDrawerItem( 61 | modifier: Modifier = Modifier, 62 | screen: BottomBar, 63 | currentDestination: NavDestination?, 64 | navController: NavHostController, 65 | onDrawerAction: () -> Unit, 66 | ) { 67 | val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true 68 | val background = if (selected) MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.1f) else Color.Transparent 69 | val contentColor = if (selected) MaterialTheme.colorScheme.background else MaterialTheme.colorScheme.background.copy(alpha = 0.4f) 70 | 71 | Box( 72 | modifier = modifier 73 | .height(Dimens.dp42) 74 | .clip(RoundedCornerShape(Dimens.dp10)) 75 | .background(background) 76 | .clickable(onClick = { 77 | onDrawerAction() 78 | navController.navigate(screen.route) { 79 | popUpTo(navController.graph.findStartDestination().id) 80 | launchSingleTop = true 81 | } 82 | }) 83 | ) { 84 | Row( 85 | modifier = modifier 86 | .padding(Dimens.dp8) 87 | .fillMaxWidth(), 88 | verticalAlignment = Alignment.CenterVertically, 89 | horizontalArrangement = Arrangement.spacedBy(Dimens.dp0) 90 | ) { 91 | Icon( 92 | painter = painterResource(id = if (selected) screen.iconFocused else screen.icon), 93 | contentDescription = stringResource(id = screen.titleResId), 94 | tint = contentColor 95 | ) 96 | Spacer(modifier = modifier.width(Dimens.dp8)) 97 | Text( 98 | text = stringResource(id = screen.titleResId), 99 | color = contentColor 100 | ) 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/navigation/navdrawer/sections/NavDrawerSection.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.navigation.navdrawer.sections 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.foundation.layout.height 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.rememberScrollState 11 | import androidx.compose.foundation.verticalScroll 12 | import androidx.compose.material3.Divider 13 | import androidx.compose.material3.MaterialTheme 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.navigation.NavDestination 18 | import androidx.navigation.NavHostController 19 | import id.barakkastudio.core.util.Dimens 20 | import id.barakkastudio.sample.ui.navigation.model.BottomBar 21 | 22 | /** Created by github.com/im-o on 11/17/2023. */ 23 | 24 | @Composable 25 | fun NavDrawerSection( 26 | modifier: Modifier = Modifier, 27 | navigationItemContentList: List, 28 | navController: NavHostController, 29 | currentDestination: NavDestination?, 30 | onDrawerAction: () -> Unit, 31 | ) { 32 | Box( 33 | modifier = modifier 34 | .fillMaxSize() 35 | .background(color = MaterialTheme.colorScheme.primary), 36 | ) { 37 | Column( 38 | modifier = modifier 39 | .fillMaxSize() 40 | .verticalScroll(state = rememberScrollState()) 41 | .padding(Dimens.dp32), 42 | ) { 43 | Spacer(modifier = modifier.height(Dimens.dp16)) 44 | NavDrawerHeader(modifier = modifier) 45 | Spacer(modifier = modifier.height(Dimens.dp16)) 46 | Divider( 47 | thickness = Dimens.dp1, 48 | color = Color.White.copy(alpha = 0.5f), 49 | ) 50 | Spacer(modifier = modifier.height(Dimens.dp16)) 51 | NavDrawerMenu( 52 | navigationItemContentList = navigationItemContentList, 53 | navController = navController, 54 | currentDestination = currentDestination, 55 | onDrawerAction = onDrawerAction, 56 | ) 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/profile/ProfileScreen.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.profile 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.rememberScrollState 10 | import androidx.compose.foundation.verticalScroll 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.Scaffold 13 | import androidx.compose.material3.Text 14 | import androidx.compose.material3.TopAppBar 15 | import androidx.compose.material3.TopAppBarDefaults 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.Alignment 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.graphics.Color 20 | import androidx.compose.ui.res.stringResource 21 | import id.barakkastudio.core.R 22 | import id.barakkastudio.core.ui.theme.Gray200 23 | import id.barakkastudio.core.util.Dimens 24 | import id.barakkastudio.sample.ui.profile.section.ProfileContent 25 | 26 | /** Created by github.com/im-o on 12/12/2022. */ 27 | 28 | @Composable 29 | fun ProfileScreen() { 30 | Scaffold( 31 | topBar = { 32 | TopAppBar( 33 | title = { 34 | Text(text = stringResource(R.string.profile)) 35 | }, 36 | colors = TopAppBarDefaults.topAppBarColors( 37 | containerColor = MaterialTheme.colorScheme.primary, 38 | titleContentColor = Color.White, 39 | ), 40 | ) 41 | }, content = { 42 | Box( 43 | contentAlignment = Alignment.Center, 44 | modifier = Modifier 45 | .fillMaxSize() 46 | .background(Gray200) 47 | .padding(it) 48 | ) { 49 | Column( 50 | modifier = Modifier 51 | .verticalScroll(rememberScrollState()) 52 | .align(Alignment.TopCenter) 53 | .fillMaxWidth() 54 | .padding(Dimens.dp32), 55 | horizontalAlignment = Alignment.CenterHorizontally 56 | ) { 57 | ProfileContent() 58 | } 59 | } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/profile/section/ProfileContent.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.profile.section 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.foundation.shape.CircleShape 10 | import androidx.compose.material3.Card 11 | import androidx.compose.material3.CardDefaults 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Alignment 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.layout.ContentScale 19 | import androidx.compose.ui.res.painterResource 20 | import androidx.compose.ui.res.stringResource 21 | import androidx.compose.ui.text.font.FontWeight 22 | import androidx.compose.ui.text.style.TextAlign 23 | import androidx.compose.ui.unit.dp 24 | import androidx.compose.ui.unit.sp 25 | import id.barakkastudio.core.R 26 | 27 | /** Created by github.com/im-o on 5/13/2023. */ 28 | 29 | @Composable 30 | fun ProfileContent() { 31 | Card( 32 | modifier = Modifier.size(160.dp), 33 | shape = CircleShape, 34 | elevation = CardDefaults.cardElevation( 35 | defaultElevation = 3.dp 36 | ) 37 | ) { 38 | Image( 39 | painterResource(R.drawable.rival_profile), 40 | contentDescription = stringResource(R.string.about_page), 41 | contentScale = ContentScale.Crop, 42 | modifier = Modifier.fillMaxSize() 43 | ) 44 | } 45 | Spacer(modifier = Modifier.size(16.dp)) 46 | Column( 47 | modifier = Modifier.fillMaxWidth(), 48 | horizontalAlignment = Alignment.CenterHorizontally 49 | ) { 50 | Text( 51 | text = stringResource(R.string.rivaldy), 52 | style = MaterialTheme.typography.bodyMedium.copy( 53 | fontWeight = FontWeight.Normal, fontSize = 28.sp, textAlign = TextAlign.Center 54 | ), 55 | color = Color.Black 56 | ) 57 | Spacer(modifier = Modifier.size(3.dp)) 58 | Text( 59 | text = stringResource(R.string.profile_web), 60 | style = MaterialTheme.typography.bodySmall.copy( 61 | fontWeight = FontWeight.Light, fontSize = 18.sp, textAlign = TextAlign.Center 62 | ), 63 | color = Color.DarkGray 64 | ) 65 | Spacer(modifier = Modifier.size(16.dp)) 66 | Text( 67 | text = stringResource(R.string.profile_description), 68 | style = MaterialTheme.typography.bodySmall.copy( 69 | fontWeight = FontWeight.Light, fontSize = 16.sp, textAlign = TextAlign.Center 70 | ), 71 | color = MaterialTheme.colorScheme.secondary 72 | ) 73 | } 74 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/search/SearchScreen.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.search 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material.icons.Icons 9 | import androidx.compose.material.icons.filled.ArrowBack 10 | import androidx.compose.material3.Icon 11 | import androidx.compose.material3.IconButton 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.runtime.LaunchedEffect 16 | import androidx.compose.runtime.collectAsState 17 | import androidx.compose.runtime.getValue 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.ui.Alignment 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.focus.FocusRequester 22 | import androidx.compose.ui.focus.focusRequester 23 | import androidx.compose.ui.res.stringResource 24 | import androidx.compose.ui.unit.dp 25 | import androidx.hilt.navigation.compose.hiltViewModel 26 | import id.barakkastudio.core.R 27 | import id.barakkastudio.core.data.UiState 28 | import id.barakkastudio.core.data.model.ProductResponse 29 | import id.barakkastudio.core.ui.component.molecules.SearchBar 30 | import id.barakkastudio.core.ui.template.MainTemplate 31 | import id.barakkastudio.core.ui.theme.Gray200 32 | import id.barakkastudio.sample.ui.component.ProgressProduct 33 | import id.barakkastudio.sample.ui.home.HomeViewModel 34 | import id.barakkastudio.sample.ui.search.section.SearchContent 35 | 36 | /** Created by github.com/im-o on 5/10/2023. */ 37 | 38 | @Composable 39 | fun SearchScreen( 40 | modifier: Modifier = Modifier, 41 | viewModel: HomeViewModel = hiltViewModel(), 42 | navigateToDetail: (Int) -> Unit, 43 | navigateBack: () -> Unit, 44 | ) { 45 | val query by viewModel.query 46 | val focusRequester = remember { FocusRequester() } 47 | val uiStateProduct by remember { viewModel.uiStateProduct }.collectAsState() 48 | 49 | LaunchedEffect(Unit) { 50 | focusRequester.requestFocus() 51 | } 52 | 53 | MainTemplate( 54 | modifier = modifier, 55 | topBar = { 56 | Row( 57 | modifier = modifier.background(MaterialTheme.colorScheme.primary), 58 | verticalAlignment = Alignment.CenterVertically, 59 | ) { 60 | IconButton( 61 | onClick = navigateBack, 62 | modifier = Modifier.padding(start = 8.dp) 63 | ) { 64 | Icon( 65 | imageVector = Icons.Default.ArrowBack, 66 | contentDescription = stringResource(id = R.string.back) 67 | ) 68 | } 69 | SearchBar( 70 | query = query, 71 | onQueryChange = viewModel::searchProductApiCall, 72 | modifier = Modifier 73 | .background(MaterialTheme.colorScheme.primary) 74 | .focusRequester(focusRequester), 75 | ) 76 | } 77 | }, 78 | content = { 79 | Box( 80 | contentAlignment = Alignment.Center, 81 | modifier = modifier 82 | .fillMaxSize() 83 | .background(Gray200) 84 | ) { 85 | when (uiStateProduct) { 86 | is UiState.Loading -> { 87 | viewModel.getProductsApiCall() 88 | ProgressProduct() 89 | } 90 | 91 | is UiState.Success -> { 92 | SearchContent( 93 | modifier = modifier, 94 | listProduct = (uiStateProduct as UiState.Success).data.products, 95 | navigateToDetail = navigateToDetail, 96 | ) 97 | } 98 | 99 | is UiState.Error -> { 100 | Text(text = stringResource(R.string.error_product), color = MaterialTheme.colorScheme.onSurface) 101 | } 102 | } 103 | } 104 | } 105 | ) 106 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/search/section/SearchContent.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.search.section 2 | 3 | import androidx.compose.animation.core.tween 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.PaddingValues 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.lazy.grid.GridCells 11 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 12 | import androidx.compose.foundation.lazy.grid.items 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.unit.dp 16 | import id.barakkastudio.core.data.model.Product 17 | import id.barakkastudio.sample.ui.component.EmptyProduct 18 | import id.barakkastudio.sample.ui.component.ProductItem 19 | 20 | /** Created by github.com/im-o on 5/10/2023. */ 21 | 22 | @OptIn(ExperimentalFoundationApi::class) 23 | @Composable 24 | fun SearchContent( 25 | modifier: Modifier, 26 | listProduct: MutableList?, 27 | navigateToDetail: (Int) -> Unit, 28 | ) { 29 | Column( 30 | modifier = modifier.fillMaxSize() 31 | ) { 32 | if (listProduct != null) { 33 | LazyVerticalGrid( 34 | columns = GridCells.Adaptive(140.dp), 35 | content = { 36 | items(listProduct, key = { it.id ?: -1 }) { product -> 37 | ProductItem( 38 | product = product, 39 | modifier = modifier 40 | .fillMaxWidth() 41 | .animateItemPlacement(tween(durationMillis = 100)) 42 | .clickable { 43 | navigateToDetail(product.id ?: return@clickable) 44 | } 45 | ) 46 | } 47 | }, contentPadding = PaddingValues(8.dp) 48 | ) 49 | if (listProduct.isEmpty()) EmptyProduct() 50 | } else EmptyProduct() 51 | } 52 | } -------------------------------------------------------------------------------- /features/sample/src/main/java/id/barakkastudio/sample/ui/splash/SplashScreen.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample.ui.splash 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.Spacer 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.foundation.layout.size 12 | import androidx.compose.foundation.shape.RoundedCornerShape 13 | import androidx.compose.material3.MaterialTheme 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.runtime.LaunchedEffect 17 | import androidx.compose.ui.Alignment 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.draw.clip 20 | import androidx.compose.ui.draw.shadow 21 | import androidx.compose.ui.layout.ContentScale 22 | import androidx.compose.ui.res.painterResource 23 | import androidx.compose.ui.res.stringResource 24 | import androidx.compose.ui.tooling.preview.Preview 25 | import id.barakkastudio.core.R 26 | import id.barakkastudio.core.ui.theme.JetShopeeTheme 27 | import id.barakkastudio.core.util.Dimens 28 | import kotlinx.coroutines.delay 29 | 30 | /** Created by github.com/im-o on 8/29/2024. */ 31 | 32 | @Composable 33 | fun SplashScreen( 34 | onTimeout: () -> Unit, modifier: Modifier = Modifier 35 | ) { 36 | LaunchedEffect(Unit) { 37 | delay(2500) 38 | onTimeout() 39 | } 40 | Box( 41 | modifier = modifier 42 | .fillMaxSize() 43 | .background(MaterialTheme.colorScheme.primary), contentAlignment = Alignment.Center 44 | ) { 45 | Column( 46 | modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally 47 | ) { 48 | Image( 49 | modifier = modifier 50 | .size(Dimens.dp120) 51 | .shadow(elevation = Dimens.dp1, shape = RoundedCornerShape(Dimens.dp12)) 52 | .clip(shape = RoundedCornerShape(Dimens.dp12)), 53 | painter = painterResource(id = R.drawable.logo_nebenginaja), 54 | contentDescription = stringResource(id = R.string.app_name), 55 | contentScale = ContentScale.Crop, 56 | ) 57 | Spacer(modifier = modifier.height(Dimens.dp16)) 58 | Text( 59 | text = stringResource(id = R.string.app_name), 60 | style = MaterialTheme.typography.headlineMedium, 61 | color = MaterialTheme.colorScheme.onPrimary 62 | ) 63 | } 64 | } 65 | } 66 | 67 | @Preview 68 | @Composable 69 | fun SplashScreenPreview() { 70 | JetShopeeTheme { 71 | SplashScreen(onTimeout = {}) 72 | } 73 | } -------------------------------------------------------------------------------- /features/sample/src/test/java/id/barakkastudio/sample/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package id.barakkastudio.sample 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/im-o/jetpack-compose-clean-architecture/ecea30212b228ed88e2a5f38b55ea4c9b107a246/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 12 07:17:22 SGT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "Jetpack Compose clean architecture" 16 | include ':app' 17 | include ':core' 18 | include ':features:sample' 19 | --------------------------------------------------------------------------------