connect localserver

This commit is contained in:
shaulascr
2025-02-02 15:58:22 +07:00
parent f50a230bb0
commit 6bf3556fed
66 changed files with 1610 additions and 45 deletions

26
.idea/appInsightsSettings.xml generated Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="tabSettings">
<map>
<entry key="Firebase Crashlytics">
<value>
<InsightsFilterSettings>
<option name="connection">
<ConnectionSetting>
<option name="appId" value="PLACEHOLDER" />
<option name="mobileSdkAppId" value="" />
<option name="projectId" value="" />
<option name="projectNumber" value="" />
</ConnectionSetting>
</option>
<option name="signal" value="SIGNAL_UNSPECIFIED" />
<option name="timeIntervalDays" value="THIRTY_DAYS" />
<option name="visibilityType" value="ALL" />
</InsightsFilterSettings>
</value>
</entry>
</map>
</option>
</component>
</project>

68
.idea/other.xml generated
View File

@ -14,6 +14,17 @@
<option name="screenX" value="720" />
<option name="screenY" value="1280" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OPPO" />
<option name="codename" value="OP573DL1" />
<option name="id" value="OP573DL1" />
<option name="manufacturer" value="OPPO" />
<option name="name" value="CPH2557" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="28" />
<option name="brand" value="DOCOMO" />
@ -113,6 +124,17 @@
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm2q" />
<option name="id" value="dm2q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S23 Plus" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
@ -135,6 +157,28 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="e3q" />
<option name="id" value="e3q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S24 Ultra" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1440" />
<option name="screenY" value="3120" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="eos" />
<option name="id" value="eos" />
<option name="manufacturer" value="Google" />
<option name="name" value="Eos" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
@ -168,6 +212,17 @@
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="motorola" />
<option name="codename" value="fogona" />
<option name="id" value="fogona" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g play - 2024" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
@ -223,6 +278,17 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="motorola" />
<option name="codename" value="maui" />
<option name="id" value="maui" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g play - 2023" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="google" />
@ -262,7 +328,7 @@
<option name="codename" value="q6q" />
<option name="id" value="q6q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-F956B" />
<option name="name" value="Galaxy Z Fold6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1856" />
<option name="screenY" value="2160" />

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,6 +1,10 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
id("kotlin-kapt")
id ("androidx.navigation.safeargs")
id("kotlin-parcelize")
// id("com.google.dagger.hilt.android")
}
android {
@ -9,7 +13,7 @@ android {
defaultConfig {
applicationId = "com.alya.ecommerce_serang"
minSdk = 24
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"
@ -19,21 +23,26 @@ android {
buildTypes {
release {
buildConfigField("String", "BASE_URL", "\"http://192.168.1.13:3000/\"")
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
debug {
buildConfigField("String", "BASE_URL", "\"http://192.168.1.13:3000/\"")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
}
buildFeatures {
buildConfig = true
viewBinding = true
}
}
@ -45,7 +54,34 @@ dependencies {
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.legacy.support.v4)
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
//retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
// implementation("com.github.zhpanvip:viewpagerindicator:1.2.3")
implementation("com.github.bumptech.glide:glide:4.16.0")
implementation("androidx.paging:paging-runtime:3.2.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
// implementation(libs.hilt.android)
// kapt("com.google.dagger:hilt-compiler:2.48")
//
// // For ViewModel injection (if needed)
// implementation(libs.androidx.hilt.lifecycle.viewmodel)
// kapt("androidx.hilt:hilt-compiler:1.0.0")
}

View File

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@ -11,7 +13,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Ecommerce_serang"
tools:targetApi="31">
tools:targetApi="31"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".ui.MainActivity"
android:exported="true">

View File

@ -0,0 +1,7 @@
package com.alya.ecommerce_serang.app
import android.app.Application
//@HiltAndroidApp
class App : Application(){
}

View File

@ -0,0 +1,11 @@
package com.alya.ecommerce_serang.data.api.dto
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class Category(
val id: String,
val image: String,
val title: String
): Parcelable

View File

@ -0,0 +1,22 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class DetailProduct(
val category: String,
val description: String,
val discount: Double?,
@SerializedName("favorite")
val wishlist: Boolean,
val id: String,
val images: List<String>,
@SerializedName("in_stock")
val inStock: Int,
val price: Double,
val rating: Double,
val related: List<Product>,
val reviews: Int,
val title: String,
@SerializedName("free_delivery")
val freeDelivery:Boolean
)

View File

@ -0,0 +1,17 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class Product (
val id: String,
val discount: Double?,
@SerializedName("favorite")
var wishlist: Boolean,
val image: String,
val price: Double,
val rating: Double,
@SerializedName("rating_count")
val ratingCount: Int,
val title: String,
)

View File

@ -0,0 +1,57 @@
package com.alya.ecommerce_serang.data.api.response
import com.google.gson.annotations.SerializedName
data class AllProductResponse(
@field:SerializedName("message")
val message: String,
@field:SerializedName("products")
val products: List<ProductsItem>
)
data class ProductsItem(
@field:SerializedName("store_id")
val storeId: Int,
@field:SerializedName("image")
val image: String,
@field:SerializedName("rating")
val rating: String,
@field:SerializedName("description")
val description: String,
@field:SerializedName("weight")
val weight: Int,
@field:SerializedName("is_pre_order")
val isPreOrder: Boolean,
@field:SerializedName("category_id")
val categoryId: Int,
@field:SerializedName("price")
val price: String,
@field:SerializedName("name")
val name: String,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("min_order")
val minOrder: Int,
@field:SerializedName("total_sold")
val totalSold: Int,
@field:SerializedName("stock")
val stock: Int,
@field:SerializedName("status")
val status: String
)

View File

@ -0,0 +1,60 @@
package com.alya.ecommerce_serang.data.api.response
import com.google.gson.annotations.SerializedName
data class ProductResponse(
@field:SerializedName("product")
val product: Product,
@field:SerializedName("message")
val message: String
)
data class Product(
@field:SerializedName("store_id")
val storeId: Int,
@field:SerializedName("image")
val image: String,
@field:SerializedName("rating")
val rating: String,
@field:SerializedName("description")
val description: String,
@field:SerializedName("weight")
val weight: Int,
@field:SerializedName("product_name")
val productName: String,
@field:SerializedName("is_pre_order")
val isPreOrder: Boolean,
@field:SerializedName("duration")
val duration: Any,
@field:SerializedName("category_id")
val categoryId: Int,
@field:SerializedName("price")
val price: String,
@field:SerializedName("product_id")
val productId: Int,
@field:SerializedName("min_order")
val minOrder: Int,
@field:SerializedName("total_sold")
val totalSold: Int,
@field:SerializedName("stock")
val stock: Int,
@field:SerializedName("product_category")
val productCategory: String
)

View File

@ -0,0 +1,25 @@
package com.alya.ecommerce_serang.data.api.retrofit
import com.alya.ecommerce_serang.BuildConfig
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class ApiConfig {
companion object{
fun getApiService(): ApiService {
val loggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
return retrofit.create(ApiService::class.java)
}
}
}

View File

@ -0,0 +1,22 @@
package com.alya.ecommerce_serang.data.api.retrofit
import com.alya.ecommerce_serang.data.api.response.AllProductResponse
import com.alya.ecommerce_serang.data.api.response.ProductResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Path
interface ApiService {
@GET("product")
fun getAllProduct(
@Header("Authorization") token: String = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzEsIm5hbWUiOiJhbHlhIiwiZW1haWwiOiJha3VuYmVsYWphci5hbHlhQGdtYWlsLmNvbSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzM4NDg0OTc0LCJleHAiOjE3NDEwNzY5NzR9.0JyXJQ_6CKiZEi0gvk1gcn-0ILu3a9lOr3HqjhJXbBE"
): Call<AllProductResponse>
@GET("product/detail/{id}")
fun getDetailProduct (
@Header("Authorization") token: String,
@Path("id") productId: Int
): Call<ProductResponse>
}

View File

@ -0,0 +1,51 @@
package com.alya.ecommerce_serang.data.model
import com.google.gson.annotations.SerializedName
data class Product(
@field:SerializedName("store_id")
val storeId: Int,
@field:SerializedName("image")
val image: String,
@field:SerializedName("rating")
val rating: String,
@field:SerializedName("description")
val description: String,
@field:SerializedName("weight")
val weight: Int,
@field:SerializedName("product_name")
val productName: String,
@field:SerializedName("is_pre_order")
val isPreOrder: Boolean,
@field:SerializedName("duration")
val duration: Any,
@field:SerializedName("category_id")
val categoryId: Int,
@field:SerializedName("price")
val price: String,
@field:SerializedName("product_id")
val productId: Int,
@field:SerializedName("min_order")
val minOrder: Int,
@field:SerializedName("total_sold")
val totalSold: Int,
@field:SerializedName("stock")
val stock: Int,
@field:SerializedName("product_category")
val productCategory: String
)

View File

@ -0,0 +1,9 @@
package com.alya.ecommerce_serang.data.model
data class User(
val username: String,
val avatar:String?,
val email:String,
val firstName:String?,
val lastName:String?
)

View File

@ -0,0 +1,35 @@
package com.alya.ecommerce_serang.data.repository
import android.util.Log
import com.alya.ecommerce_serang.data.api.response.ProductsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class ProductRepository(private val apiService: ApiService) {
suspend fun getAllProducts(): Result<List<ProductsItem>> =
withContext(Dispatchers.IO) {
try {
Log.d("ProductRepository", "Attempting to fetch products")
val response = apiService.getAllProduct().execute()
Log.d("ProductRepository", "Response received. Success: ${response.isSuccessful}")
Log.d("ProductRepository", "Response code: ${response.code()}")
Log.d("ProductRepository", "Response message: ${response.message()}")
if (response.isSuccessful) {
Result.success(response.body()?.products ?: emptyList())
} else {
Result.failure(Exception("Failed to fetch products"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
// suspend fun getCategories():List<Category>
//
// fun getProducts(query: ProductQuery) : Flow<PagingData<Product>>
// fun getRecentSearchs(): Flow<List<String>>
// suspend fun clearRecents()
// suspend fun addRecents(search:String)
// suspend fun getProduct(id:String):DetailProduct
}

View File

@ -1,31 +1,57 @@
package com.alya.ecommerce_serang.ui
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.databinding.ActivityMainBinding
import com.google.android.material.bottomnavigation.BottomNavigationView
//@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val navController by lazy {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation)
setupBottomNavigation()
observeDestinationChanges()
}
private fun setupBottomNavigation() {
binding.bottomNavigation.setupWithNavController(navController)
binding.bottomNavigation.setOnItemSelectedListener { menuItem ->
when (menuItem.itemId) {
R.id.home_item_fragment -> {
navController.navigate(R.id.homeFragment)
true
}
R.id.chat_item_fragment -> {
navController.navigate(R.id.chatFragment)
true
}
R.id.profile_item_fragment -> {
navController.navigate(R.id.profileFragment)
true
}
else -> false
}
}
}
private fun observeDestinationChanges() {
navController.addOnDestinationChangedListener { _, destination, _ ->
binding.bottomNavigation.isVisible = when (destination.id) {
R.id.homeFragment, R.id.chatFragment, R.id.profileFragment -> true
else -> false // Bottom Navigation tidak terlihat di layar lain
}
}
}
}

View File

@ -0,0 +1,31 @@
package com.alya.ecommerce_serang.ui.chat
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.alya.ecommerce_serang.R
class ChatFragment : Fragment() {
companion object {
fun newInstance() = ChatFragment()
}
private val viewModel: ChatViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TODO: Use the ViewModel
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_chat, container, false)
}
}

View File

@ -0,0 +1,7 @@
package com.alya.ecommerce_serang.ui.chat
import androidx.lifecycle.ViewModel
class ChatViewModel : ViewModel() {
// TODO: Implement the ViewModel
}

View File

@ -0,0 +1,39 @@
package com.alya.ecommerce_serang.ui.home
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.data.api.dto.Category
import com.alya.ecommerce_serang.databinding.ItemCategoryHomeBinding
import com.bumptech.glide.Glide
class HomeCategoryAdapter(
private val categories:List<Category>,
//A lambda function that will be invoked when a category item is clicked.
private val onClick:(category:Category) -> Unit
): RecyclerView.Adapter<HomeCategoryAdapter.ViewHolder>() {
/*
ViewHolder is responsible for caching and managing the view references for each item in
the RecyclerView.It binds the Category data to the corresponding views within the item layout.
*/
inner class ViewHolder(private val binding: ItemCategoryHomeBinding): RecyclerView.ViewHolder(binding.root){
fun bind(category: Category) = with(binding){
Glide.with(root).load(category.image).into(image)
name.text = category.title
root.setOnClickListener{
onClick(category)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder (
ItemCategoryHomeBinding.inflate(LayoutInflater.from(parent.context),parent,false)
)
override fun getItemCount() = categories.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(categories[position])
}
}

View File

@ -0,0 +1,145 @@
package com.alya.ecommerce_serang.ui.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.ProductsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
import com.alya.ecommerce_serang.utils.setLightStatusBar
import kotlinx.coroutines.launch
//@AndroidEntryPoint
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var viewModel: HomeViewModel
private var productAdapter: HorizontalProductAdapter? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val repository = ProductRepository(ApiConfig.getApiService())
viewModel = ViewModelProvider(
this,
// Pass a lambda that creates the ViewModel
BaseViewModelFactory {
HomeViewModel(repository)
}
)[HomeViewModel::class.java]
initUi()
setupRecyclerView()
observeData()
}
private fun setupRecyclerView() {
productAdapter = HorizontalProductAdapter(
products = emptyList(),
onClick = { product -> handleProductClick(product) }
)
binding.newProducts.apply {
adapter = productAdapter
layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
}
}
private fun observeData() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is HomeUiState.Loading -> {
binding.loading.root.isVisible = true
binding.error.root.isVisible = false
binding.home.isVisible = false
}
is HomeUiState.Success -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = false
binding.home.isVisible = true
productAdapter?.updateProducts(state.products)
}
is HomeUiState.Error -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = true
binding.home.isVisible = false
binding.error.errorMessage.text = state.message
binding.error.retryButton.setOnClickListener {
viewModel.retry()
}
}
}
}
}
}
private fun initUi() {
// For LightStatusBar
setLightStatusBar()
val banners = binding.banners
banners.offscreenPageLimit = 1
val nextItemVisiblePx = resources.getDimension(R.dimen.viewpager_next_item_visible)
val currentItemHorizontalMarginPx =
resources.getDimension(R.dimen.viewpager_current_item_horizontal_margin)
val pageTranslationX = nextItemVisiblePx + currentItemHorizontalMarginPx
banners.setPageTransformer { page, position ->
page.translationX = -pageTranslationX * position
page.scaleY = 1 - (0.25f * kotlin.math.abs(position))
}
banners.addItemDecoration(
HorizontalMarginItemDecoration(
requireContext(),
R.dimen.viewpager_current_item_horizontal_margin
)
)
}
private fun handleProductClick(product: ProductsItem) {
// Navigate to product detail
// findNavController().navigate(
// HomeFragmentDirections.actionHomeToDetail(product.id)
// )
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun showLoading(isLoading: Boolean) {
if (isLoading) {
binding.progressBar.visibility = View.VISIBLE
} else {
binding.progressBar.visibility = View.GONE
}
}
}

View File

@ -0,0 +1,60 @@
package com.alya.ecommerce_serang.ui.home
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.response.AllProductResponse
import com.alya.ecommerce_serang.data.api.response.ProductsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.ProductRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class HomeViewModel (
private val productRepository: ProductRepository
): ViewModel() {
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
val home = MutableLiveData<AllProductResponse?>(null)
constructor() : this(ProductRepository(ApiConfig.getApiService()))
init {
loadProducts()
}
private fun loadProducts() {
viewModelScope.launch {
_uiState.value = HomeUiState.Loading
productRepository.getAllProducts()
.onSuccess { products ->
_uiState.value = HomeUiState.Success(products)
}
.onFailure { error ->
_uiState.value = HomeUiState.Error(error.message ?: "Unknown error")
Log.e("ProductViewModel", "Products fetch failed", error)
}
}
}
fun retry() {
loadProducts()
}
// fun toggleWishlist(product: Product) = viewModelScope.launch {
// try {
// productRepository.toggleWishlist(product.id,product.wishlist)
// }catch (e:Exception){
//
// }
// }
}
sealed class HomeUiState {
object Loading : HomeUiState()
data class Success(val products: List<ProductsItem>) : HomeUiState()
data class Error(val message: String) : HomeUiState()
}

View File

@ -0,0 +1,52 @@
package com.alya.ecommerce_serang.ui.home
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.data.api.response.ProductsItem
import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
import com.bumptech.glide.Glide
class HorizontalProductAdapter(
private var products: List<ProductsItem>,
private val onClick: (ProductsItem) -> Unit
) : RecyclerView.Adapter<HorizontalProductAdapter.ProductViewHolder>() {
inner class ProductViewHolder(private val binding: ItemProductHorizontalBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(product: ProductsItem) = with(binding) {
itemName.text = product.name
itemPrice.text = product.price
rating.text = product.rating
// productSold.text = "${product.totalSold} sold"
// Load image using Glide
Glide.with(itemView)
// .load("${BuildConfig.BASE_URL}/product/${product.image}")
// .load("${BuildConfig.BASE_URL}/${product.image}")
.load(product.image)
.into(image)
root.setOnClickListener { onClick(product) }
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val binding = ItemProductHorizontalBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return ProductViewHolder(binding)
}
override fun getItemCount() = products.size
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
holder.bind(products[position])
}
fun updateProducts(newProducts: List<ProductsItem>) {
products = newProducts
notifyDataSetChanged()
}
}

View File

@ -0,0 +1,35 @@
package com.alya.ecommerce_serang.ui.product
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.Product
import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
import com.bumptech.glide.Glide
class ProductViewHolder(private val binding: ItemProductHorizontalBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
product: Product,
onClick: (product: Product) -> Unit,
) = with(binding) {
Glide.with(root).load(product.image).into(image)
// discount.isVisible = product.discount != null
// product.discount?.let {
// val discount = (product.discount / product.price * 100).roundToInt()
// binding.discount.text =
// root.context.getString(R.string.fragment_item_product_discount, discount)
// }
itemName.text = product.title
rating.text = String.format("%.1f", product.rating)
// val current = product.price - (product.discount ?: 0.0)
val current = product.price
itemPrice.text = root.context.getString(R.string.item_price_txt, current)
root.setOnClickListener {
onClick(product)
}
}
}

View File

@ -0,0 +1,31 @@
package com.alya.ecommerce_serang.ui.profile
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.alya.ecommerce_serang.R
class ProfileFragment : Fragment() {
companion object {
fun newInstance() = ProfileFragment()
}
private val viewModel: ProfileViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TODO: Use the ViewModel
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_profile, container, false)
}
}

View File

@ -0,0 +1,7 @@
package com.alya.ecommerce_serang.ui.profile
import androidx.lifecycle.ViewModel
class ProfileViewModel : ViewModel() {
// TODO: Implement the ViewModel
}

View File

@ -0,0 +1,7 @@
package com.alya.ecommerce_serang.utils
import android.view.LayoutInflater
import android.view.ViewGroup
typealias Inflate<T> = (LayoutInflater, ViewGroup?, Boolean) -> T

View File

@ -0,0 +1,13 @@
package com.alya.ecommerce_serang.utils
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class BaseViewModelFactory<VM : ViewModel>(
private val creator: () -> VM
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return creator() as T
}
}

View File

@ -0,0 +1,22 @@
package com.alya.ecommerce_serang.utils
import android.os.Build
import android.view.View
import android.view.WindowInsetsController
import androidx.fragment.app.Fragment
fun Fragment.setLightStatusBar(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
requireActivity().window.insetsController?.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS)
} else {
@Suppress("DEPRECATION")
requireActivity().window.decorView.systemUiVisibility = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
} else {
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
}
}

View File

@ -0,0 +1,22 @@
package com.alya.ecommerce_serang.utils
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.annotation.DimenRes
import androidx.recyclerview.widget.RecyclerView
class HorizontalMarginItemDecoration(context: Context, @DimenRes horizontalMarginInDp: Int) :
RecyclerView.ItemDecoration() {
private val horizontalMarginInPx: Int =
context.resources.getDimension(horizontalMarginInDp).toInt()
override fun getItemOffsets(
outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State
) {
outRect.right = horizontalMarginInPx
outRect.left = horizontalMarginInPx
}
}

View File

@ -0,0 +1,20 @@
package com.alya.ecommerce_serang.utils
import android.os.Parcelable
import com.alya.ecommerce_serang.data.api.dto.Category
import kotlinx.parcelize.Parcelize
@Parcelize
data class ProductQuery (
val category: Category? = null,
val search:String? = null,
val range:Pair<Float,Float> = 0f to 10000f,
val rating:Int? = null,
val discount:Int? = null,
val sort:List<Sort> = emptyList(),
val favorite:Boolean = false
): Parcelable
enum class Sort{
disconunt, voucher, shipping, delivery
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#E8ECF2" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@color/soft_gray" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFE86D" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path
android:fillColor="@android:color/white"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6z"
/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M15.55,13c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.37,-0.66 -0.11,-1.48 -0.87,-1.48L5.21,4l-0.94,-2L1,2v2h2l3.6,7.59 -1.35,2.44C4.52,15.37 5.48,17 7,17h12v-2L7,15l1.1,-2h7.45zM6.16,6h12.15l-2.76,5L8.53,11L6.16,6zM7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM17,18c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="1dp"
android:color="#33B8BBC6"/>
<corners android:radius="11dp"/>
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="1dp"
android:color="@color/soft_gray"/>
<corners android:radius="15dp"/>
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_checked="true"
android:drawable="@drawable/baseline_account_circle_24" />
<item
android:drawable="@drawable/outline_account_circle_24" />
</selector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_checked="true"
android:drawable="@drawable/baseline_chat_24" />
<item
android:drawable="@drawable/outline_chat_24" />
</selector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_checked="true"
android:drawable="@drawable/baseline_home_24" />
<item
android:drawable="@drawable/outline_home_24" />
</selector>

View File

@ -1,32 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
tools:context=".ui.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
<!-- NavHostFragment -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<!-- BottomNavigationView -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/Widget.Material3.BottomNavigationView"
app:menu="@menu/bottom_navigation_menu"
app:itemIconTint="#489EC6"
app:itemIconSize="@dimen/m3_comp_navigation_bar_active_indicator_height"/>
app:itemIconSize="@dimen/m3_comp_navigation_bar_active_indicator_height" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.chat.ChatFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello" />
</FrameLayout>

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.home.HomeFragment">
<ScrollView
android:id="@+id/home"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/searchContainer"
layout="@layout/view_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="19dp"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/banners"
android:layout_width="match_parent"
android:layout_height="132dp"
android:layout_marginTop="23dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/searchContainer"
tools:layout_editor_absoluteX="16dp" />
<TextView
android:id="@+id/categoriesText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="24dp"
android:text="@string/fragment_home_categories"
android:textColor="@color/dark"
android:textSize="22sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/banners" />
<com.google.android.material.button.MaterialButton
android:id="@+id/showAll"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:text="@string/show_all"
android:textAllCaps="false"
android:textColor="@color/blue1"
android:textSize="16sp"
app:layout_constraintBaseline_toBaselineOf="@id/categoriesText"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/categories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="19dp"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@id/categoriesText"
tools:layout_editor_absoluteX="0dp"
tools:listitem="@layout/item_category_home" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/new_products"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="39dp"
android:layout_marginBottom="32dp"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@id/categories"
tools:itemCount="5"
tools:listitem="@layout/item_section_horizontal" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<include
android:id="@+id/loading"
layout="@layout/view_loading"/>
<include
android:id="@+id/error"
layout="@layout/view_error"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- <com.zhpan.indicator.IndicatorView-->
<!-- android:id="@+id/indicator"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="20dp"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toBottomOf="@+id/banners" />-->

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.profile.ProfileFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello" />
</FrameLayout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="88dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/imageLayout"
app:layout_constraintDimensionRatio="1:1"
app:cardCornerRadius="14dp"
app:layout_constraintTop_toTopOf="parent"
app:strokeWidth="1dp"
app:strokeColor="@color/gray_1">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/image"
android:src="@drawable/makanan_ringan"
android:scaleType="centerCrop" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/name"
android:textAlignment="center"
android:text="@string/fragment_home_item_categories"
app:layout_constraintTop_toBottomOf="@id/imageLayout"
android:textSize="16sp"
android:textColor="@color/dark"
android:layout_marginTop="12dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="185dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.card.MaterialCardView
android:id="@+id/imageLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:cardCornerRadius="14dp"
app:layout_constraintDimensionRatio="272:218"
app:layout_constraintTop_toTopOf="parent"
app:cardElevation="0dp"
app:strokeColor="@color/gray_1"
app:strokeWidth="1dp">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@color/blue1" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/item_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="17dp"
android:text="Banana"
android:textColor="@color/dark"
android:textSize="18sp"
app:layout_constraintTop_toBottomOf="@id/imageLayout" />
<TextView
android:id="@+id/item_price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/item_price_txt"
android:textColor="@color/dark"
android:textStyle="bold"
android:textSize="18sp"
app:layout_constraintTop_toBottomOf="@id/item_name" />
<TextView
android:id="@+id/rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rating_background"
android:drawablePadding="4dp"
android:paddingStart="7dp"
android:paddingTop="5dp"
android:paddingEnd="11dp"
android:paddingBottom="3dp"
android:text="4.5"
android:textColor="@color/dark"
android:textSize="10sp"
android:textAlignment="center"
android:gravity="center"
app:drawableStartCompat="@drawable/baseline_star_24"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/item_price" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginVertical="20dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:textColor="@color/dark"
android:text="All grocery"
android:textSize="22sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/showAll"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:text="@string/show_all"
android:textAllCaps="false"
android:textColor="@color/blue1"
android:textSize="16sp"
app:layout_constraintBaseline_toBaselineOf="@id/title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/products"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="23dp"
android:clipToPadding="false"
android:clipChildren="false"
app:layout_constraintTop_toBottomOf="@id/title"
android:layout_marginTop="16dp"
android:orientation="horizontal"
tools:listitem="@layout/item_product_horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="20dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:textColor="@color/dark"
android:text="All grocery"
android:textSize="22sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/showAll"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:text="@string/show_all"
android:textAllCaps="false"
android:textColor="@color/blue1"
android:textSize="16sp"
app:layout_constraintBaseline_toBaselineOf="@id/title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/products"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingVertical="25dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@id/title"
tools:itemCount="5"
tools:layout_editor_absoluteX="16dp"
tools:listitem="@layout/item_product_horizontal" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:id="@+id/errorMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/retryButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="@+id/retryButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Retry"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorMessage" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/search"
android:layout_width="0dp"
android:layout_height="48dp"
android:background="@drawable/search_background"
android:hint="@string/fragment_home_search"
android:textColor="@color/soft_gray"
android:textSize="16sp"
android:layout_marginStart="16dp"
android:drawablePadding="8dp"
android:paddingHorizontal="25dp"
android:imeOptions="actionSearch"
android:inputType="text"
app:layout_constraintEnd_toStartOf="@id/btn_notification"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:drawableStart="@drawable/baseline_search_24" />
<ImageButton
android:id="@+id/btn_notification"
android:layout_width="0dp"
android:layout_height="0dp"
android:backgroundTint="@color/white"
android:src="@drawable/outline_notifications_24"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintBottom_toBottomOf="@id/search"
app:layout_constraintEnd_toStartOf="@id/btn_cart"
app:layout_constraintTop_toTopOf="@id/search"/>
<ImageButton
android:id="@+id/btn_cart"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="16dp"
android:padding="8dp"
android:backgroundTint="@color/white"
android:src="@drawable/outline_shopping_cart_24"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintBottom_toBottomOf="@id/search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/search"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/page_1"
android:id="@+id/home_item_fragment"
android:enabled="true"
android:icon="@drawable/outline_home_24"
android:icon="@drawable/selector_home"
android:title="Beranda" />
<item
android:id="@+id/page_2"
android:id="@+id/chat_item_fragment"
android:enabled="true"
android:icon="@drawable/outline_chat_24"
android:icon="@drawable/selector_chat"
android:title="Pesan" />
<item
android:id="@+id/page_3"
android:id="@+id/profile_item_fragment"
android:enabled="true"
android:icon="@drawable/outline_account_circle_24"
android:icon="@drawable/selector_account"
android:title="Profil" />
</menu>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.alya.ecommerce_serang.ui.home.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/profileFragment"
android:name="com.alya.ecommerce_serang.ui.profile.ProfileFragment"
android:label="fragment_profile"
tools:layout="@layout/fragment_profile" />
<fragment
android:id="@+id/chatFragment"
android:name="com.alya.ecommerce_serang.ui.chat.ChatFragment"
android:label="fragment_chat"
tools:layout="@layout/fragment_chat" />
</navigation>

View File

@ -2,4 +2,8 @@
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="gray_1">#E8ECF2</color>
<color name="soft_gray">#7D8FAB</color>
<color name="dark">#303733</color>
<color name="blue1">#489EC6</color>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="viewpager_current_item_horizontal_margin">32dp</dimen>
<dimen name="viewpager_next_item_visible">16dp</dimen>
</resources>

View File

@ -1,3 +1,14 @@
<resources>
<string name="app_name">ecommerce_serang</string>
<!--Placeholder-->
<string name="fragment_home_search">Cari produk</string>
<string name="fragment_home_categories">Kategori Produk</string>
<string name="fragment_home_item_categories">Makanan Ringan</string>
<string name="show_all">Lainnya</string>
<string name="item_price_txt">Rp%.1f</string>
<string name="retry">Coba lagi\n</string>
<string name="error_loading">Terdapat error...</string>
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.1.13</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>

View File

@ -1,4 +1,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath ("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.1")
// classpath("com.google.dagger:hilt-android-gradle-plugin:2.55")
}
}
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false

View File

@ -1,5 +1,7 @@
[versions]
agp = "8.5.2"
hiltAndroid = "2.51"
hiltLifecycleViewmodel = "1.0.0-alpha03"
kotlin = "1.9.0"
coreKtx = "1.10.1"
junit = "4.13.2"
@ -9,9 +11,17 @@ appcompat = "1.7.0"
material = "1.12.0"
activity = "1.9.2"
constraintlayout = "2.1.4"
legacySupportV4 = "1.0.0"
lifecycleLivedataKtx = "2.8.7"
lifecycleViewmodelKtx = "2.8.7"
fragmentKtx = "1.5.6"
navigationFragmentKtx = "2.8.5"
navigationUiKtx = "2.8.5"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-hilt-lifecycle-viewmodel = { module = "androidx.hilt:hilt-lifecycle-viewmodel", version.ref = "hiltLifecycleViewmodel" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@ -19,6 +29,12 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" }
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }