Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .run/spotlessApply.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
<configuration default="false" name="spotlessApply" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--init-script=gradle/init.gradle.kts" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="spotlessApply" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ dependencies {
implementation(project(":exoplayer"))
implementation(project(":cms"))
implementation(project(":popbackstack"))
implementation(project(":store"))
"baselineProfile"(project(":benchmarks"))

implementation(libs.kotlin.stdlib)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")

// ================================================================
Expand Down Expand Up @@ -982,6 +984,15 @@ private fun NavGraphBuilder.addTutorialIndexScreen(
)
}

composable(TutorialsScreen.TutorialStore.route) {
StoreMainScreen(
updateUiThemeMode = updateUiThemeMode,
goBack = {
navController.popBackStackOrIgnore()
}
)
}

composable(TutorialsScreen.TutorialDeepLink.route) {
DeepLinksScreen(
goBack = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
Binary file added app/src/main/res/drawable/store.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file added common-ui-compose/src/main/res/drawable/store.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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" }
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ include(":exoplayer")
include(":cms")
include(":popbackstack")
include(":benchmarks")
include(":store")
1 change: 1 addition & 0 deletions store/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
137 changes: 137 additions & 0 deletions store/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
4 changes: 4 additions & 0 deletions store/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>

</manifest>
Original file line number Diff line number Diff line change
@@ -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<Long, Product>() {

override suspend fun load(params: LoadParams<Long>): LoadResult<Long, Product> {
val pagePosition = params.key ?: 1

return try {
val products = repository.getProducts(
pagePosition
)
Comment on lines +19 to +21

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The fakestoreapi.com documentation indicates that the /products endpoint supports limit and sort query parameters, but not page. Using pagePosition here will likely not result in correct pagination from the API. You might need to adjust the API call to use limit and handle pagination manually based on the number of items fetched, or find an alternative API that supports page-based pagination.


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, Product>): 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
}
}
}
40 changes: 40 additions & 0 deletions store/src/main/kotlin/com/example/store/di/StoreAppModule.kt
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example.store.models.categorie

class Categories : ArrayList<String>()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Extending ArrayList directly is generally not idiomatic in Kotlin. It's usually better to define a type alias or a data class that wraps a List<String> to provide more type safety and clarity about the data's purpose.

Suggested change
class Categories : ArrayList<String>()
typealias Categories = List<String>

Original file line number Diff line number Diff line change
@@ -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")
)
Loading
Loading