├── .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 |
--------------------------------------------------------------------------------