diff --git a/.run/spotlessApply.run.xml b/.run/spotlessApply.run.xml
index 5c4dac83..5be23fba 100644
--- a/.run/spotlessApply.run.xml
+++ b/.run/spotlessApply.run.xml
@@ -18,6 +18,29 @@
true
true
false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9ed5d5ce..ef1ec016 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -118,6 +118,7 @@ dependencies {
implementation(project(":exoplayer"))
implementation(project(":cms"))
implementation(project(":popbackstack"))
+ implementation(project(":store"))
"baselineProfile"(project(":benchmarks"))
implementation(libs.kotlin.stdlib)
diff --git a/app/src/main/kotlin/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt b/app/src/main/kotlin/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt
index 6c5aae43..940b4aeb 100644
--- a/app/src/main/kotlin/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt
+++ b/app/src/main/kotlin/org/imaginativeworld/whynotcompose/ui/screens/NavGraphMain.kt
@@ -50,6 +50,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import androidx.navigation.navigation
+import com.example.store.ui.screen.StoreMainScreen
import org.imaginativeworld.whynotcompose.base.extensions.getJsonFromObj
import org.imaginativeworld.whynotcompose.base.extensions.getObjFromJson
import org.imaginativeworld.whynotcompose.base.extensions.navArg
@@ -239,6 +240,7 @@ sealed class TutorialsScreen(val route: String) {
data object TutorialTicTacToe : TutorialsScreen("tutorial/tic-tac-toe")
data object TutorialExoPlayer : TutorialsScreen("tutorial/exoplayer")
data object TutorialCMS : TutorialsScreen("tutorial/cms")
+ data object TutorialStore : TutorialsScreen("tutorial/store")
data object TutorialDeepLink : TutorialsScreen("tutorial/deep-link")
// ================================================================
@@ -982,6 +984,15 @@ private fun NavGraphBuilder.addTutorialIndexScreen(
)
}
+ composable(TutorialsScreen.TutorialStore.route) {
+ StoreMainScreen(
+ updateUiThemeMode = updateUiThemeMode,
+ goBack = {
+ navController.popBackStackOrIgnore()
+ }
+ )
+ }
+
composable(TutorialsScreen.TutorialDeepLink.route) {
DeepLinksScreen(
goBack = {
diff --git a/app/src/main/kotlin/org/imaginativeworld/whynotcompose/ui/screens/tutorial/index/TutorialList.kt b/app/src/main/kotlin/org/imaginativeworld/whynotcompose/ui/screens/tutorial/index/TutorialList.kt
index 0d969788..8ee29648 100644
--- a/app/src/main/kotlin/org/imaginativeworld/whynotcompose/ui/screens/tutorial/index/TutorialList.kt
+++ b/app/src/main/kotlin/org/imaginativeworld/whynotcompose/ui/screens/tutorial/index/TutorialList.kt
@@ -129,6 +129,12 @@ data class Tutorial(
route = TutorialsScreen.TutorialCMS,
level = TutorialLevel.Advanced
),
+ Tutorial(
+ name = "Store",
+ description = "Example of a Content Management System.",
+ route = TutorialsScreen.TutorialStore,
+ level = TutorialLevel.Advanced
+ ),
Tutorial(
name = "Deep Link",
description = "Example of Deep Link.",
diff --git a/app/src/main/res/drawable/store.jpg b/app/src/main/res/drawable/store.jpg
new file mode 100644
index 00000000..2ada3e6b
Binary files /dev/null and b/app/src/main/res/drawable/store.jpg differ
diff --git a/base/src/main/kotlin/org/imaginativeworld/whynotcompose/base/utils/Constants.kt b/base/src/main/kotlin/org/imaginativeworld/whynotcompose/base/utils/Constants.kt
index 53263537..bd48c2b6 100644
--- a/base/src/main/kotlin/org/imaginativeworld/whynotcompose/base/utils/Constants.kt
+++ b/base/src/main/kotlin/org/imaginativeworld/whynotcompose/base/utils/Constants.kt
@@ -34,6 +34,7 @@ object Constants {
*/
const val SERVER_ENDPOINT = "https://imaginativeworld.org"
const val CMS_SERVER_ENDPOINT = "https://gorest.co.in/public"
+ const val STORE_SERVER_ENDPOINT = "https://fakestoreapi.com"
/**
* For MyNotificationOpenedHandler
diff --git a/common-ui-compose/src/main/res/drawable/store.jpg b/common-ui-compose/src/main/res/drawable/store.jpg
new file mode 100644
index 00000000..2ada3e6b
Binary files /dev/null and b/common-ui-compose/src/main/res/drawable/store.jpg differ
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 15f329a0..4047bae0 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -104,6 +104,7 @@ swiperefreshlayout = "1.1.0"
mlkitBarcodeScanning = "17.3.0"
cameraX = "1.4.1"
haze = "1.2.2"
+material = "1.8.0"
[libraries]
@@ -236,6 +237,10 @@ mlkit-barcode-scanning = { group = "com.google.mlkit", name = "barcode-scanning"
haze = { group = "dev.chrisbanes.haze", name = "haze", version.ref = "haze" }
haze-materials = { group = "dev.chrisbanes.haze", name = "haze-materials", version.ref = "haze" }
+androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+
+
# The following line is optional, as the core library is included indirectly by camera-camera2
camerax-core = { group = "androidx.camera", name = "camera-core", version.ref = "cameraX" }
camerax-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "cameraX" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 57fdea9b..6e877b70 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -56,3 +56,4 @@ include(":exoplayer")
include(":cms")
include(":popbackstack")
include(":benchmarks")
+include(":store")
diff --git a/store/.gitignore b/store/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/store/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/store/build.gradle.kts b/store/build.gradle.kts
new file mode 100644
index 00000000..de7bea4d
--- /dev/null
+++ b/store/build.gradle.kts
@@ -0,0 +1,137 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.compose)
+ alias(libs.plugins.ksp)
+}
+
+android {
+ namespace = "org.imaginativeworld.whynotcompose.store"
+ compileSdk = BuildConfigConst.compileSdk
+
+ defaultConfig {
+ minSdk = BuildConfigConst.minSdk
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = "17"
+
+ freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn"
+
+ // Enable experimental compose APIs
+ freeCompilerArgs =
+ freeCompilerArgs + "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api"
+ freeCompilerArgs =
+ freeCompilerArgs + "-opt-in=androidx.compose.animation.ExperimentalAnimationApi"
+ freeCompilerArgs =
+ freeCompilerArgs + "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi"
+ freeCompilerArgs =
+ freeCompilerArgs + "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi"
+ }
+
+ ksp {
+ arg("room.schemaLocation", "$projectDir/schemas")
+ }
+
+ buildFeatures {
+ buildConfig = true
+ compose = true
+ }
+}
+
+dependencies {
+ implementation(project(":base"))
+ implementation(project(":common-ui-compose"))
+
+ implementation(libs.kotlin.stdlib)
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.swiperefreshlayout)
+
+ // ----------------------------------------------------------------
+ // Compose
+ // ----------------------------------------------------------------
+ implementation(platform(libs.androidx.compose.bom))
+
+ implementation(libs.androidx.compose.ui)
+ implementation(libs.androidx.compose.ui.util)
+ // Tooling support (Previews, etc.)
+ debugImplementation(libs.androidx.compose.ui.tooling)
+ implementation(libs.androidx.compose.ui.tooling.preview)
+ // Animation
+ implementation(libs.androidx.compose.animation)
+ // Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
+ implementation(libs.androidx.compose.foundation)
+ implementation(libs.androidx.compose.foundation.layout)
+ // Material Design
+ implementation(libs.androidx.compose.material3)
+ implementation(libs.androidx.compose.material3.windowSizeClass)
+ // Material design icons
+ implementation(libs.androidx.compose.material.iconsCore)
+ implementation(libs.androidx.compose.material.iconsExtended)
+ // Integration with observables
+ implementation(libs.androidx.compose.runtime)
+ implementation(libs.androidx.compose.runtime.livedata)
+ implementation(libs.androidx.compose.runtime.tracing)
+ // compose Navigation Component
+ implementation(libs.androidx.navigation.compose)
+ // Constraint Layout
+ implementation(libs.androidx.constraintlayout.compose)
+ // Integration with activities
+ implementation(libs.androidx.activity.compose)
+
+ // Jetpack compose Integration for ViewModel
+ implementation(libs.androidx.lifecycle.viewmodel.compose)
+
+ // Paging
+ implementation(libs.androidx.paging.compose)
+
+ // ----------------------------------------------------------------
+
+ // Timber
+ implementation(libs.timber)
+
+ // Hilt
+ implementation(libs.hilt.android)
+ ksp(libs.hilt.compiler)
+ implementation(libs.androidx.hilt.navigation.compose)
+
+ // Retrofit
+ implementation(libs.retrofit.core)
+ implementation(libs.okhttp.logging)
+ implementation("com.squareup.retrofit2:retrofit:2.9.0")
+ implementation("com.squareup.retrofit2:converter-gson:2.9.0")
+
+ // Moshi
+ implementation(libs.retrofit.moshi)
+ implementation(libs.moshi.kotlin)
+ ksp(libs.moshi.kotlin.codegen)
+
+ // Room Persistence Library
+ implementation(libs.room.runtime)
+ ksp(libs.room.compiler)
+
+ // Room: Kotlin Extensions and Coroutines support for Room
+ implementation(libs.room.ktx)
+
+ // Coil
+ implementation(libs.coil.compose)
+ implementation(libs.coil.svg)
+ implementation(libs.coil.network)
+
+ // Serialization
+ implementation(libs.kotlinx.serialization.json)
+
+ // Haze
+ implementation(libs.haze)
+ implementation(libs.haze.materials)
+
+ implementation(libs.androidx.material3)
+ implementation(libs.material)
+}
diff --git a/store/src/main/AndroidManifest.xml b/store/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..44008a43
--- /dev/null
+++ b/store/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/store/src/main/kotlin/com/example/store/datasource/ProductPagingSource.kt b/store/src/main/kotlin/com/example/store/datasource/ProductPagingSource.kt
new file mode 100644
index 00000000..0afb0c35
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/datasource/ProductPagingSource.kt
@@ -0,0 +1,62 @@
+package com.example.store.datasource
+
+import androidx.paging.PagingSource
+import androidx.paging.PagingState
+import com.example.store.models.product.Product
+import com.example.store.repositories.ProductRepository
+import okio.IOException
+import org.imaginativeworld.whynotcompose.base.network.ApiException
+import retrofit2.HttpException
+
+class ProductPagingSource(
+ private val repository: ProductRepository
+) : PagingSource() {
+
+ override suspend fun load(params: LoadParams): LoadResult {
+ val pagePosition = params.key ?: 1
+
+ return try {
+ val products = repository.getProducts(
+ pagePosition
+ )
+
+ if (products == null) {
+ LoadResult.Error(ApiException("No data returned!"))
+ } else {
+ val nextKey = if (products.isEmpty()) {
+ null
+ } else {
+ pagePosition + 1
+ }
+
+ LoadResult.Page(
+ data = products,
+ prevKey = if (pagePosition == 1L) null else pagePosition - 1,
+ nextKey = nextKey
+ )
+ }
+ } catch (exception: IOException) {
+ return LoadResult.Error(exception)
+ } catch (exception: HttpException) {
+ return LoadResult.Error(exception)
+ } catch (exception: ApiException) {
+ return LoadResult.Error(exception)
+ }
+ }
+
+ // The refresh key is used for subsequent refresh calls to PagingSource.load after the initial load
+ override fun getRefreshKey(state: PagingState): Long? {
+ // We need to get the previous key (or next key if previous is null) of the page
+ // that was closest to the most recently accessed index.
+ // Anchor position is the most recently accessed index
+ return state.anchorPosition?.let { anchorPosition ->
+ state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
+ ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
+
+ // For cursor paging use this:
+ // https://stackoverflow.com/questions/67691903/how-to-implement-pagingsource-getrefreshkey-for-cursor-based-pagination-androi
+ // val anchorPageIndex = state.pages.indexOf(state.closestPageToPosition(anchorPosition))
+ // state.pages.getOrNull(anchorPageIndex + 1)?.prevKey ?: state.pages.getOrNull(anchorPageIndex - 1)?.nextKey
+ }
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/di/StoreAppModule.kt b/store/src/main/kotlin/com/example/store/di/StoreAppModule.kt
new file mode 100644
index 00000000..87d414f3
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/di/StoreAppModule.kt
@@ -0,0 +1,40 @@
+package com.example.store.di
+
+import com.example.store.network.api.CategoriesApiInterface
+import com.example.store.network.api.ProductDetailsApiInterface
+import com.example.store.network.api.ProductsApiInterface
+import com.squareup.moshi.Moshi
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Named
+import javax.inject.Singleton
+import org.imaginativeworld.whynotcompose.base.network.ApiClient
+import org.imaginativeworld.whynotcompose.base.utils.Constants
+import retrofit2.Retrofit
+
+@InstallIn(SingletonComponent::class)
+@Module
+class StoreAppModule {
+ @Singleton
+ @Provides
+ @Named("STORE")
+ fun provideRetrofit(moshi: Moshi): Retrofit = ApiClient.getRetrofit(
+ moshi,
+ Constants.STORE_SERVER_ENDPOINT + "/",
+ mapOf()
+ )
+
+ @Singleton
+ @Provides
+ fun provideProductApiInterface(@Named("STORE") retrofit: Retrofit): ProductsApiInterface = retrofit.create(ProductsApiInterface::class.java)
+
+ @Singleton
+ @Provides
+ fun provideProductDetailsApiInterface(@Named("STORE") retrofit: Retrofit): ProductDetailsApiInterface = retrofit.create(ProductDetailsApiInterface::class.java)
+
+ @Singleton
+ @Provides
+ fun provideCategoriesApiInterface(@Named("STORE") retrofit: Retrofit): CategoriesApiInterface = retrofit.create(CategoriesApiInterface::class.java)
+}
diff --git a/store/src/main/kotlin/com/example/store/models/categorie/Categories.kt b/store/src/main/kotlin/com/example/store/models/categorie/Categories.kt
new file mode 100644
index 00000000..06314cb2
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/models/categorie/Categories.kt
@@ -0,0 +1,3 @@
+package com.example.store.models.categorie
+
+class Categories : ArrayList()
diff --git a/store/src/main/kotlin/com/example/store/models/categorie/Category.kt b/store/src/main/kotlin/com/example/store/models/categorie/Category.kt
new file mode 100644
index 00000000..aa69a27c
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/models/categorie/Category.kt
@@ -0,0 +1,14 @@
+package com.example.store.models.categorie
+
+data class Category(
+ val id: Int,
+ val name: String,
+ val imageUrl: String
+)
+
+val categories = listOf(
+ Category(id = 1, name = "Electron-ics", imageUrl = "https://fakestoreapi.com/img/61IBBVJvSDL._AC_SY879_.jpg"),
+ Category(id = 2, name = "Jewelery", imageUrl = "https://fakestoreapi.com/img/71pWzhdJNwL._AC_UL640_QL65_ML3_.jpg"),
+ Category(id = 3, name = "Men's Clothing", imageUrl = "https://fakestoreapi.com/img/71pWzhdJNwL._AC_UL640_QL65_ML3_.jpg"),
+ Category(id = 4, name = "Women's Clothing", imageUrl = "https://fakestoreapi.com/img/61IBBVJvSDL._AC_SY879_.jpg")
+)
diff --git a/store/src/main/kotlin/com/example/store/models/product/Product.kt b/store/src/main/kotlin/com/example/store/models/product/Product.kt
new file mode 100644
index 00000000..92162aac
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/models/product/Product.kt
@@ -0,0 +1,24 @@
+package com.example.store.models.product
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class Product(
+ @Json(name = "category")
+ val category: String,
+ @Json(name = "description")
+ val description: String,
+ @Json(name = "id")
+ val id: Int,
+ @Json(name = "image")
+ val image: String,
+ @Json(name = "price")
+ val price: Double,
+ @Json(name = "rating")
+ val rating: Rating,
+ @Json(name = "title")
+ val title: String,
+ @Json(name = "quantity")
+ var quantity: Int? = null
+)
diff --git a/store/src/main/kotlin/com/example/store/models/product/ProductsResponse.kt b/store/src/main/kotlin/com/example/store/models/product/ProductsResponse.kt
new file mode 100644
index 00000000..23f244af
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/models/product/ProductsResponse.kt
@@ -0,0 +1,3 @@
+package com.example.store.models.product
+
+class ProductsResponse : ArrayList()
\ No newline at end of file
diff --git a/store/src/main/kotlin/com/example/store/models/product/Rating.kt b/store/src/main/kotlin/com/example/store/models/product/Rating.kt
new file mode 100644
index 00000000..a2bf6c2b
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/models/product/Rating.kt
@@ -0,0 +1,12 @@
+package com.example.store.models.product
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class Rating(
+ @Json(name = "count")
+ val count: Int,
+ @Json(name = "rate")
+ val rate: Double
+)
diff --git a/store/src/main/kotlin/com/example/store/network/api/CategoriesApiInterface.kt b/store/src/main/kotlin/com/example/store/network/api/CategoriesApiInterface.kt
new file mode 100644
index 00000000..37c9a881
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/network/api/CategoriesApiInterface.kt
@@ -0,0 +1,10 @@
+package com.example.store.network.api
+
+import retrofit2.Response
+import retrofit2.http.GET
+
+interface CategoriesApiInterface {
+
+ @GET("products/categories")
+ suspend fun getCategories(): Response>
+}
diff --git a/store/src/main/kotlin/com/example/store/network/api/ProductDetailsApiInterface.kt b/store/src/main/kotlin/com/example/store/network/api/ProductDetailsApiInterface.kt
new file mode 100644
index 00000000..4f1fab5b
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/network/api/ProductDetailsApiInterface.kt
@@ -0,0 +1,12 @@
+package com.example.store.network.api
+
+import com.example.store.models.product.Product
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+
+interface ProductDetailsApiInterface {
+
+ @GET("products/{id}")
+ suspend fun getProductDetails(@Path("id") id: Int): Response
+}
diff --git a/store/src/main/kotlin/com/example/store/network/api/ProductsApiInterface.kt b/store/src/main/kotlin/com/example/store/network/api/ProductsApiInterface.kt
new file mode 100644
index 00000000..9f5518eb
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/network/api/ProductsApiInterface.kt
@@ -0,0 +1,12 @@
+package com.example.store.network.api
+
+import com.example.store.models.product.Product
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface ProductsApiInterface {
+
+ @GET("products")
+ suspend fun getProducts(@Query("page") page: Long): Response>
+}
diff --git a/store/src/main/kotlin/com/example/store/repositories/CategoriesRepository.kt b/store/src/main/kotlin/com/example/store/repositories/CategoriesRepository.kt
new file mode 100644
index 00000000..ef4a8ade
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/repositories/CategoriesRepository.kt
@@ -0,0 +1,20 @@
+package com.example.store.repositories
+
+import android.content.Context
+import com.example.store.network.api.CategoriesApiInterface
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.imaginativeworld.whynotcompose.base.network.SafeApiRequest
+
+class CategoriesRepository @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val categoriesApi: CategoriesApiInterface
+) {
+ suspend fun getCategories() = withContext(Dispatchers.IO) {
+ SafeApiRequest.apiRequest(context) {
+ categoriesApi.getCategories()
+ }
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/repositories/ProductRepository.kt b/store/src/main/kotlin/com/example/store/repositories/ProductRepository.kt
new file mode 100644
index 00000000..1558699f
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/repositories/ProductRepository.kt
@@ -0,0 +1,30 @@
+package com.example.store.repositories
+
+import android.content.Context
+import com.example.store.network.api.ProductDetailsApiInterface
+import com.example.store.network.api.ProductsApiInterface
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.imaginativeworld.whynotcompose.base.network.SafeApiRequest
+
+class ProductRepository @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val productApi: ProductsApiInterface,
+
+ private val productDetailsApi: ProductDetailsApiInterface
+
+) {
+ suspend fun getProducts(page: Long) = withContext(Dispatchers.IO) {
+ SafeApiRequest.apiRequest(context) {
+ productApi.getProducts(page)
+ }
+ }
+
+ suspend fun getProductsId(id: Int) = withContext(Dispatchers.IO) {
+ SafeApiRequest.apiRequest(context) {
+ productDetailsApi.getProductDetails(id)
+ }
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/theme/Color.kt b/store/src/main/kotlin/com/example/store/theme/Color.kt
new file mode 100644
index 00000000..b46315b1
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/theme/Color.kt
@@ -0,0 +1,11 @@
+package com.example.store.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
diff --git a/store/src/main/kotlin/com/example/store/theme/Theme.kt b/store/src/main/kotlin/com/example/store/theme/Theme.kt
new file mode 100644
index 00000000..6fcb4c75
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/theme/Theme.kt
@@ -0,0 +1,29 @@
+package com.example.store.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColorScheme()
+
+private val LightColorPalette = lightColorScheme()
+
+@Composable
+fun StoreAppTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit
+) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(
+ colorScheme = colors,
+ typography = Typography,
+ content = content
+ )
+}
diff --git a/store/src/main/kotlin/com/example/store/theme/Type.kt b/store/src/main/kotlin/com/example/store/theme/Type.kt
new file mode 100644
index 00000000..50801834
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/theme/Type.kt
@@ -0,0 +1,34 @@
+package com.example.store.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+ /* Other default text styles to override
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ )
+ */
+)
diff --git a/store/src/main/kotlin/com/example/store/ui/compositions/CartCard.kt b/store/src/main/kotlin/com/example/store/ui/compositions/CartCard.kt
new file mode 100644
index 00000000..a04dd4d0
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/compositions/CartCard.kt
@@ -0,0 +1,180 @@
+package com.example.store.ui.compositions
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Remove
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import coil3.compose.AsyncImage
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.screen.productdetails.Product
+import com.example.store.ui.screen.productdetails.dummyProducts
+
+@Composable
+fun CartItemCard(
+ product: Product,
+ onQuantityChange: (Int) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ var quantity by remember { mutableIntStateOf(2) }
+ val totalPrice = product.price * quantity
+
+ Card(
+ modifier = modifier
+ .fillMaxWidth(),
+ elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ AsyncImage(
+ model = product.imageUrl,
+ contentDescription = null,
+ modifier = Modifier
+ .width(80.dp)
+ .height(80.dp)
+ .clip(RoundedCornerShape(12.dp))
+ .background(Color.White)
+ )
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ ) {
+ Text(
+ product.title,
+ style = MaterialTheme.typography.bodyLarge,
+ maxLines = 1,
+ fontWeight = FontWeight.Bold
+ )
+ Text(
+ text = "$${product.price}",
+ style = MaterialTheme.typography.bodyMedium,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+
+ HorizontalDivider()
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.weight(.4f)
+ ) {
+ IconButton(
+ onClick = {
+ if (quantity > 1) {
+ quantity--
+ onQuantityChange(quantity)
+ }
+ }
+ ) {
+ Box(
+ modifier = Modifier
+ .clip(RoundedCornerShape(8.dp))
+ .background(MaterialTheme.colorScheme.primary.copy(alpha = .7f))
+ .padding(4.dp),
+// .border(1.dp, MaterialTheme.colorScheme.surface),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ Icons.Default.Remove,
+ contentDescription = "Decrease",
+ tint = MaterialTheme.colorScheme.surface
+ )
+ }
+ }
+
+ Text(
+ text = "$quantity",
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.padding(horizontal = 8.dp)
+ )
+
+ IconButton(
+ onClick = {
+ quantity++
+ onQuantityChange(quantity)
+ }
+ ) {
+ Box(
+ modifier = Modifier
+ .clip(RoundedCornerShape(8.dp))
+ .background(MaterialTheme.colorScheme.primary.copy(alpha = .7f))
+ .padding(4.dp)
+ ) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = "Increase",
+ tint = MaterialTheme.colorScheme.surface
+ )
+ }
+ }
+ }
+
+ Text(
+ text = "$$totalPrice",
+ style = MaterialTheme.typography.bodyMedium,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.End,
+ color = MaterialTheme.colorScheme.error,
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ .weight(.5f)
+ )
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun CartItemCardPreview() {
+ StoreAppTheme {
+ CartItemCard(
+ product = dummyProducts[0],
+ onQuantityChange = {},
+ modifier = Modifier
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/compositions/CategoryCard.kt b/store/src/main/kotlin/com/example/store/ui/compositions/CategoryCard.kt
new file mode 100644
index 00000000..90e6f16d
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/compositions/CategoryCard.kt
@@ -0,0 +1,90 @@
+package com.example.store.ui.compositions
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import com.example.store.models.categorie.Category
+import com.example.store.theme.StoreAppTheme
+import dev.chrisbanes.haze.HazeState
+import dev.chrisbanes.haze.hazeEffect
+import dev.chrisbanes.haze.hazeSource
+import dev.chrisbanes.haze.materials.HazeMaterials
+
+@Composable
+fun CategoryCard(
+ category: Category,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {}
+) {
+ val hazeState = remember { HazeState() }
+ Box(
+ modifier = modifier
+ .size(150.dp)
+ .clip(RoundedCornerShape(12.dp))
+ .hazeSource(state = hazeState)
+ .clickable {
+ onClick()
+ }
+ ) {
+ Image(
+ painter = painterResource(id = org.imaginativeworld.whynotcompose.common.compose.R.drawable.store),
+ contentDescription = "",
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.matchParentSize()
+ )
+
+ Box(
+ modifier = Modifier
+ .wrapContentSize()
+ .align(Alignment.Center)
+ .padding(16.dp)
+ .clip(RoundedCornerShape(20))
+ .background(MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.9f))
+ .hazeEffect(state = hazeState, style = HazeMaterials.ultraThin()),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = category.name,
+ style = MaterialTheme.typography.headlineMedium,
+ fontWeight = FontWeight.ExtraBold,
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colorScheme.surface,
+ modifier = Modifier
+ .padding(12.dp)
+ .align(Alignment.Center)
+ )
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun CategoryCardPreview() {
+ StoreAppTheme {
+ CategoryCard(
+ category = Category(
+ id = 1,
+ name = " Women's Clothing",
+ imageUrl = ""
+ )
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/compositions/KeyValue.kt b/store/src/main/kotlin/com/example/store/ui/compositions/KeyValue.kt
new file mode 100644
index 00000000..e8e89da4
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/compositions/KeyValue.kt
@@ -0,0 +1,36 @@
+package com.example.store.ui.compositions
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun KeyValue(
+ title: String,
+ value: String
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ Text(
+ text = value,
+ style = MaterialTheme.typography.bodyMedium,
+ fontWeight = FontWeight.Medium
+ )
+ }
+}
\ No newline at end of file
diff --git a/store/src/main/kotlin/com/example/store/ui/compositions/OrderCard.kt b/store/src/main/kotlin/com/example/store/ui/compositions/OrderCard.kt
new file mode 100644
index 00000000..58895f00
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/compositions/OrderCard.kt
@@ -0,0 +1,136 @@
+package com.example.store.ui.compositions
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.screen.order.Order
+import com.example.store.ui.screen.productdetails.Product
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun OrderCard(
+ order: Order,
+ modifier: Modifier = Modifier
+) {
+ var isExpanded by remember { mutableStateOf(true) }
+
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(12.dp))
+ .background(MaterialTheme.colorScheme.onBackground)
+ .padding(16.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = "#${order.id}",
+ style = MaterialTheme.typography.bodyMedium,
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.surface
+ )
+ Text(
+ text = order.date,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.surface
+ )
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(
+ onClick = { isExpanded = !isExpanded },
+ modifier = Modifier.fillMaxWidth(),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainer
+ ),
+ shape = MaterialTheme.shapes.medium
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = "Total ${order.products.size} ${
+ if (order.products.size > 1) {
+ "products"
+ } else {
+ "product"
+ }
+ }",
+ color = MaterialTheme.colorScheme.primary
+ )
+ Icon(
+ imageVector = if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
+ contentDescription = "Expand",
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+
+ if (isExpanded) {
+ Spacer(modifier = Modifier.height(8.dp))
+ Column {
+ order.products.forEach { product ->
+ OrderProductItem(product = product)
+ Spacer(modifier = Modifier.height(4.dp))
+ }
+ }
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun OrderCardPreview() {
+ StoreAppTheme {
+ OrderCard(
+ order = Order(
+ id = 7,
+ date = "01 Mar 2020",
+ products = listOf(
+ Product(
+ category = "Backpack",
+ id = 1,
+ title = "Fits 15 Laptops",
+ imageUrl = "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg",
+ price = 109.95,
+ rating = 4.5,
+ reviewCount = 120,
+ description = "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday",
+ quantity = 10
+ )
+ )
+ )
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/compositions/OrderProductItem.kt b/store/src/main/kotlin/com/example/store/ui/compositions/OrderProductItem.kt
new file mode 100644
index 00000000..7798b680
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/compositions/OrderProductItem.kt
@@ -0,0 +1,107 @@
+package com.example.store.ui.compositions
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.screen.productdetails.Product
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun OrderProductItem(
+ product: Product,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(id = org.imaginativeworld.whynotcompose.common.compose.R.drawable.store),
+ contentDescription = "",
+ modifier = Modifier
+ .size(40.dp)
+ .clip(RoundedCornerShape(8.dp)),
+ contentScale = ContentScale.Crop
+ )
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = product.title,
+ style = MaterialTheme.typography.bodyMedium,
+ maxLines = 1,
+ color = MaterialTheme.colorScheme.outline,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = buildAnnotatedString {
+ append("$${product.price} × ")
+ withStyle(SpanStyle(color = MaterialTheme.colorScheme.error)) {
+ append("${product.quantity}")
+ }
+ },
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.outline,
+ )
+
+ Text(
+ text = "$${"%.2f".format(product.price * product.quantity)}",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.error,
+ fontWeight = FontWeight.Bold,
+ )
+ }
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun OrderProductItemPreview() {
+ StoreAppTheme {
+ OrderProductItem(
+ product = Product(
+ category = "Jacket",
+ id = 3,
+ title = "Mens Cotton Jacket",
+ imageUrl = "https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg",
+ price = 55.99,
+ rating = 4.5,
+ reviewCount = 500,
+ description = "Great outerwear jackets for Spring/Autumn/Winter, suitable for many occasions, such as working, hiking, camping, mountain/rock climbing, cycling.",
+ quantity = 5
+ )
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/compositions/ProductItem.kt b/store/src/main/kotlin/com/example/store/ui/compositions/ProductItem.kt
new file mode 100644
index 00000000..e8a7488d
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/compositions/ProductItem.kt
@@ -0,0 +1,197 @@
+package com.example.store.ui.compositions
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Remove
+import androidx.compose.material.icons.filled.Star
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import coil3.compose.AsyncImage
+import com.example.store.models.product.Product
+import com.example.store.theme.StoreAppTheme
+
+@Composable
+fun ProductItem(
+ product: Product,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ var quantity by remember { mutableIntStateOf(0) }
+
+ Card(
+ modifier = modifier
+ .clickable { onClick() },
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainer
+ ),
+ elevation = CardDefaults.cardElevation(4.dp),
+ shape = RoundedCornerShape(8.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ AsyncImage(
+ model = product.image,
+ contentDescription = "Product Image",
+ modifier = Modifier
+ .width(150.dp)
+ .height(150.dp)
+ .clip(RoundedCornerShape(12.dp))
+ .background(Color.White)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = product.title,
+ style = MaterialTheme.typography.bodyMedium,
+ textAlign = TextAlign.Start,
+ fontWeight = FontWeight.Bold,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier
+ .fillMaxWidth()
+ )
+ Text(
+ text = "$${product.price}",
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.error,
+ textAlign = TextAlign.Start,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Icon(
+ Icons.Default.Star,
+ contentDescription = "Rating",
+ tint = Color.Yellow.copy(alpha = .5f)
+ )
+ Text(
+ text = ("${product.rating.rate} (${product.rating.count})"),
+ modifier = Modifier.padding(start = 4.dp),
+ color = MaterialTheme.colorScheme.outline
+ )
+ }
+ }
+
+ HorizontalDivider()
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ IconButton(
+ onClick = { if (quantity > 0) quantity-- },
+ modifier = Modifier
+ ) {
+ Box(
+ modifier = Modifier
+ .clip(RoundedCornerShape(8.dp))
+ .background(MaterialTheme.colorScheme.primary.copy(alpha = .7f))
+ .padding(4.dp),
+// .border(1.dp, MaterialTheme.colorScheme.surface),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ Icons.Default.Remove,
+ contentDescription = "Decrease",
+ tint = MaterialTheme.colorScheme.surface
+ )
+ }
+ }
+
+ Text(
+ text = "$quantity",
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.padding(horizontal = 8.dp)
+ )
+
+ IconButton(
+ onClick = { quantity++ }
+ ) {
+ Box(
+ modifier = Modifier
+ .clip(RoundedCornerShape(8.dp))
+ .background(MaterialTheme.colorScheme.primary.copy(alpha = .7f))
+ .padding(4.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = "Increase",
+ tint = MaterialTheme.colorScheme.surface
+ )
+ }
+ }
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun PreviewProductDetailsScreen() {
+ StoreAppTheme {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ ) {
+// ProductItem(
+// onClick = {},
+// product = Product(
+// category = "jewelery",
+// id = 1,
+// title = "WD 2TB Elements Portable External...",
+// imageUrl = "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg",
+// price = 64.00,
+// rating = 3.3,
+// reviewCount = 203,
+// description = "USB 3.0 and USB 2.0 Compatibility Fast data transfers Improve PC Performance High Capacity;...",
+// quantity = 0
+// )
+// )
+ }
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/compositions/StoreAppBar.kt b/store/src/main/kotlin/com/example/store/ui/compositions/StoreAppBar.kt
new file mode 100644
index 00000000..cb17d4a2
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/compositions/StoreAppBar.kt
@@ -0,0 +1,81 @@
+package com.example.store.ui.compositions
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.rounded.ArrowBack
+import androidx.compose.material.icons.rounded.BrightnessAuto
+import androidx.compose.material.icons.rounded.DarkMode
+import androidx.compose.material.icons.rounded.LightMode
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import com.example.store.theme.StoreAppTheme
+import org.imaginativeworld.whynotcompose.base.models.UIThemeMode
+import org.imaginativeworld.whynotcompose.base.utils.UIThemeController
+
+@Composable
+fun StoreAppBar(
+ modifier: Modifier = Modifier,
+ title: String = "Store Overflow",
+ goBack: () -> Unit = {},
+ toggleUIMode: () -> Unit = {}
+) {
+ val uiThemeMode by UIThemeController.uiThemeMode.collectAsState()
+
+ CenterAlignedTopAppBar(
+ modifier = modifier,
+ title = {
+ Text(
+ title,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ fontWeight = FontWeight.Bold
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = {
+ goBack()
+ }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
+ contentDescription = "Go back"
+ )
+ }
+ },
+ actions = {
+ IconButton(onClick = {
+ toggleUIMode()
+ }) {
+ Icon(
+ imageVector = when (uiThemeMode) {
+ UIThemeMode.AUTO -> Icons.Rounded.BrightnessAuto
+ UIThemeMode.LIGHT -> Icons.Rounded.LightMode
+ UIThemeMode.DARK -> Icons.Rounded.DarkMode
+ },
+ contentDescription = ""
+ )
+ }
+ },
+ colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainer
+ )
+ )
+}
+
+@PreviewLightDark
+@Composable
+private fun StoreAppBarPreview() {
+ StoreAppTheme {
+ StoreAppBar()
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/compositions/TabScreen.kt b/store/src/main/kotlin/com/example/store/ui/compositions/TabScreen.kt
new file mode 100644
index 00000000..fa5bad5e
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/compositions/TabScreen.kt
@@ -0,0 +1,150 @@
+package com.example.store.ui.compositions
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Category
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.ShoppingCart
+import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import com.example.store.models.categorie.Category
+import com.example.store.models.product.Product
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.screen.cart.CartScreen
+import com.example.store.ui.screen.categories.CategoriesScreen
+import com.example.store.ui.screen.home.StoreHomeScreen
+import com.example.store.ui.screen.home.StoreHomeScreenViewModel
+import com.example.store.ui.screen.productdetails.dummyProducts
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun TabScreen(
+ userName: String,
+ onOrderClick: () -> Unit,
+ onSignOutClick: () -> Unit,
+ goBack: () -> Unit,
+ toggleUIMode: () -> Unit,
+ onCheckout: () -> Unit,
+ onProductClick: (Product) -> Unit,
+ onCategoryClick: (Category) -> Unit
+) {
+ val navController = rememberNavController()
+ val navBackStackEntry by navController.currentBackStackEntryAsState()
+ val currentRoute = navBackStackEntry?.destination?.route
+
+ Scaffold(
+ bottomBar = {
+ NavigationBar {
+ bottomNavigationItems().forEach { navigationItem ->
+ NavigationBarItem(
+ selected = currentRoute == navigationItem.route,
+ label = { Text(navigationItem.label) },
+ icon = {
+ Icon(
+ navigationItem.icon,
+ contentDescription = navigationItem.label
+ )
+ },
+ onClick = {
+ if (currentRoute != navigationItem.route) {
+ navController.navigate(navigationItem.route) {
+ popUpTo(navController.graph.findStartDestination().id) {
+ saveState = true
+ }
+ launchSingleTop = true
+ restoreState = true
+ }
+ }
+ }
+ )
+ }
+ }
+ },
+ contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)
+ ) { paddingValues ->
+ NavHost(
+ navController = navController,
+ startDestination = Screens.Home.route,
+ modifier = Modifier.padding(paddingValues)
+ ) {
+ composable(Screens.Home.route) {
+ val viewModel: StoreHomeScreenViewModel = hiltViewModel()
+ StoreHomeScreen(
+ viewModel = viewModel,
+ userName = userName,
+ onOrderClick = onOrderClick,
+ onSignOutClick = onSignOutClick,
+ toggleUIMode = toggleUIMode,
+ onProductClick = onProductClick,
+ onCategoryClick = onCategoryClick
+ )
+ }
+ composable(Screens.Categories.route) {
+ CategoriesScreen(
+ goBack = goBack,
+ toggleUIMode = toggleUIMode,
+ onCategoryClick = onCategoryClick
+ )
+ }
+ composable(Screens.Cart.route) {
+ CartScreen(
+ products = dummyProducts,
+ goBack = goBack,
+ onCheckout = onCheckout,
+ toggleUIMode = toggleUIMode
+ )
+ }
+ }
+ }
+}
+
+data class NavigationItem(
+ val label: String,
+ val icon: ImageVector,
+ val route: String
+)
+
+fun bottomNavigationItems(): List = listOf(
+ NavigationItem("Home", Icons.Filled.Home, Screens.Home.route),
+ NavigationItem("Categories", Icons.Filled.Category, Screens.Categories.route),
+ NavigationItem("Cart", Icons.Filled.ShoppingCart, Screens.Cart.route)
+)
+
+sealed class Screens(val route: String) {
+ object Home : Screens("home")
+ object Categories : Screens("categories")
+ object Cart : Screens("cart")
+}
+
+@PreviewLightDark
+@Composable
+private fun TabScreenPreview() {
+ StoreAppTheme {
+ TabScreen(
+ userName = "John Doe",
+ onOrderClick = {},
+ onSignOutClick = {},
+ goBack = {},
+ toggleUIMode = {},
+ onCheckout = {},
+ onProductClick = {},
+ onCategoryClick = {}
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/StoreMainScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/StoreMainScreen.kt
new file mode 100644
index 00000000..3ed97780
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/StoreMainScreen.kt
@@ -0,0 +1,68 @@
+package com.example.store.ui.screen
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.compose.rememberNavController
+import com.example.store.theme.StoreAppTheme
+import org.imaginativeworld.whynotcompose.base.models.UIThemeMode
+import org.imaginativeworld.whynotcompose.base.utils.UIThemeController
+
+@Composable
+fun StoreMainScreen(
+ updateUiThemeMode: (UIThemeMode) -> Unit,
+ goBack: () -> Unit
+) {
+ val uiThemeMode by UIThemeController.uiThemeMode.collectAsState()
+ val isSystemInDarkTheme = isSystemInDarkTheme()
+
+ val isDarkMode by remember(isSystemInDarkTheme) {
+ derivedStateOf {
+ when (uiThemeMode) {
+ UIThemeMode.AUTO -> isSystemInDarkTheme
+ UIThemeMode.LIGHT -> false
+ UIThemeMode.DARK -> true
+ }
+ }
+ }
+
+ StoreAppTheme(
+ darkTheme = isDarkMode
+ ) {
+ StoreMainScreenSkeleton(
+ updateUiThemeMode = updateUiThemeMode,
+ goBack = goBack
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun StoreMainScreenSkeletonPreview() {
+ StoreAppTheme {
+ StoreMainScreenSkeleton()
+ }
+}
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun StoreMainScreenSkeleton(
+ updateUiThemeMode: (UIThemeMode) -> Unit = {},
+ goBack: () -> Unit = {}
+) {
+ val navController = rememberNavController()
+
+ StoreNavHost(
+ modifier = Modifier.background(MaterialTheme.colorScheme.background),
+ navController = navController,
+ updateUiThemeMode = updateUiThemeMode,
+ goBack = goBack
+ )
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/StoreNavGraph.kt b/store/src/main/kotlin/com/example/store/ui/screen/StoreNavGraph.kt
new file mode 100644
index 00000000..22a06fa8
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/StoreNavGraph.kt
@@ -0,0 +1,304 @@
+package com.example.store.ui.screen
+
+import LoginScreen
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.toRoute
+import com.example.store.ui.compositions.TabScreen
+import com.example.store.ui.screen.categories.CategoriesScreen
+import com.example.store.ui.screen.categorieswiseproduct.CategoriesWiseProductScreen
+import com.example.store.ui.screen.checkout.CheckOutScreen
+import com.example.store.ui.screen.home.StoreHomeScreen
+import com.example.store.ui.screen.home.StoreHomeScreenViewModel
+import com.example.store.ui.screen.order.Order
+import com.example.store.ui.screen.order.OrdersScreen
+import com.example.store.ui.screen.order.dummyOrders
+import com.example.store.ui.screen.productdetails.ProductDetailsScreen
+import com.example.store.ui.screen.productdetails.ProductDetailsViewModel
+import com.example.store.ui.screen.productdetails.dummyProducts
+import com.example.store.ui.screen.profile.ProfileScreen
+import com.example.store.ui.screen.splash.StoreSplashScreen
+import kotlinx.serialization.Serializable
+import org.imaginativeworld.whynotcompose.base.models.UIThemeMode
+import org.imaginativeworld.whynotcompose.base.models.nextMode
+import org.imaginativeworld.whynotcompose.base.utils.UIThemeController
+
+sealed class SplashScreen {
+ @Serializable
+ object Splash
+}
+
+sealed class AuthScreen {
+ @Serializable
+ object Login
+}
+
+sealed class MainScreen {
+ @Serializable
+ object TabScreen
+}
+
+sealed class StoreScreen {
+ @Serializable
+ object StoreHome
+}
+
+@Serializable
+sealed class CategorieScreen {
+ @Serializable
+ object Categories
+}
+
+@Serializable
+sealed class CategoriesWiseProducts {
+ @Serializable
+ data class CategoriesWiseProduct(val categoryTitle: String)
+}
+
+@Serializable
+sealed class DetailsScreen {
+ @Serializable
+ data class ProductDetails(val productId: Int)
+}
+
+@Serializable
+sealed class CartScreen {
+ @Serializable
+ object Cart
+}
+
+@Serializable
+sealed class ProfilesScreen {
+ @Serializable
+ object Profile
+}
+
+@Serializable
+sealed class OrderScreen {
+ @Serializable
+ object Order
+}
+
+@Serializable
+sealed class CheckoutScreen {
+ @Serializable
+ object Checkout
+}
+
+@Composable
+fun StoreNavHost(
+ navController: NavHostController,
+ updateUiThemeMode: (UIThemeMode) -> Unit,
+ goBack: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ NavHost(
+ modifier = modifier,
+ navController = navController,
+ startDestination = SplashScreen.Splash
+ ) {
+ composable {
+ StoreSplashScreen(
+ gotoHomeIndex = {
+ navController.navigate(AuthScreen.Login) {
+ popUpTo(SplashScreen.Splash) { inclusive = true }
+ }
+ }
+ )
+ }
+
+ composable {
+ val isDarkMode by UIThemeController.uiThemeMode.collectAsState()
+
+ LoginScreen(
+ onLogin = {
+ navController.navigate(MainScreen.TabScreen) {
+ popUpTo(AuthScreen.Login) { inclusive = true }
+ }
+ },
+ toggleUIMode = {
+ updateUiThemeMode(isDarkMode.nextMode())
+ }
+ )
+ }
+
+ addStoreScreens(
+ navController = navController,
+ updateUiThemeMode = updateUiThemeMode,
+ goBack = goBack
+ )
+ }
+}
+
+private fun NavGraphBuilder.addStoreScreens(
+ navController: NavHostController,
+ updateUiThemeMode: (UIThemeMode) -> Unit,
+ goBack: () -> Unit
+) {
+ composable {
+ val isDarkMode by UIThemeController.uiThemeMode.collectAsState()
+
+ TabScreen(
+ userName = "John Doe",
+ onOrderClick = {
+ navController.navigate(OrderScreen.Order) {
+ popUpTo(MainScreen.TabScreen) { inclusive = true }
+ }
+ },
+ onSignOutClick = {
+ navController.navigate(AuthScreen.Login) {
+ popUpTo(MainScreen.TabScreen) { inclusive = true }
+ }
+ },
+ goBack = {
+ navController.popBackStack()
+ },
+ toggleUIMode = {
+ updateUiThemeMode(isDarkMode.nextMode())
+ },
+ onProductClick = { product ->
+ navController.navigate(DetailsScreen.ProductDetails(product.id))
+ },
+ onCategoryClick = {
+ navController.navigate(CategoriesWiseProducts.CategoriesWiseProduct(it.name))
+ },
+ onCheckout = {
+ navController.navigate(CheckoutScreen.Checkout)
+ }
+ )
+ }
+
+ composable {
+ val isDarkMode by UIThemeController.uiThemeMode.collectAsState()
+ val viewModel: StoreHomeScreenViewModel = hiltViewModel()
+ StoreHomeScreen(
+ viewModel = viewModel,
+ userName = "John Doe",
+ onOrderClick = {
+ navController.navigate(OrderScreen.Order) {
+ popUpTo(MainScreen.TabScreen) { inclusive = true }
+ }
+ },
+ onSignOutClick = {
+ navController.navigate(AuthScreen.Login) {
+ popUpTo(MainScreen.TabScreen) { inclusive = true }
+ }
+ },
+ toggleUIMode = {
+ updateUiThemeMode(isDarkMode.nextMode())
+ },
+ onProductClick = { product ->
+ navController.navigate(DetailsScreen.ProductDetails(product.id))
+ },
+ onCategoryClick = {
+ navController.navigate(CategoriesWiseProducts.CategoriesWiseProduct(it.name))
+ }
+ )
+ }
+
+ composable { backStackEntry ->
+ val productDetails: DetailsScreen.ProductDetails = backStackEntry.toRoute()
+ val product = dummyProducts.find { it.id == productDetails.productId }
+ val isDarkMode by UIThemeController.uiThemeMode.collectAsState()
+ val viewModel: ProductDetailsViewModel = hiltViewModel()
+ product?.let {
+ ProductDetailsScreen(
+ viewModel = viewModel,
+ product = it,
+ goBack = {
+ navController.popBackStack()
+ },
+ toggleUIMode = {
+ updateUiThemeMode(isDarkMode.nextMode())
+ }
+ )
+ }
+ }
+
+ composable {
+ val isDarkMode by UIThemeController.uiThemeMode.collectAsState()
+ CategoriesScreen(
+ goBack = {
+ navController.popBackStack()
+ },
+ onCategoryClick = {
+ navController.navigate(CategoriesWiseProducts.CategoriesWiseProduct(it.name))
+ },
+ toggleUIMode = {
+ updateUiThemeMode(isDarkMode.nextMode())
+ }
+ )
+ }
+
+ composable { backStackEntry ->
+ val categoryDetails: CategoriesWiseProducts.CategoriesWiseProduct = backStackEntry.toRoute()
+ val isDarkMode by UIThemeController.uiThemeMode.collectAsState()
+ val products = dummyProducts.filter { it.category == categoryDetails.categoryTitle }
+ CategoriesWiseProductScreen(
+ products = products,
+ onProductClick = { product ->
+ navController.navigate(DetailsScreen.ProductDetails(product.id))
+ },
+ goBack = {
+ navController.popBackStack()
+ },
+ toggleUIMode = {
+ updateUiThemeMode(isDarkMode.nextMode())
+ }
+ )
+ }
+
+ composable {
+ ProfileScreen(
+ onDismiss = {
+ navController.popBackStack()
+ },
+ onOrdersClick = {
+ navController.navigate(OrderScreen.Order) {
+ popUpTo(MainScreen.TabScreen) { inclusive = true }
+ }
+ },
+ onSignOutClick = {
+ navController.navigate(AuthScreen.Login) {
+ popUpTo(MainScreen.TabScreen) { inclusive = true }
+ }
+ }
+ )
+ }
+
+ composable {
+ val isDarkMode by UIThemeController.uiThemeMode.collectAsState()
+ OrdersScreen(
+ orders = dummyOrders,
+ toggleUIMode = {
+ updateUiThemeMode(isDarkMode.nextMode())
+ },
+ goBack = {
+ navController.navigate(MainScreen.TabScreen)
+ }
+ )
+ }
+
+ composable {
+ val isDarkMode by UIThemeController.uiThemeMode.collectAsState()
+ CheckOutScreen(
+ goBack = {
+ navController.navigate(MainScreen.TabScreen)
+ },
+ toggleUIMode = {
+ updateUiThemeMode(isDarkMode.nextMode())
+ },
+ goToTab = {
+ navController.navigate(MainScreen.TabScreen)
+ }
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/cart/CartScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/cart/CartScreen.kt
new file mode 100644
index 00000000..5a2856ec
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/cart/CartScreen.kt
@@ -0,0 +1,166 @@
+package com.example.store.ui.screen.cart
+
+import android.widget.Toast
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableDoubleStateOf
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.compositions.CartItemCard
+import com.example.store.ui.compositions.StoreAppBar
+import com.example.store.ui.screen.productdetails.Product
+import com.example.store.ui.screen.productdetails.dummyProducts
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun CartScreen(
+ products: List,
+ goBack: () -> Unit,
+ onCheckout: () -> Unit,
+ toggleUIMode: () -> Unit
+) {
+ CartScreenSkeleton(
+ products = products,
+ goBack = goBack,
+ onCheckout = onCheckout,
+ toggleUIMode = toggleUIMode
+ )
+}
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun CartScreenSkeleton(
+ products: List,
+ goBack: () -> Unit,
+ onCheckout: () -> Unit = {},
+ toggleUIMode: () -> Unit = {}
+) {
+ Scaffold(
+ topBar = {
+ StoreAppBar(
+ title = "Cart",
+ goBack = goBack,
+ toggleUIMode = toggleUIMode
+ )
+ }
+ ) { paddingValues ->
+ val context = LocalContext.current
+ val cartItems = remember { mutableStateListOf(*products.toTypedArray()) }
+ val totalPrice by remember { mutableDoubleStateOf(cartItems.sumOf { it.price * it.quantity }) }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .padding(horizontal = 16.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .verticalScroll(rememberScrollState()),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ if (cartItems.isEmpty()) {
+ Text(
+ text = "Your cart is empty",
+ fontSize = 18.sp,
+ color = MaterialTheme.colorScheme.outline
+ )
+ } else {
+ cartItems.forEach { product ->
+ CartItemCard(
+ product = product,
+ onQuantityChange = { newQuantity ->
+ if (newQuantity == 0) {
+ cartItems.remove(product)
+ } else {
+ product.quantity = newQuantity
+ }
+ }
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = "Total",
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold
+ )
+ Text(
+ text = "$${"%.2f".format(totalPrice)}",
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ TextButton(
+ onClick = {
+ Toast.makeText(context, "Order Placed Complete", Toast.LENGTH_SHORT).show()
+ onCheckout()
+ },
+ modifier = Modifier
+ .fillMaxWidth(),
+ colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
+ shape = MaterialTheme.shapes.medium
+ ) {
+ Text(
+ text = "Place Order",
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.surface
+ )
+ }
+ }
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun CartScreenSkeletonPreview() {
+ StoreAppTheme {
+ CartScreenSkeleton(
+ products = dummyProducts,
+ goBack = {},
+ onCheckout = {},
+ toggleUIMode = {}
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/categories/CategoriesScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/categories/CategoriesScreen.kt
new file mode 100644
index 00000000..d8868205
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/categories/CategoriesScreen.kt
@@ -0,0 +1,90 @@
+package com.example.store.ui.screen.categories
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import com.example.store.models.categorie.Category
+import com.example.store.models.categorie.categories
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.compositions.CategoryCard
+import com.example.store.ui.compositions.StoreAppBar
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun CategoriesScreen(
+ goBack: () -> Unit,
+ onCategoryClick: (Category) -> Unit,
+ toggleUIMode: () -> Unit
+) {
+ CategoriesScreenSkeleton(
+ goBack = goBack,
+ onCategoryClick = {
+ onCategoryClick(it)
+ },
+ toggleUIMode = toggleUIMode
+ )
+}
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun CategoriesScreenSkeleton(
+ goBack: () -> Unit = {},
+ toggleUIMode: () -> Unit = {},
+ onCategoryClick: (Category) -> Unit = {}
+) {
+ Scaffold(
+ topBar = {
+ StoreAppBar(
+ title = "Category",
+ goBack = goBack,
+ toggleUIMode = toggleUIMode
+ )
+ }
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .padding(innerPadding)
+ .padding(16.dp)
+ .fillMaxSize()
+ ) {
+ LazyVerticalGrid(
+ modifier = Modifier,
+ columns = GridCells.Fixed(2),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ items(categories.size) { index ->
+ val category = categories[index]
+ CategoryCard(
+ category = category,
+ modifier = Modifier
+ .fillMaxWidth(),
+ onClick = {
+ onCategoryClick(category)
+ }
+ )
+ }
+ }
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun CategoriesScreenSkeletonPreview() {
+ StoreAppTheme {
+ CategoriesScreenSkeleton(
+ goBack = {},
+ toggleUIMode = {}
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/categories/CategoriesViewModel.kt b/store/src/main/kotlin/com/example/store/ui/screen/categories/CategoriesViewModel.kt
new file mode 100644
index 00000000..538f4577
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/categories/CategoriesViewModel.kt
@@ -0,0 +1,2 @@
+package com.example.store.ui.screen.categories
+
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/categorieswiseproduct/CategoriesWiseProductScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/categorieswiseproduct/CategoriesWiseProductScreen.kt
new file mode 100644
index 00000000..a9c5a8a0
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/categorieswiseproduct/CategoriesWiseProductScreen.kt
@@ -0,0 +1,76 @@
+package com.example.store.ui.screen.categorieswiseproduct
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.compositions.ProductItem
+import com.example.store.ui.compositions.StoreAppBar
+import com.example.store.ui.screen.productdetails.Product
+import com.example.store.ui.screen.productdetails.dummyProducts
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun CategoriesWiseProductScreen(
+ products: List,
+ onProductClick: (Product) -> Unit,
+ goBack: () -> Unit,
+ toggleUIMode: () -> Unit
+) {
+ Scaffold(
+ topBar = {
+ StoreAppBar(
+ title = "Category Wise Product",
+ goBack = goBack,
+ toggleUIMode = toggleUIMode
+ )
+ }
+ ) { innerPadding ->
+ LazyVerticalGrid(
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize(),
+ columns = GridCells.Fixed(2),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+// items(dummyProducts.size) { index ->
+// val product = dummyProducts[index]
+// ProductItem(
+// product = product,
+// onClick = { onProductClick(product) },
+// modifier = Modifier
+// .then(
+// if (index % 2 == 0) {
+// Modifier.padding(start = 16.dp)
+// } else {
+// Modifier.padding(end = 16.dp)
+// }
+//
+// )
+// )
+// }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun CategoriesWiseProductScreenPreview() {
+ StoreAppTheme {
+ val products = dummyProducts
+ CategoriesWiseProductScreen(
+ products = products,
+ onProductClick = {},
+ goBack = {},
+ toggleUIMode = {}
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/checkout/CheckOutScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/checkout/CheckOutScreen.kt
new file mode 100644
index 00000000..59f7fca9
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/checkout/CheckOutScreen.kt
@@ -0,0 +1,229 @@
+package com.example.store.ui.screen.checkout
+
+import android.widget.Toast
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Category
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.store.ui.compositions.OrderProductItem
+import com.example.store.ui.compositions.StoreAppBar
+import com.example.store.ui.screen.productdetails.Product
+import com.example.store.ui.screen.productdetails.dummyProducts
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun CheckOutScreen(
+ goBack: () -> Unit = {},
+ toggleUIMode: () -> Unit = {},
+ goToTab: () -> Unit = {}
+) {
+ CheckOutScreenSkeleton(
+ goBack = goBack,
+ products = dummyProducts,
+ toggleUIMode = toggleUIMode,
+ goToTab = goToTab
+ )
+}
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun CheckOutScreenSkeleton(
+ goBack: () -> Unit = {},
+ products: List,
+ toggleUIMode: () -> Unit,
+ goToTab: () -> Unit = {}
+) {
+ var context = LocalContext.current
+ var name by remember { mutableStateOf(TextFieldValue("")) }
+ var phone by remember { mutableStateOf(TextFieldValue("")) }
+ var address by remember { mutableStateOf(TextFieldValue("")) }
+ val totalPrice = products.sumOf { it.price }
+ val productCount = products.size
+ Scaffold(
+ topBar = {
+ StoreAppBar(
+ goBack = goBack,
+ title = "Checkout",
+ toggleUIMode = toggleUIMode
+ )
+ }
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding)
+ .padding(horizontal = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .verticalScroll(rememberScrollState()),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "\uD83D\uDCCD Shipping Address",
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Medium
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+
+ TextField(
+ value = name,
+ onValueChange = { name = it },
+ placeholder = { Text(text = "Your name...") },
+ modifier = Modifier.fillMaxWidth(),
+ colors = TextFieldDefaults.colors(
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ disabledIndicatorColor = Color.Transparent
+ ),
+ shape = RoundedCornerShape(12.dp)
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+
+ TextField(
+ value = phone,
+ onValueChange = { phone = it },
+ placeholder = { Text(text = "Phone number...") },
+ modifier = Modifier.fillMaxWidth(),
+ colors = TextFieldDefaults.colors(
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ disabledIndicatorColor = Color.Transparent
+ ),
+ shape = RoundedCornerShape(12.dp)
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+
+ TextField(
+ value = address,
+ onValueChange = { address = it },
+ placeholder = { Text(text = "Enter your address here...") },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(
+ 130.dp
+ ),
+ colors = TextFieldDefaults.colors(
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ disabledIndicatorColor = Color.Transparent
+ ),
+ shape = RoundedCornerShape(12.dp),
+ maxLines = 5
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = Icons.Default.Category,
+ contentDescription = ""
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = "$productCount Products",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ products.forEach { product ->
+ OrderProductItem(product = product)
+ Spacer(modifier = Modifier.height(12.dp))
+ }
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = "Total",
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold
+ )
+ Text(
+ text = "$4948.00",
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ TextButton(
+ onClick = {
+ goToTab()
+ Toast.makeText(context, "Checkout Successful", Toast.LENGTH_SHORT).show()
+ },
+ modifier = Modifier
+ .fillMaxWidth(),
+ colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
+ shape = MaterialTheme.shapes.medium
+ ) {
+ Text(
+ text = "Checkout",
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.surface
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun PreviewCheckoutScreen() {
+ StoreAppBar {}
+ CheckOutScreen()
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/home/StoreHomeScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/home/StoreHomeScreen.kt
new file mode 100644
index 00000000..ae6283cf
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/home/StoreHomeScreen.kt
@@ -0,0 +1,307 @@
+package com.example.store.ui.screen.home
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.SportsBaseball
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import androidx.paging.compose.LazyPagingItems
+import androidx.paging.compose.collectAsLazyPagingItems
+import coil3.compose.AsyncImage
+import com.example.store.models.categorie.Category
+import com.example.store.models.product.Product
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.compositions.ProductItem
+import com.example.store.ui.compositions.StoreAppBar
+import com.example.store.ui.screen.profile.ProfileScreen
+
+@Suppress("ktlint:compose:param-order-check")
+@Composable
+fun StoreHomeScreen(
+ viewModel: StoreHomeScreenViewModel,
+ userName: String,
+ onOrderClick: () -> Unit,
+ onSignOutClick: () -> Unit,
+ onCategoryClick: (Category) -> Unit,
+ onProductClick: (Product) -> Unit,
+ toggleUIMode: () -> Unit
+) {
+ val state by viewModel.state.collectAsState()
+ val pagedProducts = state.items.collectAsLazyPagingItems()
+
+ LaunchedEffect(Unit) {
+ viewModel.loadCategories()
+ }
+
+ StoreHomeSkeleton(
+ userName = userName,
+ onOrdersClick = onOrderClick,
+ onSignOutClick = onSignOutClick,
+ categories = state.categories,
+ products = pagedProducts,
+ onCategoryClick = onCategoryClick,
+ onProductClick = onProductClick,
+ toggleUIMode = toggleUIMode
+ )
+}
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun StoreHomeSkeleton(
+ userName: String,
+ onOrdersClick: () -> Unit,
+ onSignOutClick: () -> Unit,
+ categories: List,
+ products: LazyPagingItems,
+ onCategoryClick: (Category) -> Unit,
+ onProductClick: (Product) -> Unit,
+ toggleUIMode: () -> Unit
+) {
+ var showProfileSheet by remember { mutableStateOf(false) }
+
+ Scaffold(
+ topBar = {
+ StoreAppBar(
+ toggleUIMode = toggleUIMode
+ )
+ },
+ contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ ) {
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(2),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ item(span = { GridItemSpan(maxCurrentLineSpan) }) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ .padding(top = 16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = "Welcome, $userName",
+ style = MaterialTheme.typography.headlineSmall,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier
+ )
+
+ Image(
+ painter = painterResource(org.imaginativeworld.whynotcompose.common.compose.R.drawable.store),
+ contentDescription = "",
+ modifier = Modifier
+ .size(40.dp)
+ .clip(CircleShape)
+ .clickable {
+ showProfileSheet = true
+ },
+ contentScale = ContentScale.Crop
+ )
+ }
+ }
+
+ item(span = { GridItemSpan(maxCurrentLineSpan) }) {
+ LazyRow(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ contentPadding = PaddingValues(
+ horizontal = 16.dp
+ )
+ ) {
+ items(homeScreenImages.size) {
+ HomeScreenImage(
+ image = homeScreenImages[it],
+ modifier = Modifier
+ .width(300.dp)
+ .height(150.dp)
+ .background(MaterialTheme.colorScheme.surface)
+ )
+ }
+ }
+ }
+
+ item(span = { GridItemSpan(maxCurrentLineSpan) }) {
+ LazyRow(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ contentPadding = PaddingValues(
+ horizontal = 16.dp
+ )
+ ) {
+ items(categories.size) {
+ CategoryItem(
+ categories = categories[it],
+ modifier = Modifier,
+ onClick = {
+ onCategoryClick(categories[it])
+ }
+ )
+ }
+ }
+ }
+ items(products.itemCount) { index ->
+ val product = products[index]
+ if (product != null) {
+ ProductItem(
+ product = product,
+ onClick = {
+ onProductClick(product)
+ },
+ modifier = Modifier
+ .then(
+ if (index % 2 == 0) {
+ Modifier.padding(start = 16.dp)
+ } else {
+ Modifier.padding(end = 16.dp)
+ }
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+ if (showProfileSheet) {
+ ProfileScreen(
+ onDismiss = { showProfileSheet = false },
+ onOrdersClick = onOrdersClick,
+ onSignOutClick = onSignOutClick
+ )
+ }
+}
+
+@Composable
+fun CategoryItem(
+ categories: Category,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {}
+) {
+ Card(
+ modifier = modifier
+ .clickable(
+ onClick = {
+ onClick()
+ }
+ ),
+ shape = RoundedCornerShape(8.dp),
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.outlineVariant)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ vertical = 8.dp,
+ horizontal = 12.dp
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.SportsBaseball,
+ contentDescription = ""
+ )
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+ Text(
+ text = categories.name,
+ style = MaterialTheme.typography.bodyMedium,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier
+ )
+ }
+ }
+}
+
+@Composable
+fun HomeScreenImage(
+ image: HomeScreenImage,
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier = modifier,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ AsyncImage(
+ model = image.imageUrl,
+ contentDescription = "Product Image",
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(RoundedCornerShape(12.dp))
+ .background(color = MaterialTheme.colorScheme.outline)
+ )
+ }
+}
+
+data class HomeScreenImage(
+ val imageUrl: String
+)
+
+val homeScreenImages = listOf(
+ HomeScreenImage(imageUrl = "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg"),
+ HomeScreenImage(imageUrl = "https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg"),
+ HomeScreenImage(imageUrl = "https://fakestoreapi.com/img/61IBBVJvSDL._AC_SY879._SX._UX._SY._UY_.jpg")
+)
+
+@PreviewLightDark
+@Composable
+private fun StoreHomeSkeletonPreview() {
+ StoreAppTheme {
+// StoreHomeSkeleton(
+// userName = "Shihab",
+// onOrdersClick = {},
+// onSignOutClick = {},
+// categories = categories,
+// products = {},
+// onCategoryClick = {},
+// onProductClick = {},
+// toggleUIMode = {}
+// )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/home/StoreHomeScreenViewModel.kt b/store/src/main/kotlin/com/example/store/ui/screen/home/StoreHomeScreenViewModel.kt
new file mode 100644
index 00000000..9b10fdd9
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/home/StoreHomeScreenViewModel.kt
@@ -0,0 +1,105 @@
+package com.example.store.ui.screen.home
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.paging.Pager
+import androidx.paging.PagingConfig
+import androidx.paging.PagingData
+import androidx.paging.cachedIn
+import com.example.store.datasource.ProductPagingSource
+import com.example.store.models.categorie.Category
+import com.example.store.models.product.Product
+import com.example.store.repositories.CategoriesRepository
+import com.example.store.repositories.ProductRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.launch
+import org.imaginativeworld.whynotcompose.base.models.Event
+
+@HiltViewModel
+class StoreHomeScreenViewModel @Inject constructor(
+ private val productRepository: ProductRepository,
+ private val categoriesRepository: CategoriesRepository
+) : ViewModel() {
+
+ private val eventShowLoading = MutableStateFlow(false)
+ private val eventShowMessage = MutableStateFlow?>(null)
+ private var items = MutableStateFlow>>(emptyFlow())
+ private val categories = MutableStateFlow>(emptyList())
+
+ // ---------------- UI State ------------------
+
+ private val _state = MutableStateFlow(StoreHomeScreenState())
+ val state = _state.asStateFlow()
+
+ // ---------------- Combine States ------------------
+
+ init {
+ viewModelScope.launch {
+ combine(
+ eventShowLoading,
+ eventShowMessage,
+ items,
+ categories
+ ) { showLoading, showMessage, items, categories ->
+
+ StoreHomeScreenState(
+ loading = showLoading,
+ message = showMessage,
+ items = items,
+ categories = categories
+ )
+ }.catch { throwable ->
+ eventShowMessage.emit(Event("Something went wrong"))
+ throw throwable
+ }.collect {
+ Log.d("Log404", "Collecting UI state: ${it.items}")
+ _state.value = it
+ }
+ }
+
+ loadProducts()
+ Log.d("Log404", "Collecting UI state: ${loadProducts()}")
+ }
+
+ // ---------------- Load Products ------------------
+
+ fun loadProducts() {
+ items.value = Pager(PagingConfig(pageSize = 10)) {
+ ProductPagingSource(productRepository)
+ }
+ .flow
+ .cachedIn(viewModelScope)
+ }
+
+ fun loadCategories() {
+ viewModelScope.launch {
+ try {
+ val response = categoriesRepository.getCategories()
+ val categoriesList = response?.mapIndexed { index, name ->
+ Category(id = index + 1, name = name, imageUrl = "")
+ } ?: emptyList()
+
+ categories.value = categoriesList
+ } catch (e: Exception) {
+ eventShowMessage.emit(Event("Failed to load categories"))
+ } finally {
+ eventShowLoading.emit(false)
+ }
+ }
+ }
+}
+
+data class StoreHomeScreenState(
+ val loading: Boolean = false,
+ val message: Event? = null,
+ val items: Flow> = emptyFlow(),
+ val categories: List = emptyList()
+)
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/login/LogInScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/login/LogInScreen.kt
new file mode 100644
index 00000000..f66ee782
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/login/LogInScreen.kt
@@ -0,0 +1,232 @@
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Visibility
+import androidx.compose.material.icons.filled.VisibilityOff
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonColors
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.compositions.StoreAppBar
+import org.imaginativeworld.whynotcompose.common.compose.theme.AppleSystemColor
+
+@Composable
+fun LoginScreen(
+ onLogin: () -> Unit = {},
+ toggleUIMode: () -> Unit = {}
+) {
+ LoginSkeleton(
+ onLogin = onLogin,
+ toggleUIMode = toggleUIMode
+ )
+}
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun LoginSkeleton(
+ onLogin: () -> Unit,
+ toggleUIMode: () -> Unit = {}
+) {
+ var username by remember { mutableStateOf("store") }
+ var password by remember { mutableStateOf("223355") }
+ var usernameError by rememberSaveable { mutableStateOf("") }
+ var passwordError by rememberSaveable { mutableStateOf("") }
+ var passwordVisible by rememberSaveable { mutableStateOf(false) }
+
+ val gradientBrush = Brush.linearGradient(
+ colors = listOf(
+ AppleSystemColor.Purple,
+ AppleSystemColor.Blue
+ )
+ )
+
+ Scaffold(
+ topBar = {
+ StoreAppBar(
+ toggleUIMode = toggleUIMode
+ )
+ }
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp)
+ .background(gradientBrush),
+ contentAlignment = Alignment.Center
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "Welcome to",
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.surface,
+ fontWeight = FontWeight.Bold
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ text = "Store Overflow",
+ style = MaterialTheme.typography.headlineMedium,
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.surface
+ )
+ }
+ }
+ Spacer(modifier = Modifier.height(20.dp))
+ Column(
+ modifier = Modifier.padding(16.dp)
+ ) {
+ TextField(
+ label = {
+ Text(
+ text = "Username",
+ color = MaterialTheme.colorScheme.outline
+ )
+ },
+ colors = TextFieldDefaults.colors(
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ disabledIndicatorColor = Color.Transparent
+ ),
+ shape = RoundedCornerShape(12.dp),
+ value = username,
+ onValueChange = {
+ username = it
+ usernameError = validateUsername(it)
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ )
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ TextField(
+ value = password,
+ onValueChange = {
+ password = it
+ passwordError = validatePassword(it)
+ },
+ label = {
+ Text(
+ text = "Password",
+ color = MaterialTheme.colorScheme.outline
+ )
+ },
+ colors = TextFieldDefaults.colors(
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ disabledIndicatorColor = Color.Transparent
+ ),
+ shape = RoundedCornerShape(12.dp),
+ singleLine = true,
+ visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
+ trailingIcon = {
+ IconButton(
+ onClick = { passwordVisible = !passwordVisible }
+ ) {
+ Icon(
+ imageVector = if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff,
+ contentDescription = "Toggle password visibility"
+ )
+ }
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Button(
+ onClick = onLogin,
+ modifier = Modifier
+ .wrapContentSize(),
+ enabled = true,
+ colors = ButtonColors(
+ containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f),
+ contentColor = MaterialTheme.colorScheme.surface,
+ disabledContainerColor = MaterialTheme.colorScheme.outlineVariant,
+ disabledContentColor = MaterialTheme.colorScheme.onPrimaryContainer
+ ),
+ shape = RoundedCornerShape(30.dp)
+ ) {
+ Text(
+ text = "Login",
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier
+ .padding(
+ vertical = 6.dp,
+ horizontal = 6.dp
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+fun validateUsername(username: String): String = when {
+ username.isEmpty() -> ""
+ username.length < 3 -> "Username should be at least 3 characters long"
+ else -> ""
+}
+
+fun validatePassword(password: String): String = when {
+ password.isEmpty() -> ""
+ password.length < 6 -> "Password should be at least 6 characters long"
+ else -> ""
+}
+
+@PreviewLightDark
+@Composable
+private fun PreviewLoginScreen() {
+ StoreAppTheme {
+ LoginSkeleton(
+ onLogin = {},
+ toggleUIMode = {}
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/order/OrderScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/order/OrderScreen.kt
new file mode 100644
index 00000000..2e8be2fa
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/order/OrderScreen.kt
@@ -0,0 +1,124 @@
+package com.example.store.ui.screen.order
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.compositions.OrderCard
+import com.example.store.ui.compositions.StoreAppBar
+import com.example.store.ui.screen.productdetails.Product
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun OrdersScreen(
+ orders: List,
+ goBack: () -> Unit,
+ toggleUIMode: () -> Unit
+) {
+ Scaffold(
+ topBar = {
+ StoreAppBar(
+ title = "Orders",
+ goBack = goBack,
+ toggleUIMode = toggleUIMode
+ )
+ }
+ ) { paddingValues ->
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues),
+ contentPadding = PaddingValues(16.dp)
+ ) {
+ items(orders) { order ->
+ OrderCard(order = order)
+ Spacer(modifier = Modifier.height(12.dp))
+ }
+ }
+ }
+}
+
+data class Order(
+ val id: Int,
+ val date: String,
+ val products: List
+)
+
+var dummyOrders = listOf(
+ Order(
+ id = 7,
+ date = "01 Mar 2020",
+ listOf(
+ Product(
+ category = "T-shirt",
+ id = 2,
+ title = "Mens Casual Premium Slim Fit T-Shirts",
+ imageUrl = "https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg",
+ price = 22.3,
+ rating = 4.5,
+ reviewCount = 259,
+ description = "Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight & soft fabric for breathable and comfortable wearing.",
+ quantity = 8
+ )
+ )
+ ),
+
+ Order(
+ id = 6,
+ date = "01 Mar 2020",
+ listOf(
+ Product(
+ category = "Backpack",
+ id = 1,
+ title = "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops",
+ imageUrl = "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg",
+ price = 109.95,
+ rating = 4.5,
+ reviewCount = 120,
+ description = "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday",
+ quantity = 10
+ )
+ )
+ ),
+
+ Order(
+ id = 5,
+ date = "01 Mar 2020",
+ listOf(
+ Product(
+ category = "Jacket",
+ id = 3,
+ title = "Mens Cotton Jacket",
+ imageUrl = "https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg",
+ price = 55.99,
+ rating = 4.5,
+ reviewCount = 500,
+ description = "Great outerwear jackets for Spring/Autumn/Winter, suitable for many occasions, such as working, hiking, camping, mountain/rock climbing, cycling.",
+ quantity = 5
+ )
+ )
+ )
+)
+
+@PreviewLightDark
+@Composable
+private fun OrdersScreenPreview() {
+ StoreAppTheme {
+ OrdersScreen(
+ orders = dummyOrders,
+ goBack = {},
+ toggleUIMode = {}
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/productdetails/ProductDetailsScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/productdetails/ProductDetailsScreen.kt
new file mode 100644
index 00000000..07698bf4
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/productdetails/ProductDetailsScreen.kt
@@ -0,0 +1,341 @@
+package com.example.store.ui.screen.productdetails
+
+import android.util.Log
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Remove
+import androidx.compose.material.icons.filled.Star
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import coil3.compose.AsyncImage
+import com.example.store.models.product.Product
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.compositions.StoreAppBar
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun ProductDetailsScreen(
+ viewModel: ProductDetailsViewModel,
+ product: Product,
+ goBack: () -> Unit = {},
+ toggleUIMode: () -> Unit = {}
+) {
+ LaunchedEffect(Unit) {
+ viewModel.loadProductDetails(product.id)
+ Log.d("Log404", "ProductDetailsScreen response:${viewModel.loadProductDetails(product.id)}-------id: ${viewModel.loadProductDetails(1)}")
+ }
+ ProductDetailsScreenSkeleton(
+ product = product,
+ goBack = goBack,
+ toggleUIMode = toggleUIMode
+ )
+}
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun ProductDetailsScreenSkeleton(
+ product: Product,
+ goBack: () -> Unit = {},
+ toggleUIMode: () -> Unit = {}
+) {
+ Scaffold(
+ topBar = {
+ StoreAppBar(
+ title = "Product Details",
+ goBack = goBack,
+ toggleUIMode = toggleUIMode
+ )
+ }
+ ) { innerPadding ->
+ var quantity by remember { mutableIntStateOf(0) }
+ val scrollState = rememberScrollState()
+
+ Column(
+ modifier = Modifier
+ .padding(innerPadding)
+ .padding(16.dp)
+ .verticalScroll(scrollState)
+ ) {
+ AsyncImage(
+ model = product.image,
+ contentDescription = "Product Image",
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(500.dp)
+ .clip(RoundedCornerShape(12.dp))
+ .background(MaterialTheme.colorScheme.surface)
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = product.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(bottom = 8.dp),
+ fontWeight = FontWeight.Bold,
+ maxLines = 1
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start
+ ) {
+ Icon(
+ Icons.Default.Star,
+ contentDescription = "Rating",
+ tint = Color.Yellow
+ )
+ Text(
+ text = ("${product.rating} (${product.reviewCount})"),
+ modifier = Modifier.padding(start = 4.dp),
+ color = MaterialTheme.colorScheme.outline
+ )
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = product.description,
+ style = MaterialTheme.typography.bodyLarge
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = "$${product.price}",
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.error,
+ fontWeight = FontWeight.Bold
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(
+ onClick = { if (quantity > 0) quantity-- },
+ modifier = Modifier
+ ) {
+ Box(
+ modifier = Modifier
+ .padding(4.dp)
+ .clip(RoundedCornerShape(8.dp))
+ .background(Color.Blue)
+ .padding(4.dp)
+ ) {
+ Icon(
+ Icons.Default.Remove,
+ contentDescription = "Decrease",
+ tint = MaterialTheme.colorScheme.surface
+ )
+ }
+ }
+
+ Text(
+ text = "$quantity",
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ )
+
+ IconButton(
+ onClick = { quantity++ }
+ ) {
+ Box(
+ modifier = Modifier
+ .padding(4.dp)
+ .clip(RoundedCornerShape(8.dp))
+ .background(Color.Blue)
+ .padding(4.dp)
+ ) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = "Increase",
+ tint = MaterialTheme.colorScheme.surface
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+//data class Product(
+// val category: String,
+// val id: Int,
+// val title: String,
+// val imageUrl: String,
+// val price: Double,
+// val rating: Double,
+// val reviewCount: Int,
+// val description: String,
+// var quantity: Int
+//)
+//
+//val dummyProducts = listOf(
+// Product(
+// category = "Backpack",
+// id = 1,
+// title = "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops",
+// imageUrl = "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg",
+// price = 109.95,
+// rating = 4.5,
+// reviewCount = 120,
+// description = "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday",
+// quantity = 10
+// ),
+// Product(
+// category = "T-shirt",
+// id = 2,
+// title = "Mens Casual Premium Slim Fit T-Shirts",
+// imageUrl = "https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg",
+// price = 22.3,
+// rating = 4.5,
+// reviewCount = 259,
+// description = "Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight & soft fabric for breathable and comfortable wearing.",
+// quantity = 8
+// ),
+// Product(
+// category = "Jacket",
+// id = 3,
+// title = "Mens Cotton Jacket",
+// imageUrl = "https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg",
+// price = 55.99,
+// rating = 4.5,
+// reviewCount = 500,
+// description = "Great outerwear jackets for Spring/Autumn/Winter, suitable for many occasions, such as working, hiking, camping, mountain/rock climbing, cycling.",
+// quantity = 5
+// ),
+// Product(
+// category = "Clothing",
+// id = 4,
+// title = "Mens Casual Slim Fit",
+// imageUrl = "https://fakestoreapi.com/img/71YXzeOuslL._AC_UY879_.jpg",
+// price = 15.99,
+// rating = 4.5,
+// reviewCount = 430,
+// description = "The color could be slightly different between on the screen and in practice.",
+// quantity = 12
+// ),
+// Product(
+// category = "Jewelry",
+// id = 5,
+// title = "John Hardy Women's Legends Naga Gold & Silver Dragon Station Chain Bracelet",
+// imageUrl = "https://fakestoreapi.com/img/71pWzhdJNwL._AC_UL640_QL65_ML3_.jpg",
+// price = 695.0,
+// rating = 4.5,
+// reviewCount = 400,
+// description = "From our Legends Collection, the Naga was inspired by the mythical water dragon that protects the ocean's pearl.",
+// quantity = 7
+// ),
+// Product(
+// category = "Jewelry",
+// id = 6,
+// title = "Solid Gold Petite Micropave",
+// imageUrl = "https://fakestoreapi.com/img/61sbMiUnoGL._AC_UL640_QL65_ML3_.jpg",
+// price = 168.0,
+// rating = 4.5,
+// reviewCount = 70,
+// description = "Satisfaction Guaranteed. Return or exchange any order within 30 days.",
+// quantity = 10
+// ),
+// Product(
+// category = "Jewelry",
+// id = 7,
+// title = "White Gold Plated Princess",
+// imageUrl = "https://fakestoreapi.com/img/71YAIFU48IL._AC_UL640_QL65_ML3_.jpg",
+// price = 9.99,
+// rating = 4.5,
+// reviewCount = 400,
+// description = "Classic Created Wedding Engagement Solitaire Diamond Promise Ring for Her.",
+// quantity = 15
+// ),
+// Product(
+// category = "Jewelry",
+// id = 8,
+// title = "Pierced Owl Rose Gold Plated Stainless Steel Double",
+// imageUrl = "https://fakestoreapi.com/img/51UDEzMJVpL._AC_UL640_QL65_ML3_.jpg",
+// price = 10.99,
+// rating = 4.5,
+// reviewCount = 100,
+// description = "Rose Gold Plated Double Flared Tunnel Plug Earrings.",
+// quantity = 3
+// ),
+// Product(
+// category = "External Hard Drive",
+// id = 9,
+// title = "WD 2TB Elements Portable External Hard Drive - USB 3.0",
+// imageUrl = "https://fakestoreapi.com/img/61IBBVJvSDL._AC_SY879_.jpg",
+// price = 64.0,
+// rating = 4.5,
+// reviewCount = 203,
+// description = "USB 3.0 and USB 2.0 Compatibility Fast data transfers.",
+// quantity = 4
+// ),
+// Product(
+// category = "Storage",
+// id = 10,
+// title = "SanDisk SSD PLUS 1TB Internal SSD - SATA III 6 Gb/s",
+// imageUrl = "https://fakestoreapi.com/img/61U7T1koQqL._AC_SX679_.jpg",
+// price = 109.0,
+// rating = 4.7,
+// reviewCount = 470,
+// description = "Easy upgrade for faster boot up, shutdown, application load and response.",
+// quantity = 6
+// )
+)
+
+@Preview(showBackground = true)
+@Composable
+private fun ProductDetailsScreenSkeletonPreview() {
+ StoreAppTheme {
+ val sampleProduct = Product(
+ category = "Storage",
+ id = 10,
+ title = "SanDisk SSD PLUS 1TB Internal SSD - SATA III 6 Gb/s",
+ imageUrl = "https://fakestoreapi.com/img/61U7T1koQqL._AC_SX679_.jpg",
+ price = 109.0,
+ rating = 4.7,
+ reviewCount = 470,
+ description = "Easy upgrade for faster boot up, shutdown, application load and response.",
+ quantity = 6
+ )
+
+ ProductDetailsScreenSkeleton(
+ product = sampleProduct
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/productdetails/ProductDetailsViewModel.kt b/store/src/main/kotlin/com/example/store/ui/screen/productdetails/ProductDetailsViewModel.kt
new file mode 100644
index 00000000..38a5e9dc
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/productdetails/ProductDetailsViewModel.kt
@@ -0,0 +1,67 @@
+package com.example.store.ui.screen.productdetails
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.store.repositories.ProductRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+import org.imaginativeworld.whynotcompose.base.models.Event
+
+@HiltViewModel
+class ProductDetailsViewModel @Inject constructor(
+ private val productRepository: ProductRepository
+) : ViewModel() {
+ private val eventShowLoading = MutableStateFlow(false)
+ private val eventShowMessage = MutableStateFlow?>(null)
+ private val productId = MutableStateFlow(-1)
+
+ private val _state = MutableStateFlow(ProductDetailsScreenState())
+ val state = _state.asStateFlow()
+
+ init {
+ viewModelScope.launch {
+ combine(
+ eventShowLoading,
+ eventShowMessage,
+ productId
+ ) { showLoading, showMessage, productId ->
+
+ ProductDetailsScreenState(
+ loading = showLoading,
+ message = showMessage,
+ productId = productId
+ )
+ }.catch { throwable ->
+ eventShowMessage.emit(Event("Something went wrong"))
+ throw throwable
+ }.collect {
+ _state.value = it
+ }
+ }
+ }
+
+ fun loadProductDetails(id: Int) {
+ viewModelScope.launch {
+ try {
+ val response = productRepository.getProductsId(id)
+ Log.d("Log404", "loadProductDetails viewMOdel response: $response")
+ } catch (e: Exception) {
+ eventShowMessage.emit(Event("Failed to load product details"))
+ } finally {
+ eventShowLoading.emit(false)
+ }
+ }
+ }
+}
+
+data class ProductDetailsScreenState(
+ val loading: Boolean = false,
+ val message: Event? = null,
+ val productId: Int = -1
+)
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/profile/ProfileScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/profile/ProfileScreen.kt
new file mode 100644
index 00000000..e50c87b4
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/profile/ProfileScreen.kt
@@ -0,0 +1,234 @@
+package com.example.store.ui.screen.profile
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.store.theme.StoreAppTheme
+import com.example.store.ui.compositions.KeyValue
+import kotlinx.coroutines.launch
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun ProfileScreen(
+ onDismiss: () -> Unit,
+ onOrdersClick: () -> Unit,
+ onSignOutClick: () -> Unit
+) {
+ ProfileScreenSkeleton(
+ onDismiss = onDismiss,
+ onOrdersClick = onOrdersClick,
+ onSignOutClick = onSignOutClick
+ )
+}
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun ProfileScreenSkeleton(
+ onDismiss: () -> Unit,
+ onOrdersClick: () -> Unit,
+ onSignOutClick: () -> Unit
+) {
+ val scope = rememberCoroutineScope()
+ val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+ ModalBottomSheet(
+ onDismissRequest = { onDismiss() },
+ sheetState = sheetState,
+ shape = RoundedCornerShape(
+ topStart = 16.dp,
+ topEnd = 16.dp
+ ),
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ Scaffold(
+ topBar = {
+ CenterAlignedTopAppBar(
+ title = {
+ Text(
+ text = "Profile",
+ style = MaterialTheme.typography.headlineSmall
+ )
+ },
+ actions = {
+ TextButton(
+ onClick = { scope.launch { sheetState.hide() }.invokeOnCompletion { onDismiss() } },
+ modifier = Modifier
+ ) {
+ Text(
+ text = "Done",
+ style = MaterialTheme.typography.titleLarge
+ )
+ }
+ }
+ )
+ },
+ contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState()),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(120.dp)
+ .padding(horizontal = 16.dp)
+ .clip(RoundedCornerShape(12.dp))
+ .background(MaterialTheme.colorScheme.surfaceContainer),
+ contentAlignment = Alignment.Center
+ ) {
+ Image(
+ painter = painterResource(org.imaginativeworld.whynotcompose.common.compose.R.drawable.store),
+ contentDescription = "Profile Image",
+ modifier = Modifier
+ .size(100.dp)
+ .clip(CircleShape),
+ contentScale = ContentScale.Crop
+ )
+ }
+
+ Spacer(modifier = Modifier.height(30.dp))
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ ) {
+ Text(
+ text = "DETAILS",
+ fontSize = 14.sp,
+ color = MaterialTheme.colorScheme.outline,
+ textAlign = TextAlign.Start,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Column(
+ modifier = Modifier
+ .clip(RoundedCornerShape(12.dp))
+ .background(MaterialTheme.colorScheme.surfaceContainer)
+ .padding(16.dp)
+ ) {
+ KeyValue(
+ title = "Name",
+ value = "John Doe"
+ )
+
+ HorizontalDivider()
+
+ KeyValue(
+ title = "Username",
+ value = "johnd"
+ )
+
+ HorizontalDivider()
+
+ KeyValue(
+ title = "Email",
+ value = "john@gmail.com"
+ )
+
+ HorizontalDivider()
+
+ KeyValue(
+ title = "Phone",
+ value = "1-570-236-7033"
+ )
+
+ HorizontalDivider()
+
+ KeyValue(
+ title = "Address",
+ value = "New Road, Kilcoole"
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Button(
+ onClick = onOrdersClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
+ shape = MaterialTheme.shapes.medium
+ ) {
+ Text(
+ text = "Orders",
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.titleMedium
+ )
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(
+ onClick = onSignOutClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
+ shape = MaterialTheme.shapes.medium
+ ) {
+ Text(
+ text = "Sign Out",
+ color = MaterialTheme.colorScheme.error,
+ style = MaterialTheme.typography.titleMedium
+ )
+ }
+ }
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun ProfileScreenSkeletonPreview() {
+ StoreAppTheme {
+ ProfileScreenSkeleton(
+ onDismiss = {},
+ onOrdersClick = {},
+ onSignOutClick = {}
+ )
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/ui/screen/splash/SplashScreen.kt b/store/src/main/kotlin/com/example/store/ui/screen/splash/SplashScreen.kt
new file mode 100644
index 00000000..fc02289c
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/ui/screen/splash/SplashScreen.kt
@@ -0,0 +1,54 @@
+package com.example.store.ui.screen.splash
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import com.example.store.theme.StoreAppTheme
+import kotlinx.coroutines.delay
+
+@Composable
+fun StoreSplashScreen(
+ gotoHomeIndex: () -> Unit = {}
+) {
+ LaunchedEffect(gotoHomeIndex) {
+ delay(1000)
+
+ gotoHomeIndex()
+ }
+
+ StoreSplashScreenSkeleton()
+}
+
+@PreviewLightDark
+@Composable
+private fun SplashScreenSkeletonPreview() {
+ StoreAppTheme {
+ StoreSplashScreenSkeleton()
+ }
+}
+
+@Suppress("ktlint:compose:modifier-missing-check")
+@Composable
+fun StoreSplashScreenSkeleton() {
+ Scaffold { innerPadding ->
+ Box(
+ Modifier
+ .padding(innerPadding)
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "Store",
+ style = MaterialTheme.typography.displayLarge
+ )
+ }
+ }
+}
diff --git a/store/src/main/kotlin/com/example/store/utlis/Constants.kt b/store/src/main/kotlin/com/example/store/utlis/Constants.kt
new file mode 100644
index 00000000..55a171e2
--- /dev/null
+++ b/store/src/main/kotlin/com/example/store/utlis/Constants.kt
@@ -0,0 +1,6 @@
+package com.example.store.utlis
+class Constants {
+ companion object {
+ const val BASE_URL = "https://fakestoreapi.com/"
+ }
+}
diff --git a/store/src/main/res/drawable/baseline_visibility_24.xml b/store/src/main/res/drawable/baseline_visibility_24.xml
new file mode 100644
index 00000000..b8e84326
--- /dev/null
+++ b/store/src/main/res/drawable/baseline_visibility_24.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/store/src/main/res/values/colors.xml b/store/src/main/res/values/colors.xml
new file mode 100644
index 00000000..f8c6127d
--- /dev/null
+++ b/store/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/store/src/main/res/values/strings.xml b/store/src/main/res/values/strings.xml
new file mode 100644
index 00000000..6f5da54f
--- /dev/null
+++ b/store/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Store
+
\ No newline at end of file
diff --git a/store/src/main/res/values/themes.xml b/store/src/main/res/values/themes.xml
new file mode 100644
index 00000000..38622fe2
--- /dev/null
+++ b/store/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file